From e74e7fa82c81c980edfbde9e806713b25e7e6cd3 Mon Sep 17 00:00:00 2001 From: Nathan Marrs Date: Thu, 4 May 2023 13:44:20 -0600 Subject: [PATCH 001/179] docs: Canvas: Update docs for v10 (#67793) Co-authored-by: Isabel <76437239+imatwawana@users.noreply.github.com> --- .../panels-visualizations/visualizations/canvas/index.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/sources/panels-visualizations/visualizations/canvas/index.md b/docs/sources/panels-visualizations/visualizations/canvas/index.md index 578f79039aa..d174271322a 100644 --- a/docs/sources/panels-visualizations/visualizations/canvas/index.md +++ b/docs/sources/panels-visualizations/visualizations/canvas/index.md @@ -54,6 +54,10 @@ When building a canvas panel, you can connect elements together to create more c {{< video-embed src="/media/docs/grafana/canvas-connections-9-4-0.mp4" max-width="750px" caption="Canvas connections demo" >}} +You can set both the size and color of connections based on fixed or field values. To do so, enter into panel edit mode, select the connection, and modify the connection's properties in the panel editor. + +{{< figure src="/media/docs/grafana/screenshot-grafana-10-0-canvas-service-graph.png" max-width="750px" caption="Canvas service graph" >}} + ## Canvas editing ### Inline editor From e7cbe0276e3a6f41c31dd8e1ac16ccaa90b184f3 Mon Sep 17 00:00:00 2001 From: Elliot Kirk Date: Thu, 4 May 2023 17:35:10 -0700 Subject: [PATCH 002/179] Faro: Adding Faro data source selector feature toggle (#66623) --- .../configure-grafana/feature-toggles/index.md | 1 + packages/grafana-data/src/types/featureToggles.gen.ts | 1 + pkg/services/featuremgmt/registry.go | 7 +++++++ pkg/services/featuremgmt/toggles_gen.csv | 1 + pkg/services/featuremgmt/toggles_gen.go | 4 ++++ 5 files changed, 14 insertions(+) diff --git a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md index ee00de1835e..dba553cc3fc 100644 --- a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md +++ b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md @@ -58,6 +58,7 @@ Some stable features are enabled by default. You can disable a stable feature by | `alertingNoNormalState` | Stop maintaining state of alerts that are not firing | | `renderAuthJWT` | Uses JWT-based auth for rendering instead of relying on remote cache | | `enableElasticsearchBackendQuerying` | Enable the processing of queries and responses in the Elasticsearch data source through backend | +| `faroDatasourceSelector` | Enable the data source selector within the Frontend Apps section of the Frontend Observability | | `enableDatagridEditing` | Enables the edit functionality in the datagrid panel | ## Alpha feature toggles diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index 7875ba2d63a..a3e1fd422c7 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -95,5 +95,6 @@ export interface FeatureToggles { pluginsAPIManifestKey?: boolean; advancedDataSourcePicker?: boolean; opensearchDetectVersion?: boolean; + faroDatasourceSelector?: boolean; enableDatagridEditing?: boolean; } diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index b142ca014f7..d8ccafd62f3 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -516,6 +516,13 @@ var ( FrontendOnly: true, Owner: awsPluginsSquad, }, + { + Name: "faroDatasourceSelector", + Description: "Enable the data source selector within the Frontend Apps section of the Frontend Observability ", + State: FeatureStateBeta, + FrontendOnly: true, + Owner: appO11ySquad, + }, { Name: "enableDatagridEditing", Description: "Enables the edit functionality in the datagrid panel", diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv index 612ff973b0c..d71c6171a70 100644 --- a/pkg/services/featuremgmt/toggles_gen.csv +++ b/pkg/services/featuremgmt/toggles_gen.csv @@ -76,4 +76,5 @@ authenticationConfigUI,alpha,@grafana/grafana-authnz-team,false,false,false,fals pluginsAPIManifestKey,alpha,@grafana/plugins-platform-backend,false,false,false,false advancedDataSourcePicker,alpha,@grafana/dashboards-squad,false,false,false,true opensearchDetectVersion,alpha,@grafana/aws-plugins,false,false,false,true +faroDatasourceSelector,beta,@grafana/app-o11y,false,false,false,true enableDatagridEditing,beta,@grafana/grafana-bi-squad,false,false,false,true diff --git a/pkg/services/featuremgmt/toggles_gen.go b/pkg/services/featuremgmt/toggles_gen.go index b1c017b10b1..53bb402ff0e 100644 --- a/pkg/services/featuremgmt/toggles_gen.go +++ b/pkg/services/featuremgmt/toggles_gen.go @@ -315,6 +315,10 @@ const ( // Enable version detection in OpenSearch FlagOpensearchDetectVersion = "opensearchDetectVersion" + // FlagFaroDatasourceSelector + // Enable the data source selector within the Frontend Apps section of the Frontend Observability + FlagFaroDatasourceSelector = "faroDatasourceSelector" + // FlagEnableDatagridEditing // Enables the edit functionality in the datagrid panel FlagEnableDatagridEditing = "enableDatagridEditing" From 21459c7c97675e06b84e1bbe137623a93c47e23b Mon Sep 17 00:00:00 2001 From: Levente Balogh Date: Fri, 5 May 2023 09:38:18 +0200 Subject: [PATCH 003/179] Connections: Show core datasource plugins as well (#67815) fix: add a new hook & selector that can show core plugins by default --- .../tabs/ConnectData/ConnectData.tsx | 9 +- .../features/plugins/admin/pages/Browse.tsx | 25 ++--- .../app/features/plugins/admin/state/hooks.ts | 33 ++----- .../plugins/admin/state/selectors.test.ts | 94 +++++++++++++------ .../features/plugins/admin/state/selectors.ts | 74 ++++++++------- 5 files changed, 133 insertions(+), 102 deletions(-) diff --git a/public/app/features/connections/tabs/ConnectData/ConnectData.tsx b/public/app/features/connections/tabs/ConnectData/ConnectData.tsx index 52e2ac97b0f..cbd2d0a3264 100644 --- a/public/app/features/connections/tabs/ConnectData/ConnectData.tsx +++ b/public/app/features/connections/tabs/ConnectData/ConnectData.tsx @@ -4,7 +4,7 @@ import React, { useMemo, useState } from 'react'; import { PluginType } from '@grafana/data'; import { useStyles2, LoadingPlaceholder } from '@grafana/ui'; import { contextSrv } from 'app/core/core'; -import { useGetAllWithFilters } from 'app/features/plugins/admin/state/hooks'; +import { useGetAll } from 'app/features/plugins/admin/state/hooks'; import { AccessControlAction } from 'app/types'; import { ROUTES } from '../../constants'; @@ -38,10 +38,9 @@ export function AddNewConnection() { setSearchTerm(e.currentTarget.value.toLowerCase()); }; - const { isLoading, error, plugins } = useGetAllWithFilters({ - query: searchTerm, - filterBy: '', - filterByType: PluginType.datasource, + const { isLoading, error, plugins } = useGetAll({ + keyword: searchTerm, + type: PluginType.datasource, }); const cardGridItems = useMemo( diff --git a/public/app/features/plugins/admin/pages/Browse.tsx b/public/app/features/plugins/admin/pages/Browse.tsx index 8a78fc20c4d..ac1856c86cc 100644 --- a/public/app/features/plugins/admin/pages/Browse.tsx +++ b/public/app/features/plugins/admin/pages/Browse.tsx @@ -2,7 +2,7 @@ import { css } from '@emotion/css'; import React, { ReactElement } from 'react'; import { useLocation } from 'react-router-dom'; -import { SelectableValue, GrafanaTheme2 } from '@grafana/data'; +import { SelectableValue, GrafanaTheme2, PluginType } from '@grafana/data'; import { config, locationSearchToObject } from '@grafana/runtime'; import { LoadingPlaceholder, Select, RadioButtonGroup, useStyles2, Tooltip, Field } from '@grafana/ui'; import { Page } from 'app/core/components/Page/Page'; @@ -16,7 +16,7 @@ import { PluginList } from '../components/PluginList'; import { SearchField } from '../components/SearchField'; import { Sorters } from '../helpers'; import { useHistory } from '../hooks/useHistory'; -import { useGetAllWithFilters, useIsRemotePluginsAvailable, useDisplayMode } from '../state/hooks'; +import { useGetAll, useIsRemotePluginsAvailable, useDisplayMode } from '../state/hooks'; import { PluginListDisplayMode } from '../types'; export default function Browse({ route }: GrafanaRouteComponentProps): ReactElement | null { @@ -27,16 +27,19 @@ export default function Browse({ route }: GrafanaRouteComponentProps): ReactElem const styles = useStyles2(getStyles); const history = useHistory(); const remotePluginsAvailable = useIsRemotePluginsAvailable(); - const query = (locationSearch.q as string) || ''; + const keyword = (locationSearch.q as string) || ''; const filterBy = (locationSearch.filterBy as string) || 'installed'; - const filterByType = (locationSearch.filterByType as string) || 'all'; + const filterByType = (locationSearch.filterByType as PluginType | 'all') || 'all'; const sortBy = (locationSearch.sortBy as Sorters) || Sorters.nameAsc; - const { isLoading, error, plugins } = useGetAllWithFilters({ - query, - filterBy, - filterByType, - sortBy, - }); + const { isLoading, error, plugins } = useGetAll( + { + keyword, + type: filterByType !== 'all' ? filterByType : undefined, + isInstalled: filterBy === 'installed' ? true : undefined, + isCore: filterBy === 'installed' ? undefined : false, // We only would like to show core plugins when the user filters to installed plugins + }, + sortBy + ); const filterByOptions = [ { value: 'all', label: 'All' }, { value: 'installed', label: 'Installed' }, @@ -81,7 +84,7 @@ export default function Browse({ route }: GrafanaRouteComponentProps): ReactElem - + {/* Filter by type */} diff --git a/public/app/features/plugins/admin/state/hooks.ts b/public/app/features/plugins/admin/state/hooks.ts index 98fd4a43972..013e0b00b92 100644 --- a/public/app/features/plugins/admin/state/hooks.ts +++ b/public/app/features/plugins/admin/state/hooks.ts @@ -1,4 +1,4 @@ -import { useEffect } from 'react'; +import { useEffect, useMemo } from 'react'; import { PluginError } from '@grafana/data'; import { useDispatch, useSelector } from 'app/types'; @@ -9,48 +9,31 @@ import { CatalogPlugin, PluginListDisplayMode } from '../types'; import { fetchAll, fetchDetails, fetchRemotePlugins, install, uninstall, fetchAllLocal, unsetInstall } from './actions'; import { setDisplayMode } from './reducer'; import { - find, - selectAll, + selectPlugins, selectById, selectIsRequestPending, selectRequestError, selectIsRequestNotFetched, selectDisplayMode, selectPluginErrors, + type PluginFilters, } from './selectors'; -type Filters = { - query?: string; // Note: this will be an escaped regex string as it comes from `FilterInput` - filterBy?: string; - filterByType?: string; - sortBy?: Sorters; -}; - -export const useGetAllWithFilters = ({ - query = '', - filterBy = 'installed', - filterByType = 'all', - sortBy = Sorters.nameAsc, -}: Filters) => { +export const useGetAll = (filters: PluginFilters, sortBy: Sorters = Sorters.nameAsc) => { useFetchAll(); - const filtered = useSelector(find(query, filterBy, filterByType)); + const selector = useMemo(() => selectPlugins(filters), [filters]); + const plugins = useSelector(selector); const { isLoading, error } = useFetchStatus(); - const sortedAndFiltered = sortPlugins(filtered, sortBy); + const sortedPlugins = sortPlugins(plugins, sortBy); return { isLoading, error, - plugins: sortedAndFiltered, + plugins: sortedPlugins, }; }; -export const useGetAll = (): CatalogPlugin[] => { - useFetchAll(); - - return useSelector(selectAll); -}; - export const useGetSingle = (id: string): CatalogPlugin | undefined => { useFetchAll(); useFetchDetails(id); diff --git a/public/app/features/plugins/admin/state/selectors.test.ts b/public/app/features/plugins/admin/state/selectors.test.ts index c038a4930f2..84441ca42af 100644 --- a/public/app/features/plugins/admin/state/selectors.test.ts +++ b/public/app/features/plugins/admin/state/selectors.test.ts @@ -3,64 +3,100 @@ import { configureStore } from 'app/store/configureStore'; import { getCatalogPluginMock, getPluginsStateMock } from '../__mocks__'; -import { find } from './selectors'; +import { selectPlugins } from './selectors'; describe('Plugins Selectors', () => { - describe('find()', () => { + describe('selectPlugins()', () => { const store = configureStore({ plugins: getPluginsStateMock([ - getCatalogPluginMock({ id: 'plugin-1', name: 'Plugin 1', isInstalled: true, type: PluginType.datasource }), - getCatalogPluginMock({ id: 'plugin-2', name: 'Plugin 2', isInstalled: true, type: PluginType.datasource }), - getCatalogPluginMock({ id: 'plugin-3', name: 'Plugin 3', isInstalled: true, type: PluginType.panel }), - getCatalogPluginMock({ id: 'plugin-4', name: 'Plugin 4', isInstalled: false, type: PluginType.panel }), - getCatalogPluginMock({ id: 'plugin-5', name: 'Plugin 5', isInstalled: true, type: PluginType.app }), + getCatalogPluginMock({ + id: 'plugin-1', + name: 'Plugin 1', + isInstalled: true, + type: PluginType.datasource, + isCore: true, + }), + getCatalogPluginMock({ + id: 'plugin-2', + name: 'Plugin 2', + isInstalled: true, + type: PluginType.datasource, + isCore: true, + }), + getCatalogPluginMock({ + id: 'plugin-3', + name: 'Plugin 3', + isInstalled: true, + type: PluginType.panel, + isCore: false, + }), + getCatalogPluginMock({ + id: 'plugin-4', + name: 'Plugin 4', + isInstalled: false, + type: PluginType.panel, + isCore: false, + }), + getCatalogPluginMock({ + id: 'plugin-5', + name: 'Plugin 5', + isInstalled: true, + type: PluginType.app, + isCore: false, + }), ]), }); it('should return all plugins if there are no filters', () => { - const query = ''; - const filterBy = 'all'; - const filterByType = 'all'; - const results = find(query, filterBy, filterByType)(store.getState()); + const results = selectPlugins({})(store.getState()); expect(results).toHaveLength(5); }); - it('should be possible to search only by the "query"', () => { - const query = 'Plugin 3'; - const filterBy = 'all'; - const filterByType = 'all'; - const results = find(query, filterBy, filterByType)(store.getState()); + it('should be possible to search only by the "keyword"', () => { + const results = selectPlugins({ keyword: 'Plugin 3' })(store.getState()); expect(results).toHaveLength(1); expect(results[0].name).toBe('Plugin 3'); }); it('should be possible to search by plugin type', () => { - const query = ''; - const filterBy = 'all'; - const filterByType = PluginType.panel; - const results = find(query, filterBy, filterByType)(store.getState()); + const results = selectPlugins({ type: PluginType.panel })(store.getState()); expect(results).toHaveLength(2); expect(results.map(({ name }) => name)).toEqual(['Plugin 3', 'Plugin 4']); }); - it('should be possible to search by plugin state (installed / all)', () => { - const query = ''; - const filterBy = 'installed'; - const filterByType = 'all'; - const results = find(query, filterBy, filterByType)(store.getState()); + it('should be possible to search for core plugins', () => { + const results = selectPlugins({ isCore: true })(store.getState()); + + expect(results).toHaveLength(2); + expect(results.map(({ name }) => name)).toEqual(['Plugin 1', 'Plugin 2']); + }); + + it('should be possible to exclude core plugins from the search', () => { + const results = selectPlugins({ isCore: false })(store.getState()); + + expect(results).toHaveLength(3); + expect(results.map(({ name }) => name)).toEqual(['Plugin 3', 'Plugin 4', 'Plugin 5']); + }); + + it('should be possible to only search for installed plugins', () => { + const results = selectPlugins({ isInstalled: true })(store.getState()); expect(results).toHaveLength(4); expect(results.map(({ name }) => name)).toEqual(['Plugin 1', 'Plugin 2', 'Plugin 3', 'Plugin 5']); }); + it('should be possible to only search for not yet installed plugins', () => { + const results = selectPlugins({ isInstalled: false })(store.getState()); + + expect(results).toHaveLength(1); + expect(results.map(({ name }) => name)).toEqual(['Plugin 4']); + }); + it('should be possible to search by multiple filters', () => { - const query = '2'; - const filterBy = 'all'; - const filterByType = PluginType.datasource; - const results = find(query, filterBy, filterByType)(store.getState()); + const results = selectPlugins({ keyword: '2', type: PluginType.datasource })(store.getState()); expect(results).toHaveLength(1); expect(results[0].name).toBe('Plugin 2'); diff --git a/public/app/features/plugins/admin/state/selectors.ts b/public/app/features/plugins/admin/state/selectors.ts index f6068facdd8..c8360328d17 100644 --- a/public/app/features/plugins/admin/state/selectors.ts +++ b/public/app/features/plugins/admin/state/selectors.ts @@ -1,8 +1,8 @@ import { createSelector } from '@reduxjs/toolkit'; -import { PluginError, PluginErrorCode, unEscapeStringFromRegex } from '@grafana/data'; +import { PluginError, PluginErrorCode, PluginType, unEscapeStringFromRegex } from '@grafana/data'; -import { RequestStatus, PluginCatalogStoreState, CatalogPlugin } from '../types'; +import { RequestStatus, PluginCatalogStoreState } from '../types'; import { pluginsAdapter } from './reducer'; @@ -14,44 +14,54 @@ export const selectDisplayMode = createSelector(selectRoot, ({ settings }) => se export const { selectAll, selectById } = pluginsAdapter.getSelectors(selectItems); -const findByState = (state: string) => - createSelector(selectAll, (plugins) => - plugins.filter((plugin) => (state === 'installed' ? plugin.isInstalled : !plugin.isCore)) - ); +export type PluginFilters = { + // Searches for a string in certain fields (e.g. "name" or "orgName") + // (Note: this will be an escaped regex string as it comes from `FilterInput`) + keyword?: string; -type PluginFilters = { - state: string; - type: string; + // (Optional, only applied if set) + type?: PluginType; + + // (Optional, only applied if set) + isCore?: boolean; + + // (Optional, only applied if set) + isInstalled?: boolean; + + // (Optional, only applied if set) + isEnterprise?: boolean; }; -const findPluginsByFilters = (filters: PluginFilters) => - createSelector(findByState(filters.state), (plugins) => - plugins.filter((plugin) => filters.type === 'all' || plugin.type === filters.type) - ); +export const selectPlugins = (filters: PluginFilters) => + createSelector(selectAll, (plugins) => { + const keyword = filters.keyword ? unEscapeStringFromRegex(filters.keyword.toLowerCase()) : ''; -const findByKeyword = (plugins: CatalogPlugin[], query: string) => { - if (query === '') { - return plugins; - } + return plugins.filter((plugin) => { + const fieldsToSearchIn = [plugin.name, plugin.orgName].filter(Boolean).map((f) => f.toLowerCase()); - return plugins.filter((plugin) => { - const fields: String[] = []; - if (plugin.name) { - fields.push(plugin.name.toLowerCase()); - } + if (keyword && !fieldsToSearchIn.some((f) => f.includes(keyword))) { + return false; + } - if (plugin.orgName) { - fields.push(plugin.orgName.toLowerCase()); - } + if (filters.type && plugin.type !== filters.type) { + return false; + } - return fields.some((f) => f.includes(unEscapeStringFromRegex(query).toLowerCase())); + if (filters.isInstalled !== undefined && plugin.isInstalled !== filters.isInstalled) { + return false; + } + + if (filters.isCore !== undefined && plugin.isCore !== filters.isCore) { + return false; + } + + if (filters.isEnterprise !== undefined && plugin.isEnterprise !== filters.isEnterprise) { + return false; + } + + return true; + }); }); -}; - -export const find = (searchBy: string, filterBy: string, filterByType: string) => - createSelector(findPluginsByFilters({ state: filterBy, type: filterByType }), (filteredPlugins) => - findByKeyword(filteredPlugins, searchBy) - ); export const selectPluginErrors = createSelector(selectAll, (plugins) => plugins From fcb14d2548d68dadb981f24dd76489088dd232d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 5 May 2023 09:54:43 +0200 Subject: [PATCH 004/179] InspectDrawer: Fixes issue with double scrollbars (#67884) * InspectDrawer: Fixes issue with double scrollbars * Fixed minor issue --- packages/grafana-ui/src/components/Table/Table.tsx | 9 ++++++++- public/app/features/inspector/InspectDataTab.tsx | 6 +----- public/app/features/inspector/styles.ts | 2 +- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/packages/grafana-ui/src/components/Table/Table.tsx b/packages/grafana-ui/src/components/Table/Table.tsx index 2f59657fbd4..11559e32c57 100644 --- a/packages/grafana-ui/src/components/Table/Table.tsx +++ b/packages/grafana-ui/src/components/Table/Table.tsx @@ -318,7 +318,14 @@ export const Table = memo((props: Props) => { }; return ( -
+
{!noHeader && ( diff --git a/public/app/features/inspector/InspectDataTab.tsx b/public/app/features/inspector/InspectDataTab.tsx index 88e93c13984..666eff6d56f 100644 --- a/public/app/features/inspector/InspectDataTab.tsx +++ b/public/app/features/inspector/InspectDataTab.tsx @@ -285,11 +285,7 @@ export class InspectDataTab extends PureComponent { return null; } - return ( -
- - - ); + return
; }} diff --git a/public/app/features/inspector/styles.ts b/public/app/features/inspector/styles.ts index 9e72803d2fa..888793288ba 100644 --- a/public/app/features/inspector/styles.ts +++ b/public/app/features/inspector/styles.ts @@ -17,6 +17,7 @@ export const getPanelInspectorStyles2 = (theme: GrafanaTheme2) => { height: 100%; width: 100%; flex: 1 1 0; + min-height: 0; `, toolbar: css` display: flex; @@ -32,7 +33,6 @@ export const getPanelInspectorStyles2 = (theme: GrafanaTheme2) => { content: css` flex-grow: 1; height: 100%; - overflow: scroll; `, editor: css` font-family: monospace; From 20217db100983303588ffbebca68fd023c80f490 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 5 May 2023 11:31:02 +0200 Subject: [PATCH 005/179] Drawer: Introduce a size property that set's width percentage and minWidth (#67809) * Drawer: Introduce drawer size that sets width and min-width * media queries * Change large drawer to 75% * Change news drawer to medium as the news items have better layout then with images on the side * Tweaks and fixed inline drawer issue * review fixes * Deprecate inline, update mdx docs * remove inline var --- .../src/components/Drawer/Drawer.mdx | 12 ++- .../src/components/Drawer/Drawer.story.tsx | 38 +------- .../src/components/Drawer/Drawer.tsx | 91 +++++++++++++------ .../AppChrome/News/NewsContainer.tsx | 7 +- .../components/HelpWizard/HelpWizard.tsx | 2 +- .../components/Inspector/InspectContent.tsx | 1 - .../SaveDashboard/SaveDashboardDrawer.tsx | 1 - 7 files changed, 82 insertions(+), 70 deletions(-) diff --git a/packages/grafana-ui/src/components/Drawer/Drawer.mdx b/packages/grafana-ui/src/components/Drawer/Drawer.mdx index f8afb9b5c3d..26a2358e217 100644 --- a/packages/grafana-ui/src/components/Drawer/Drawer.mdx +++ b/packages/grafana-ui/src/components/Drawer/Drawer.mdx @@ -21,10 +21,20 @@ onClose = () => { }; return ( - +
Put your Drawer content here
); ``` +## Sizes + +The Drawer supports 3 sizes: `sm`, `md`, and `lg`. This option defines a width in percentage and as well as a min-width. + +- sm: width = 25vh and min-width = 384px +- md: width = 50vh and min-width = 568px +- lg: width = 75vh and min-width = 744px + +## Props + diff --git a/packages/grafana-ui/src/components/Drawer/Drawer.story.tsx b/packages/grafana-ui/src/components/Drawer/Drawer.story.tsx index 9ffec537740..d762336e438 100644 --- a/packages/grafana-ui/src/components/Drawer/Drawer.story.tsx +++ b/packages/grafana-ui/src/components/Drawer/Drawer.story.tsx @@ -22,7 +22,6 @@ const meta: ComponentMeta = { args: { closeOnMaskClick: true, scrollableContent: false, - width: '40%', expandable: false, subtitle: 'This is a subtitle.', }, @@ -142,42 +141,6 @@ LongContent.args = { title: 'Drawer title with long content', }; -export const InLine: ComponentStory = (args) => { - const [isOpen, setIsOpen] = useState(false); - return ( - <> -
- - {isOpen && ( - setIsOpen(false)}> -
    -
  • this
  • -
  • is
  • -
  • a
  • -
  • list
  • -
  • of
  • -
  • menu
  • -
  • items
  • -
-
- )} -
- - ); -}; -InLine.args = { - title: 'Drawer title inline', - inline: true, -}; - export const WithTabs: ComponentStory = (args) => { const [isOpen, setIsOpen] = useState(false); const [activeTab, setActiveTab] = useState('options'); @@ -206,6 +169,7 @@ export const WithTabs: ComponentStory = (args) => { ); }; + WithTabs.args = { title: 'Drawer title with tabs', }; diff --git a/packages/grafana-ui/src/components/Drawer/Drawer.tsx b/packages/grafana-ui/src/components/Drawer/Drawer.tsx index 7c5efda7ba7..1aad5038d33 100644 --- a/packages/grafana-ui/src/components/Drawer/Drawer.tsx +++ b/packages/grafana-ui/src/components/Drawer/Drawer.tsx @@ -1,9 +1,9 @@ -import { css } from '@emotion/css'; +import { css, cx } from '@emotion/css'; import { useDialog } from '@react-aria/dialog'; import { FocusScope } from '@react-aria/focus'; import { useOverlay } from '@react-aria/overlays'; import RcDrawer from 'rc-drawer'; -import React, { CSSProperties, ReactNode, useState, useEffect } from 'react'; +import React, { ReactNode, useState, useEffect } from 'react'; import { GrafanaTheme2 } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; @@ -20,12 +20,21 @@ export interface Props { subtitle?: ReactNode; /** Should the Drawer be closable by clicking on the mask, defaults to true */ closeOnMaskClick?: boolean; - /** Render the drawer inside a container on the page */ + /** @deprecated */ inline?: boolean; - /** Either a number in px or a string with unit postfix */ + /** + * @deprecated use the size property instead + **/ width?: number | string; /** Should the Drawer be expandable to full width */ expandable?: boolean; + /** + * Specifies the width and min-width. + * sm = width 25vw & min-width 384px + * md = width 50vw & min-width 568px + * lg = width 75vw & min-width 744px + **/ + size?: 'sm' | 'md' | 'lg'; /** Tabs */ tabs?: React.ReactNode; /** Set to true if the component rendered within in drawer content has its own scroll */ @@ -36,20 +45,19 @@ export interface Props { export function Drawer({ children, - inline = false, onClose, closeOnMaskClick = true, scrollableContent = false, title, subtitle, - width = '40%', + width, + size = 'md', expandable = false, tabs, }: Props) { - const drawerStyles = useStyles2(getStyles); + const styles = useStyles2(getStyles); const [isExpanded, setIsExpanded] = useState(false); const [isOpen, setIsOpen] = useState(false); - const currentWidth = isExpanded ? '100%' : width; const overlayRef = React.useRef(null); const { dialogProps, titleProps } = useDialog({}, overlayRef); const { overlayProps } = useOverlay( @@ -66,31 +74,29 @@ export function Drawer({ setIsOpen(true); }, []); - const content =
{children}
; - const style: CSSProperties = {}; - if (inline) { - style.position = 'absolute'; - } + // deprecated width prop now defaults to empty string which make the size prop take over + const fixedWidth = isExpanded ? '100%' : width ?? ''; + const rootClass = cx(styles.drawer, !fixedWidth && styles.sizes[size]); + const content =
{children}
; return ( @@ -100,14 +106,14 @@ export function Drawer({ ? selectors.components.Drawer.General.title(title) : selectors.components.Drawer.General.title('no title') } - className={drawerStyles.container} + className={styles.container} {...overlayProps} {...dialogProps} ref={overlayRef} > {typeof title === 'string' && ( -
-
+
+
{expandable && !isExpanded && (
-
+

{title}

{typeof subtitle === 'string' &&
{subtitle}
} {typeof subtitle !== 'string' && subtitle} - {tabs &&
{tabs}
} + {tabs &&
{tabs}
}
)} {typeof title !== 'string' && title} -
+
{!scrollableContent ? content : {content}}
@@ -162,10 +168,39 @@ const getStyles = (theme: GrafanaTheme2) => { box-shadow: ${theme.shadows.z3}; ${theme.breakpoints.down('sm')} { - width: 100% !important; + width: calc(100% - ${theme.spacing(2)}) !important; + min-width: 0 !important; } } `, + sizes: { + sm: css({ + '.rc-drawer-content-wrapper': { + label: 'drawer-sm', + width: '25vw', + minWidth: theme.spacing(48), + }, + }), + md: css({ + '.rc-drawer-content-wrapper': { + label: 'drawer-md', + width: '50vw', + minWidth: theme.spacing(66), + }, + }), + lg: css({ + '.rc-drawer-content-wrapper': { + label: 'drawer-lg', + width: '75vw', + minWidth: theme.spacing(93), + + [theme.breakpoints.down('md')]: { + width: `calc(100% - ${theme.spacing(2)}) !important`, + minWidth: 0, + }, + }, + }), + }, drawerContent: css` background-color: ${theme.colors.background.primary} !important; display: flex; diff --git a/public/app/core/components/AppChrome/News/NewsContainer.tsx b/public/app/core/components/AppChrome/News/NewsContainer.tsx index 727e4122570..80c6a2099f0 100644 --- a/public/app/core/components/AppChrome/News/NewsContainer.tsx +++ b/public/app/core/components/AppChrome/News/NewsContainer.tsx @@ -22,7 +22,12 @@ export function NewsContainer({ className }: NewsContainerProps) { <> {showNewsDrawer && ( - + )} diff --git a/public/app/features/dashboard/components/HelpWizard/HelpWizard.tsx b/public/app/features/dashboard/components/HelpWizard/HelpWizard.tsx index f2af5932fbf..541dc7bba11 100644 --- a/public/app/features/dashboard/components/HelpWizard/HelpWizard.tsx +++ b/public/app/features/dashboard/components/HelpWizard/HelpWizard.tsx @@ -72,7 +72,7 @@ export function HelpWizard({ panel, plugin, onClose }: Props) { return ( From f5ac099907febb5c559f86e5e9960745013652af Mon Sep 17 00:00:00 2001 From: Ivana Huckova <30407135+ivanahuckova@users.noreply.github.com> Date: Fri, 5 May 2023 11:35:30 +0200 Subject: [PATCH 006/179] Elasticsearch: Run requestAllIndices trough resource call if enableElasticsearchBackendQuerying enabled (#67825) * Elasticsearch: Run requestAllIndices trough resource call if enabled * Unlock resource call path * Fix lint --- pkg/tsdb/elasticsearch/elasticsearch.go | 4 +- .../elasticsearch/datasource.test.ts | 174 ++++++++++++++++++ .../datasource/elasticsearch/datasource.ts | 11 +- 3 files changed, 184 insertions(+), 5 deletions(-) diff --git a/pkg/tsdb/elasticsearch/elasticsearch.go b/pkg/tsdb/elasticsearch/elasticsearch.go index 0f714996d08..546345d070a 100644 --- a/pkg/tsdb/elasticsearch/elasticsearch.go +++ b/pkg/tsdb/elasticsearch/elasticsearch.go @@ -11,6 +11,7 @@ import ( "net/url" "path" "strconv" + "strings" "github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana-plugin-sdk-go/backend/datasource" @@ -182,7 +183,8 @@ func (s *Service) CallResource(ctx context.Context, req *backend.CallResourceReq logger := eslog.FromContext(ctx) // allowed paths for resource calls: // - empty string for fetching db version - if req.Path != "" { + // - /?/_mapping for fetching index mapping + if req.Path != "" && !strings.HasSuffix(req.Path, "/_mapping") { return fmt.Errorf("invalid resource URL: %s", req.Path) } diff --git a/public/app/plugins/datasource/elasticsearch/datasource.test.ts b/public/app/plugins/datasource/elasticsearch/datasource.test.ts index ab9c07af866..149103c6ff7 100644 --- a/public/app/plugins/datasource/elasticsearch/datasource.test.ts +++ b/public/app/plugins/datasource/elasticsearch/datasource.test.ts @@ -1326,4 +1326,178 @@ describe('ElasticDatasource using backend', () => { expect(version).toBe(null); }); }); + + describe('getFields', () => { + const getFieldsMockData = { + '[test-]YYYY.MM.DD': { + mappings: { + properties: { + '@timestamp_millis': { + type: 'date', + format: 'epoch_millis', + }, + classification_terms: { + type: 'keyword', + }, + ip_address: { + type: 'ip', + }, + justification_blob: { + properties: { + criterion: { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + shallow: { + properties: { + jsi: { + properties: { + sdb: { + properties: { + dsel2: { + properties: { + 'bootlegged-gille': { + properties: { + botness: { + type: 'float', + }, + general_algorithm_score: { + type: 'float', + }, + }, + }, + 'uncombed-boris': { + properties: { + botness: { + type: 'float', + }, + general_algorithm_score: { + type: 'float', + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + overall_vote_score: { + type: 'float', + }, + }, + }, + }, + }; + + it('should not retry when ES is down', async () => { + const twoDaysBefore = toUtc().subtract(2, 'day').format('YYYY.MM.DD'); + const { ds, timeSrv } = getTestContext({ + from: 'now-2w', + jsonData: { interval: 'Daily' }, + }); + + ds.getResource = jest.fn().mockImplementation((options) => { + if (options.url === `test-${twoDaysBefore}/_mapping`) { + return of({ + data: {}, + }); + } + return throwError({ status: 500 }); + }); + + const range = timeSrv.timeRange(); + await expect(ds.getFields(undefined, range)).toEmitValuesWith((received) => { + expect(received.length).toBe(1); + expect(received[0]).toStrictEqual({ status: 500 }); + expect(ds.getResource).toBeCalledTimes(1); + }); + }); + + it('should not retry more than 7 indices', async () => { + const { ds, timeSrv } = getTestContext({ + from: 'now-2w', + jsonData: { interval: 'Daily' }, + }); + const range = timeSrv.timeRange(); + + ds.getResource = jest.fn().mockImplementation(() => { + return throwError({ status: 404 }); + }); + + await expect(ds.getFields(undefined, range)).toEmitValuesWith((received) => { + expect(received.length).toBe(1); + expect(received[0]).toStrictEqual('Could not find an available index for this time range.'); + expect(ds.getResource).toBeCalledTimes(7); + }); + }); + + it('should return nested fields', async () => { + const { ds } = getTestContext({ + from: 'now-2w', + jsonData: { interval: 'Daily' }, + }); + + ds.getResource = jest.fn().mockResolvedValue(getFieldsMockData); + + await expect(ds.getFields()).toEmitValuesWith((received) => { + expect(received.length).toBe(1); + + const fieldObjects = received[0]; + const fields = map(fieldObjects, 'text'); + expect(fields).toEqual([ + '@timestamp_millis', + 'classification_terms', + 'ip_address', + 'justification_blob.criterion.keyword', + 'justification_blob.criterion', + 'justification_blob.shallow.jsi.sdb.dsel2.bootlegged-gille.botness', + 'justification_blob.shallow.jsi.sdb.dsel2.bootlegged-gille.general_algorithm_score', + 'justification_blob.shallow.jsi.sdb.dsel2.uncombed-boris.botness', + 'justification_blob.shallow.jsi.sdb.dsel2.uncombed-boris.general_algorithm_score', + 'overall_vote_score', + ]); + }); + }); + it('should return number fields', async () => { + const { ds } = getTestContext({}); + ds.getResource = jest.fn().mockResolvedValue(getFieldsMockData); + + await expect(ds.getFields(['number'])).toEmitValuesWith((received) => { + expect(received.length).toBe(1); + + const fieldObjects = received[0]; + const fields = map(fieldObjects, 'text'); + expect(fields).toEqual([ + 'justification_blob.shallow.jsi.sdb.dsel2.bootlegged-gille.botness', + 'justification_blob.shallow.jsi.sdb.dsel2.bootlegged-gille.general_algorithm_score', + 'justification_blob.shallow.jsi.sdb.dsel2.uncombed-boris.botness', + 'justification_blob.shallow.jsi.sdb.dsel2.uncombed-boris.general_algorithm_score', + 'overall_vote_score', + ]); + }); + }); + + it('should return date fields', async () => { + const { ds } = getTestContext({}); + ds.getResource = jest.fn().mockResolvedValue(getFieldsMockData); + + await expect(ds.getFields(['date'])).toEmitValuesWith((received) => { + expect(received.length).toBe(1); + + const fieldObjects = received[0]; + const fields = map(fieldObjects, 'text'); + expect(fields).toEqual(['@timestamp_millis']); + }); + }); + }); }); diff --git a/public/app/plugins/datasource/elasticsearch/datasource.ts b/public/app/plugins/datasource/elasticsearch/datasource.ts index 7f2f52da49d..6647112e8c9 100644 --- a/public/app/plugins/datasource/elasticsearch/datasource.ts +++ b/public/app/plugins/datasource/elasticsearch/datasource.ts @@ -179,9 +179,12 @@ export class ElasticDatasource }).pipe( mergeMap((index) => { // catch all errors and emit an object with an err property to simplify checks later in the pipeline - return this.legacyQueryRunner - .request('GET', indexUrlList[listLen - index - 1]) - .pipe(catchError((err) => of({ err }))); + const path = indexUrlList[listLen - index - 1]; + const requestObservable = config.featureToggles.enableElasticsearchBackendQuerying + ? from(this.getResource(path)) + : this.legacyQueryRunner.request('GET', path); + + return requestObservable.pipe(catchError((err) => of({ err }))); }), skipWhile((resp) => resp?.err?.status === 404), // skip all requests that fail because missing Elastic index throwIfEmpty(() => 'Could not find an available index for this time range.'), // when i === Math.min(listLen, maxTraversals) generate will complete but without emitting any values which means we didn't find a valid index @@ -462,7 +465,7 @@ export class ElasticDatasource return true; } - // equal query type filter, or via typemap translation + // equal query type filter, or via type map translation return type.includes(obj.type) || type.includes(typeMap[obj.type]); }; From 6b51f9e06dda2be9fc32d02ca8b42046113a3934 Mon Sep 17 00:00:00 2001 From: Armand Grillet <2117580+armandgrillet@users.noreply.github.com> Date: Fri, 5 May 2023 11:38:35 +0200 Subject: [PATCH 007/179] Update error message when Alertmanager configuration override (#67895) --- public/app/features/alerting/unified/state/actions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app/features/alerting/unified/state/actions.ts b/public/app/features/alerting/unified/state/actions.ts index e2835e7aff6..3e15c9ed9bc 100644 --- a/public/app/features/alerting/unified/state/actions.ts +++ b/public/app/features/alerting/unified/state/actions.ts @@ -580,7 +580,7 @@ export const updateAlertManagerConfigAction = createAsyncThunk Date: Fri, 5 May 2023 11:54:39 +0200 Subject: [PATCH 008/179] Docs: Add a note about disabling SAML UI (#67877) --- .../configure-authentication/saml-ui/index.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/sources/setup-grafana/configure-security/configure-authentication/saml-ui/index.md b/docs/sources/setup-grafana/configure-security/configure-authentication/saml-ui/index.md index 9b3cc4aaef3..885638a9bc3 100644 --- a/docs/sources/setup-grafana/configure-security/configure-authentication/saml-ui/index.md +++ b/docs/sources/setup-grafana/configure-security/configure-authentication/saml-ui/index.md @@ -20,6 +20,8 @@ The Grafana SAML UI provides the following advantages over configuring SAML in t > **Note:** Configuration in the UI takes precedence over the configuration in the Grafana configuration file. SAML settings from the UI will override any SAML configuration set in the Grafana configuration file. +> **Note:** Disabling the UI does not affect any configuration settings that were previously set up through the UI. Those settings will continue to function as intended even with the UI disabled. + ## Before you begin To follow this guide, you need: From 11ea63d9ddee0bc9348f1a9033b2105a8e54286d Mon Sep 17 00:00:00 2001 From: Armand Grillet <2117580+armandgrillet@users.noreply.github.com> Date: Fri, 5 May 2023 12:18:39 +0200 Subject: [PATCH 009/179] Optional team filter when sending issue notification on Slack (#67901) --- .github/teams.yml | 3 ++- .github/workflows/issue-labeled.yml | 24 ++++++++++++++++++++---- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/.github/teams.yml b/.github/teams.yml index 38b8e264661..366c2471cd8 100644 --- a/.github/teams.yml +++ b/.github/teams.yml @@ -7,4 +7,5 @@ test: # Alerting team area/alerting: - channel-label: C02B9MXQE0J + channel-label: C028MCV4R7C + exclude-github-team: alerting-squad diff --git a/.github/workflows/issue-labeled.yml b/.github/workflows/issue-labeled.yml index dada3acf164..c6046ce7447 100644 --- a/.github/workflows/issue-labeled.yml +++ b/.github/workflows/issue-labeled.yml @@ -1,4 +1,4 @@ -name: Notify Slack channel based on issue label +name: Notify Slack channel based on new issue label on: issues: @@ -15,6 +15,7 @@ jobs: run: | # Default to null values. CHANNEL="null" + TEAM="null" echo "${{ github.event.label.name }} label added" export CURRENT_LABEL="${{ github.event.label.name }}" # Enable the use of the label in yq evaluations @@ -25,10 +26,16 @@ jobs: CHANNEL=$(yq '.[env(CURRENT_LABEL)].channel-label' teams.yml) echo "Ready to send issue to channel ID ${CHANNEL}" fi + + if [[ $(yq '.[env(CURRENT_LABEL)] | has("exclude-github-team")' teams.yml ) == true ]]; then + TEAM=$(yq '.[env(CURRENT_LABEL)].exclude-github-team' teams.yml) + echo "Will not send issue to channel if issue author is part of the team ${TEAM}" + fi fi - # set environment for next step + # set environment for next steps echo "CHANNEL=${CHANNEL}" >> $GITHUB_ENV + echo "TEAM=${TEAM}" >> $GITHUB_ENV - name: "Prepare payload" uses: frabert/replace-string-action@v2.0 @@ -40,15 +47,24 @@ jobs: replace-with: "'" flags: 'g' + - name: "Check that issue author is not part of the team" + if: ${{ env.TEAM != 'null' }} + uses: tspascoal/get-user-teams-membership@v2 + id: checkUserMember + with: + username: ${{ github.event.issue.user.login }} + team: "${{ env.TEAM }}" + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: "Send Slack notification" - if: ${{ env.CHANNEL != 'null' }} + if: ${{ (env.CHANNEL != 'null') && ((steps.checkUserMember.outputs.isTeamMember == 'false') || (env.TEAM != 'null')) }} uses: slackapi/slack-github-action@v1.23.0 with: payload: > { "icon_emoji": ":grafana:", "username": "Grafana issue labeled", - "text": "Issue \"${{ steps.preparePayload.outputs.replaced }}\" labeled \"${{ github.event.label.name }}\": ${{ github.event.issue.html_url }}", + "text": "Issue \"${{ steps.preparePayload.outputs.replaced }}\" labeled \"${{ github.event.label.name }}\": ${{ github.event.issue.html_url }}, please triage.", "channel": "${{ env.CHANNEL }}" } env: From 5cb888650d740cfa19e715ea84d158349d814b76 Mon Sep 17 00:00:00 2001 From: Armand Grillet <2117580+armandgrillet@users.noreply.github.com> Date: Fri, 5 May 2023 12:43:56 +0200 Subject: [PATCH 010/179] Change GitHub token in issue-labeled.yml workflow (#67905) --- .github/workflows/issue-labeled.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/issue-labeled.yml b/.github/workflows/issue-labeled.yml index c6046ce7447..10e9ca5a114 100644 --- a/.github/workflows/issue-labeled.yml +++ b/.github/workflows/issue-labeled.yml @@ -54,7 +54,7 @@ jobs: with: username: ${{ github.event.issue.user.login }} team: "${{ env.TEAM }}" - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GH_BOT_ACCESS_TOKEN }} - name: "Send Slack notification" if: ${{ (env.CHANNEL != 'null') && ((steps.checkUserMember.outputs.isTeamMember == 'false') || (env.TEAM != 'null')) }} From eb2eb65de85704cff08d5bfba7cd552ca0ef323f Mon Sep 17 00:00:00 2001 From: Victor Marin <36818606+mdvictor@users.noreply.github.com> Date: Fri, 5 May 2023 13:57:57 +0300 Subject: [PATCH 011/179] TablePanel: Do not allow auto-reset (#67477) disallow react table to autoreset --- packages/grafana-ui/src/components/Table/Table.tsx | 1 + public/app/plugins/panel/table/TablePanel.tsx | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grafana-ui/src/components/Table/Table.tsx b/packages/grafana-ui/src/components/Table/Table.tsx index 11559e32c57..be2ed09f413 100644 --- a/packages/grafana-ui/src/components/Table/Table.tsx +++ b/packages/grafana-ui/src/components/Table/Table.tsx @@ -120,6 +120,7 @@ export const Table = memo((props: Props) => { data: memoizedData, disableResizing: !resizable, stateReducer: stateReducer, + autoResetPage: false, initialState: getInitialState(initialSortBy, memoizedColumns), autoResetFilters: false, sortTypes: { diff --git a/public/app/plugins/panel/table/TablePanel.tsx b/public/app/plugins/panel/table/TablePanel.tsx index a080c36b3cf..fcc50f96d31 100644 --- a/public/app/plugins/panel/table/TablePanel.tsx +++ b/public/app/plugins/panel/table/TablePanel.tsx @@ -41,7 +41,6 @@ export function TablePanel(props: Props) { const tableElement = (
Date: Fri, 5 May 2023 15:00:39 +0200 Subject: [PATCH 012/179] Elasticsearch: Improve query type selection (#63402) * elastic: display query types explicitly * fixed test * fixed test * updated test names * fix react-key-issue --- .../MetricEditor.test.tsx | 46 ++++++++++++- .../MetricAggregationsEditor/MetricEditor.tsx | 1 + .../MetricAggregationsEditor/index.tsx | 47 ++++++++++---- .../QueryEditorSpecialMetricRow.tsx | 36 +++++++++++ .../QueryEditor/QueryTypeSelector.tsx | 64 +++++++++++++++++++ .../components/QueryEditor/index.test.tsx | 15 +++-- .../components/QueryEditor/index.tsx | 42 +++++++----- 7 files changed, 214 insertions(+), 37 deletions(-) create mode 100644 public/app/plugins/datasource/elasticsearch/components/QueryEditor/QueryEditorSpecialMetricRow.tsx create mode 100644 public/app/plugins/datasource/elasticsearch/components/QueryEditor/QueryTypeSelector.tsx diff --git a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/MetricEditor.test.tsx b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/MetricEditor.test.tsx index 995ecf3c128..3b844138b92 100644 --- a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/MetricEditor.test.tsx +++ b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/MetricEditor.test.tsx @@ -1,4 +1,5 @@ import { act, fireEvent, render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import React, { PropsWithChildren } from 'react'; import { from } from 'rxjs'; @@ -9,7 +10,7 @@ import { defaultBucketAgg } from '../../../queryDef'; import { ElasticsearchQuery } from '../../../types'; import { ElasticsearchProvider } from '../ElasticsearchQueryContext'; -import { Average, UniqueCount } from './../../../types'; +import { Average, Count, UniqueCount } from './../../../types'; import { MetricEditor } from './MetricEditor'; describe('Metric Editor', () => { @@ -85,4 +86,47 @@ describe('Metric Editor', () => { expect(await screen.findByText('No options found')).toBeInTheDocument(); expect(screen.queryByText('None')).not.toBeInTheDocument(); }); + + it('Should not list special metrics', async () => { + const count: Count = { + id: '1', + type: 'count', + }; + + const query: ElasticsearchQuery = { + refId: 'A', + query: '', + metrics: [count], + bucketAggs: [], + }; + + const getDatabaseVersion: ElasticDatasource['getDatabaseVersion'] = jest.fn(() => Promise.resolve(null)); + + const wrapper = ({ children }: PropsWithChildren<{}>) => ( + {}} + onRunQuery={() => {}} + > + {children} + + ); + + render(, { wrapper }); + + act(() => { + userEvent.click(screen.getByText('Count')); + }); + + // we check if the list-of-options is visible by + // checking for an item to exist + expect(await screen.findByText('Extended Stats')).toBeInTheDocument(); + + // now we make sure the should-not-be-shown items are not shown + expect(screen.queryByText('Logs')).toBeNull(); + expect(screen.queryByText('Raw Data')).toBeNull(); + expect(screen.queryByText('Raw Document (deprecated)')).toBeNull(); + }); }); diff --git a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/MetricEditor.tsx b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/MetricEditor.tsx index 14aad375418..a476ff13069 100644 --- a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/MetricEditor.tsx +++ b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/MetricEditor.tsx @@ -48,6 +48,7 @@ const getTypeOptions = ( return ( Object.entries(metricAggregationConfig) + .filter(([_, config]) => !config.isSingleMetric) // Only showing metrics type supported by the version of ES. // if we cannot determine the version, we assume it is suitable. .filter(([_, { versionRange = '*' }]) => (esVersion != null ? satisfies(esVersion, versionRange) : true)) diff --git a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/index.tsx b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/index.tsx index c89cc86eef0..a916ef77ba9 100644 --- a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/index.tsx +++ b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/index.tsx @@ -4,6 +4,7 @@ import { useDispatch } from '../../../hooks/useStatelessReducer'; import { IconButton } from '../../IconButton'; import { useQuery } from '../ElasticsearchQueryContext'; import { QueryEditorRow } from '../QueryEditorRow'; +import { QueryEditorSpecialMetricRow } from '../QueryEditorSpecialMetricRow'; import { MetricAggregation } from './../../../types'; import { MetricEditor } from './MetricEditor'; @@ -21,21 +22,39 @@ export const MetricAggregationsEditor = ({ nextId }: Props) => { return ( <> - {metrics?.map((metric, index) => ( - + ); + } + })} ); }; diff --git a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/QueryEditorSpecialMetricRow.tsx b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/QueryEditorSpecialMetricRow.tsx new file mode 100644 index 00000000000..67f30d472e7 --- /dev/null +++ b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/QueryEditorSpecialMetricRow.tsx @@ -0,0 +1,36 @@ +import React from 'react'; + +import { InlineFieldRow, InlineLabel, InlineSegmentGroup } from '@grafana/ui'; + +import { MetricAggregation } from '../../types'; + +import { SettingsEditor } from './MetricAggregationsEditor/SettingsEditor'; + +type Props = { + name: string; + metric: MetricAggregation; + info?: string; +}; + +export const QueryEditorSpecialMetricRow = ({ name, metric, info }: Props) => { + // this widget is only used in scenarios when there is only a single + // metric, so the array of "previousMetrics" (meaning all the metrics + // before the current metric), is an ampty-array + const previousMetrics: MetricAggregation[] = []; + + return ( + + + + {name} + + + + {info != null && ( + + {info} + + )} + + ); +}; diff --git a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/QueryTypeSelector.tsx b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/QueryTypeSelector.tsx new file mode 100644 index 00000000000..9b8ade09bfa --- /dev/null +++ b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/QueryTypeSelector.tsx @@ -0,0 +1,64 @@ +import React from 'react'; + +import { SelectableValue } from '@grafana/data'; +import { RadioButtonGroup } from '@grafana/ui'; + +import { useDispatch } from '../../hooks/useStatelessReducer'; +import { MetricAggregation } from '../../types'; + +import { useQuery } from './ElasticsearchQueryContext'; +import { changeMetricType } from './MetricAggregationsEditor/state/actions'; + +type QueryType = 'metrics' | 'logs' | 'raw_data' | 'raw_document'; + +const OPTIONS: Array> = [ + { value: 'metrics', label: 'Metrics' }, + { value: 'logs', label: 'Logs' }, + { value: 'raw_data', label: 'Raw Data' }, + { value: 'raw_document', label: 'Raw Document' }, +]; + +function metricTypeToQueryType(type: MetricAggregation['type']): QueryType { + switch (type) { + case 'logs': + case 'raw_data': + case 'raw_document': + return type; + default: + return 'metrics'; + } +} + +function queryTypeToMetricType(type: QueryType): MetricAggregation['type'] { + switch (type) { + case 'logs': + case 'raw_data': + case 'raw_document': + return type; + case 'metrics': + return 'count'; + default: + // should never happen + throw new Error(`invalid query type: ${type}`); + } +} + +export const QueryTypeSelector = () => { + const query = useQuery(); + const dispatch = useDispatch(); + + const firstMetric = query.metrics?.[0]; + + if (firstMetric == null) { + // not sure if this can really happen, but we should handle it anyway + return null; + } + + const queryType = metricTypeToQueryType(firstMetric.type); + + const onChange = (newQueryType: QueryType) => { + dispatch(changeMetricType({ id: firstMetric.id, type: queryTypeToMetricType(newQueryType) })); + }; + + return fullWidth={false} options={OPTIONS} value={queryType} onChange={onChange} />; +}; diff --git a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/index.test.tsx b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/index.test.tsx index b92fc8cfc8d..67c19e51607 100644 --- a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/index.test.tsx +++ b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/index.test.tsx @@ -22,10 +22,15 @@ describe('QueryEditor', () => { metrics: [ { id: '1', - type: 'raw_data', + type: 'count', + }, + ], + bucketAggs: [ + { + type: 'date_histogram', + id: '2', }, ], - bucketAggs: [], }; const onChange = jest.fn(); @@ -51,7 +56,7 @@ describe('QueryEditor', () => { expect(onChange.mock.calls[0][0].alias).toBe(newAlias); }); - it('Should be disabled if last bucket aggregation is not Date Histogram', () => { + it('Should not be shown if last bucket aggregation is not Date Histogram', () => { const query: ElasticsearchQuery = { refId: 'A', query: '', @@ -66,10 +71,10 @@ describe('QueryEditor', () => { render(); - expect(screen.getByLabelText('Alias')).toBeDisabled(); + expect(screen.queryByLabelText('Alias')).toBeNull(); }); - it('Should be enabled if last bucket aggregation is Date Histogram', () => { + it('Should be shown if last bucket aggregation is Date Histogram', () => { const query: ElasticsearchQuery = { refId: 'A', query: '', diff --git a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/index.tsx b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/index.tsx index 69a4cb4be14..2e60a71865e 100644 --- a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/index.tsx +++ b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/index.tsx @@ -15,6 +15,7 @@ import { BucketAggregationsEditor } from './BucketAggregationsEditor'; import { ElasticsearchProvider } from './ElasticsearchQueryContext'; import { MetricAggregationsEditor } from './MetricAggregationsEditor'; import { metricAggregationConfig } from './MetricAggregationsEditor/utils'; +import { QueryTypeSelector } from './QueryTypeSelector'; import { changeAliasPattern, changeQuery } from './state'; export type ElasticQueryEditorProps = QueryEditorProps; @@ -66,7 +67,7 @@ const getStyles = (theme: GrafanaTheme2) => ({ root: css` display: flex; `, - queryFieldWrapper: css` + queryItem: css` flex-grow: 1; margin: 0 ${theme.spacing(0.5)} ${theme.spacing(0.5)} 0; `, @@ -80,14 +81,14 @@ export const ElasticSearchQueryField = ({ value, onChange }: { value?: string; o const styles = useStyles2(getStyles); return ( -
+
{}} onChange={onChange} - placeholder="Lucene Query" + placeholder="Enter a lucene query" portalOrigin="elasticsearch" />
@@ -109,22 +110,29 @@ const QueryEditorForm = ({ value }: Props) => { return ( <>
- Query + Query type +
+ +
+
+
+ Lucene Query dispatch(changeQuery(query))} value={value?.query} /> - - dispatch(changeAliasPattern(e.currentTarget.value))} - defaultValue={value.alias} - /> - + {isTimeSeriesQuery && ( + + dispatch(changeAliasPattern(e.currentTarget.value))} + defaultValue={value.alias} + /> + + )}
From a340d102a6bbd1467e994e0ee5727477e51104c6 Mon Sep 17 00:00:00 2001 From: Shirley <4163034+fridgepoet@users.noreply.github.com> Date: Fri, 5 May 2023 15:03:50 +0200 Subject: [PATCH 013/179] CloudWatch: Update docs for cloudWatchDynamicLabels feature toggle removal and alias deprecation (#67386) CloudWatch: Update docs for alias deprecation --- .../datasources/aws-cloudwatch/query-editor/index.md | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/docs/sources/datasources/aws-cloudwatch/query-editor/index.md b/docs/sources/datasources/aws-cloudwatch/query-editor/index.md index 2911fa63d72..ef7cd46de95 100644 --- a/docs/sources/datasources/aws-cloudwatch/query-editor/index.md +++ b/docs/sources/datasources/aws-cloudwatch/query-editor/index.md @@ -195,14 +195,10 @@ If the period field is left blank or set to `auto`, then it calculates automatic The label field allows you to override the default name of the metric legend using CloudWatch dynamic labels. If you're using a time-based dynamic label such as `${MIN_MAX_TIME_RANGE}`, then the legend value is derived from the current timezone specified in the time range picker. To see the full list of label patterns and the dynamic label limitations, refer to the [CloudWatch dynamic labels](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/graph-dynamic-labels.html) documentation. -> **Alias pattern deprecation:** Since Grafana v9.0, dynamic labels replaced alias patterns in the CloudWatch data source. +> **Alias pattern deprecation:** Since Grafana v10.0, the alias field has been deprecated and replaced by dynamic +> labels. > Any existing alias pattern is migrated upon upgrade to a corresponding dynamic label pattern. -> To use alias patterns instead of dynamic labels, set the feature toggle `cloudWatchDynamicLabels` to `false` in the [Grafana configuration file]({{< relref "../../../setup-grafana/configure-grafana/" >}}). -> This reverts to the alias pattern system and uses the previous alias formatting logic. - -The alias field will be deprecated and removed in a release. -During this interim period, we won't fix bugs related to the alias pattern system. -For details on why we're changing this feature, refer to [issue #48434](https://github.com/grafana/grafana/issues/48434). +> For details on this change, refer to [issue #48434](https://github.com/grafana/grafana/issues/48434). ## Query CloudWatch Logs From 4abc56c583d66007c94abecfbeac76fd364422bf Mon Sep 17 00:00:00 2001 From: Kyle Brandt Date: Fri, 5 May 2023 09:07:39 -0400 Subject: [PATCH 014/179] =?UTF-8?q?Prometheus:=20(Chore/Instrumentation)?= =?UTF-8?q?=20add=20trace=20spans=20for=20result=20proces=E2=80=A6=20(#678?= =?UTF-8?q?58)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Prometheus: (Chore/Instrumentation) add trace spans for result processing --- pkg/tsdb/prometheus/querydata/response.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/pkg/tsdb/prometheus/querydata/response.go b/pkg/tsdb/prometheus/querydata/response.go index 4c4b2ff026a..713a0c149fe 100644 --- a/pkg/tsdb/prometheus/querydata/response.go +++ b/pkg/tsdb/prometheus/querydata/response.go @@ -14,6 +14,7 @@ import ( "github.com/grafana/grafana/pkg/tsdb/prometheus/models" "github.com/grafana/grafana/pkg/tsdb/prometheus/querydata/exemplar" + "github.com/grafana/grafana/pkg/tsdb/prometheus/utils" "github.com/grafana/grafana/pkg/util/converter" ) @@ -24,6 +25,9 @@ func (s *QueryData) parseResponse(ctx context.Context, q *models.Query, res *htt } }() + ctx, endSpan := utils.StartTrace(ctx, s.tracer, "datasource.prometheus.parseResponse", []utils.Attribute{}) + defer endSpan() + iter := jsoniter.Parse(jsoniter.ConfigDefault, res.Body, 1024) r := converter.ReadPrometheusStyleResult(iter, converter.Options{ MatrixWideSeries: s.enableWideSeries, @@ -46,13 +50,15 @@ func (s *QueryData) parseResponse(ctx context.Context, q *models.Query, res *htt } if r.Error == nil { - r = s.processExemplars(q, r) + r = s.processExemplars(ctx, q, r) } return r } -func (s *QueryData) processExemplars(q *models.Query, dr backend.DataResponse) backend.DataResponse { +func (s *QueryData) processExemplars(ctx context.Context, q *models.Query, dr backend.DataResponse) backend.DataResponse { + _, endSpan := utils.StartTrace(ctx, s.tracer, "datasource.prometheus.processExemplars", []utils.Attribute{}) + defer endSpan() sampler := s.exemplarSampler() labelTracker := exemplar.NewLabelTracker() From 29e4df6a33e9f734572994dd26bcd819c40b2a3b Mon Sep 17 00:00:00 2001 From: Josh Hunt Date: Fri, 5 May 2023 13:46:53 +0000 Subject: [PATCH 015/179] Chore: Remove 'PerformanceBackend: flushing' log (#67915) Remove 'PerformanceBackend: flushing' log --- public/app/core/services/echo/backends/PerformanceBackend.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/public/app/core/services/echo/backends/PerformanceBackend.ts b/public/app/core/services/echo/backends/PerformanceBackend.ts index 5b3d9af2830..29ff37882bc 100644 --- a/public/app/core/services/echo/backends/PerformanceBackend.ts +++ b/public/app/core/services/echo/backends/PerformanceBackend.ts @@ -32,11 +32,6 @@ export class PerformanceBackend implements EchoBackend Date: Fri, 5 May 2023 15:50:40 +0200 Subject: [PATCH 016/179] PluginExtensions: Make context read only with a proxy instead of object freeze (#67781) * quick poc on how we could use proxies instead of object freeze for the extension context. * wip * wip * added tests for readOnlyProxy. * Changed so we use the read only proxy for the context instead of the frozen object. * updated names according to feedback. --- .../extensions/getPluginExtensions.test.ts | 15 ++-- .../plugins/extensions/getPluginExtensions.ts | 10 ++- .../plugins/extensions/utils.test.tsx | 90 ++++++++++++++++++- .../app/features/plugins/extensions/utils.tsx | 49 ++++++++++ 4 files changed, 154 insertions(+), 10 deletions(-) diff --git a/public/app/features/plugins/extensions/getPluginExtensions.test.ts b/public/app/features/plugins/extensions/getPluginExtensions.test.ts index 54d5b63b838..5002fa16c30 100644 --- a/public/app/features/plugins/extensions/getPluginExtensions.test.ts +++ b/public/app/features/plugins/extensions/getPluginExtensions.test.ts @@ -2,6 +2,7 @@ import { PluginExtensionLinkConfig, PluginExtensionTypes } from '@grafana/data'; import { createPluginExtensionRegistry } from './createPluginExtensionRegistry'; import { getPluginExtensions } from './getPluginExtensions'; +import { isReadOnlyProxy } from './utils'; import { assertPluginExtensionLink } from './validators'; describe('getPluginExtensions()', () => { @@ -96,19 +97,19 @@ describe('getPluginExtensions()', () => { expect(link2.configure).toHaveBeenCalledTimes(1); expect(extensions).toHaveLength(0); }); - test('should pass a frozen copy of the context to the configure() function', () => { + test('should pass a read only context to the configure() function', () => { const context = { title: 'New title from the context!' }; const registry = createPluginExtensionRegistry([{ pluginId, extensionConfigs: [link2] }]); const { extensions } = getPluginExtensions({ registry, context, extensionPointId: extensionPoint2 }); const [extension] = extensions; - const frozenContext = (link2.configure as jest.Mock).mock.calls[0][0]; + const readOnlyContext = (link2.configure as jest.Mock).mock.calls[0][0]; assertPluginExtensionLink(extension); expect(link2.configure).toHaveBeenCalledTimes(1); - expect(Object.isFrozen(frozenContext)).toBe(true); + expect(isReadOnlyProxy(readOnlyContext)).toBe(true); expect(() => { - frozenContext.title = 'New title'; + readOnlyContext.title = 'New title'; }).toThrow(); expect(context.title).toBe('New title from the context!'); }); @@ -247,7 +248,7 @@ describe('getPluginExtensions()', () => { expect(global.console.warn).toHaveBeenCalledWith('[Plugin Extensions] Something went wrong!'); }); - test('should pass a frozen copy of the context to the onClick() function', () => { + test('should pass a read only context to the onClick() function', () => { const context = { title: 'New title from the context!' }; link2.path = undefined; @@ -263,13 +264,13 @@ describe('getPluginExtensions()', () => { const helpers = (link2.onClick as jest.Mock).mock.calls[0][1]; expect(link2.configure).toHaveBeenCalledTimes(1); - expect(Object.isFrozen(helpers.context)).toBe(true); + expect(isReadOnlyProxy(helpers.context)).toBe(true); expect(() => { helpers.context.title = 'New title'; }).toThrow(); }); - test('should should not freeze the original context', () => { + test('should should not make original context read only', () => { const context = { title: 'New title from the context!', nested: { title: 'title' }, diff --git a/public/app/features/plugins/extensions/getPluginExtensions.ts b/public/app/features/plugins/extensions/getPluginExtensions.ts index 7440528c4bc..071a67028b4 100644 --- a/public/app/features/plugins/extensions/getPluginExtensions.ts +++ b/public/app/features/plugins/extensions/getPluginExtensions.ts @@ -6,7 +6,13 @@ import { } from '@grafana/data'; import type { PluginExtensionRegistry } from './types'; -import { isPluginExtensionLinkConfig, deepFreeze, logWarning, generateExtensionId, getEventHelpers } from './utils'; +import { + isPluginExtensionLinkConfig, + getReadOnlyProxy, + logWarning, + generateExtensionId, + getEventHelpers, +} from './utils'; import { assertIsNotPromise, assertLinkPathIsValid, assertStringProps, isPromise } from './validators'; type GetExtensions = ({ @@ -21,7 +27,7 @@ type GetExtensions = ({ // Returns with a list of plugin extensions for the given extension point export const getPluginExtensions: GetExtensions = ({ context, extensionPointId, registry }) => { - const frozenContext = context ? deepFreeze(context) : {}; + const frozenContext = context ? getReadOnlyProxy(context) : {}; const registryItems = registry[extensionPointId] ?? []; // We don't return the extensions separated by type, because in that case it would be much harder to define a sort-order for them. const extensions: PluginExtension[] = []; diff --git a/public/app/features/plugins/extensions/utils.test.tsx b/public/app/features/plugins/extensions/utils.test.tsx index 7c20bf2f2df..f814bd1df3b 100644 --- a/public/app/features/plugins/extensions/utils.test.tsx +++ b/public/app/features/plugins/extensions/utils.test.tsx @@ -1,6 +1,6 @@ import { PluginExtensionLinkConfig, PluginExtensionTypes } from '@grafana/data'; -import { deepFreeze, isPluginExtensionLinkConfig, handleErrorsInFn } from './utils'; +import { deepFreeze, isPluginExtensionLinkConfig, handleErrorsInFn, getReadOnlyProxy } from './utils'; describe('Plugin Extensions / Utils', () => { describe('deepFreeze()', () => { @@ -219,4 +219,92 @@ describe('Plugin Extensions / Utils', () => { }).not.toThrow(); }); }); + + describe('getReadOnlyProxy()', () => { + it('should not be possible to modify values in proxied object', () => { + const proxy = getReadOnlyProxy({ a: 'a' }); + + expect(() => { + proxy.a = 'b'; + }).toThrowError(TypeError); + }); + + it('should not be possible to modify values in proxied array', () => { + const proxy = getReadOnlyProxy([1, 2, 3]); + + expect(() => { + proxy[0] = 2; + }).toThrowError(TypeError); + }); + + it('should not be possible to modify nested objects in proxied object', () => { + const proxy = getReadOnlyProxy({ + a: { + c: 'c', + }, + b: 'b', + }); + + expect(() => { + proxy.a.c = 'testing'; + }).toThrowError(TypeError); + }); + + it('should not be possible to modify nested arrays in proxied object', () => { + const proxy = getReadOnlyProxy({ + a: { + c: ['c', 'd'], + }, + b: 'b', + }); + + expect(() => { + proxy.a.c[0] = 'testing'; + }).toThrowError(TypeError); + }); + + it('should be possible to modify source object', () => { + const source = { a: 'b' }; + + getReadOnlyProxy(source); + source.a = 'c'; + + expect(source.a).toBe('c'); + }); + + it('should be possible to modify source array', () => { + const source = ['a', 'b']; + + getReadOnlyProxy(source); + source[0] = 'c'; + + expect(source[0]).toBe('c'); + }); + + it('should be possible to modify nedsted objects in source object', () => { + const source = { a: { b: 'c' } }; + + getReadOnlyProxy(source); + source.a.b = 'd'; + + expect(source.a.b).toBe('d'); + }); + + it('should be possible to modify nedsted arrays in source object', () => { + const source = { a: { b: ['c', 'd'] } }; + + getReadOnlyProxy(source); + source.a.b[0] = 'd'; + + expect(source.a.b[0]).toBe('d'); + }); + + it('should be possible to call functions in proxied object', () => { + const proxy = getReadOnlyProxy({ + a: () => 'testing', + }); + + expect(proxy.a()).toBe('testing'); + }); + }); }); diff --git a/public/app/features/plugins/extensions/utils.tsx b/public/app/features/plugins/extensions/utils.tsx index dd7de44c70a..8e02ff40c84 100644 --- a/public/app/features/plugins/extensions/utils.tsx +++ b/public/app/features/plugins/extensions/utils.tsx @@ -1,3 +1,4 @@ +import { isArray, isObject } from 'lodash'; import React from 'react'; import { @@ -110,3 +111,51 @@ export function generateExtensionId(pluginId: string, extensionConfig: PluginExt .reduce((s, c) => (Math.imul(31, s) + c.charCodeAt(0)) | 0, 0) .toString(); } + +const _isProxy = Symbol('isReadOnlyProxy'); + +/** + * Returns a proxy that wraps the given object in a way that makes it read only. + * If you try to modify the object a TypeError exception will be thrown. + * + * @param obj The object to make read only + * @returns A new read only object, does not modify the original object + */ +export function getReadOnlyProxy(obj: T): T { + if (!obj || typeof obj !== 'object' || isReadOnlyProxy(obj)) { + return obj; + } + + const cache = new WeakMap(); + + return new Proxy(obj, { + defineProperty: () => false, + deleteProperty: () => false, + isExtensible: () => false, + set: () => false, + get(target, prop, receiver) { + if (prop === _isProxy) { + return true; + } + + const value = Reflect.get(target, prop, receiver); + + if (isObject(value) || isArray(value)) { + if (!cache.has(value)) { + cache.set(value, getReadOnlyProxy(value)); + } + return cache.get(value); + } + + return value; + }, + }); +} + +function isRecord(value: unknown): value is Record { + return typeof value === 'object' && value !== null; +} + +export function isReadOnlyProxy(value: unknown): boolean { + return isRecord(value) && value[_isProxy] === true; +} From c0bb4049811d0a3f4e3b8842338c5f9d2fa37743 Mon Sep 17 00:00:00 2001 From: Isabella Siu Date: Fri, 5 May 2023 10:41:40 -0400 Subject: [PATCH 017/179] CloudWatch: Use grafana-aws-sdk v0.15.0 (#67876) Cloudwatch: Update grafana-aws-sdk --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index fb604764e12..967b0c8b1a7 100644 --- a/go.mod +++ b/go.mod @@ -57,7 +57,7 @@ require ( github.com/google/wire v0.5.0 github.com/gorilla/websocket v1.5.0 github.com/grafana/alerting v0.0.0-20230428095912-33c5aa68a5ba - github.com/grafana/grafana-aws-sdk v0.12.0 + github.com/grafana/grafana-aws-sdk v0.15.0 github.com/grafana/grafana-azure-sdk-go v1.6.0 github.com/grafana/grafana-plugin-sdk-go v0.160.0 github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 diff --git a/go.sum b/go.sum index 8b749978c97..1d845a72423 100644 --- a/go.sum +++ b/go.sum @@ -1290,6 +1290,8 @@ github.com/grafana/go-mssqldb v0.9.2 h1:FkyRJR4ywsT07iMtpFMHStrl8uuNkGIwp253Fee0 github.com/grafana/go-mssqldb v0.9.2/go.mod h1:HTCsUqZdb7oIO7jc37YauiSB5C3P/13AnpctVWBhlus= github.com/grafana/grafana-aws-sdk v0.12.0 h1:eUjFdFZeZE+nyu/RMRz+qFxTBew69ToLBrbRhTbjkfM= github.com/grafana/grafana-aws-sdk v0.12.0/go.mod h1:rCXLYoMpPqF90U7XqgVJ1HIAopFVF0bB3SXBVEJIm3I= +github.com/grafana/grafana-aws-sdk v0.15.0 h1:ZOPHQcC5NUFi1bLTwnju91G0KmGh1z+qXOKj9nDfxNs= +github.com/grafana/grafana-aws-sdk v0.15.0/go.mod h1:rCXLYoMpPqF90U7XqgVJ1HIAopFVF0bB3SXBVEJIm3I= github.com/grafana/grafana-azure-sdk-go v1.6.0 h1:lxvH/mVY7gKBtJKhZ4B/6tIZFY7Jth97HxBA38olaxs= github.com/grafana/grafana-azure-sdk-go v1.6.0/go.mod h1:X4PdEQIYgHfn0KTa2ZTKvufhNz6jbCEKUQPZIlcyOGw= github.com/grafana/grafana-google-sdk-go v0.1.0 h1:LKGY8z2DSxKjYfr2flZsWgTRTZ6HGQbTqewE3JvRaNA= From 848f1f0fb2d0b13112842301d1cdad0db9e4d27d Mon Sep 17 00:00:00 2001 From: Ieva Date: Fri, 5 May 2023 15:45:48 +0100 Subject: [PATCH 018/179] Chore: change default log level (#64304) raise log level Co-authored-by: Emil Tullstedt --- pkg/util/errutil/status.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/pkg/util/errutil/status.go b/pkg/util/errutil/status.go index 722d455cb6d..338cc0349a2 100644 --- a/pkg/util/errutil/status.go +++ b/pkg/util/errutil/status.go @@ -88,19 +88,19 @@ func (s CoreStatus) HTTPStatus() int { func (s CoreStatus) LogLevel() LogLevel { switch s { case StatusUnauthorized: - return LevelDebug + return LevelInfo case StatusForbidden: - return LevelDebug + return LevelInfo case StatusNotFound: - return LevelDebug + return LevelInfo case StatusTimeout: - return LevelDebug + return LevelInfo case StatusTooManyRequests: - return LevelDebug + return LevelInfo case StatusBadRequest: - return LevelDebug + return LevelInfo case StatusValidationFailed: - return LevelDebug + return LevelInfo case StatusNotImplemented: return LevelDebug case StatusUnknown, StatusInternal: From 26fc359933554be9b21f20c0e2fafe07c54adf91 Mon Sep 17 00:00:00 2001 From: Andre Pereira Date: Fri, 5 May 2023 15:51:13 +0100 Subject: [PATCH 019/179] Trace View: Rename span detail attribute sections (#67849) * Rename span detail attribute sections * Fix tests * Fix more tests --- public/app/features/explore/TraceView/TraceView.test.tsx | 8 ++++---- .../TraceTimelineViewer/SpanDetail/index.test.tsx | 4 ++-- .../components/TraceTimelineViewer/SpanDetail/index.tsx | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/public/app/features/explore/TraceView/TraceView.test.tsx b/public/app/features/explore/TraceView/TraceView.test.tsx index ddb7338155c..6287620f49e 100644 --- a/public/app/features/explore/TraceView/TraceView.test.tsx +++ b/public/app/features/explore/TraceView/TraceView.test.tsx @@ -91,14 +91,14 @@ describe('TraceView', () => { it('toggles detailState', async () => { renderTraceViewNew(); - expect(screen.queryByText(/Attributes/)).toBeFalsy(); + expect(screen.queryByText(/Span Attributes/)).toBeFalsy(); const spanView = screen.getAllByText('', { selector: 'div[data-testid="span-view"]' })[0]; await userEvent.click(spanView); - expect(screen.queryByText(/Attributes/)).toBeTruthy(); + expect(screen.queryByText(/Span Attributes/)).toBeTruthy(); await userEvent.click(spanView); - screen.debug(screen.queryAllByText(/Attributes/)); - expect(screen.queryByText(/Attributes/)).toBeFalsy(); + screen.debug(screen.queryAllByText(/Span Attributes/)); + expect(screen.queryByText(/Span Attributes/)).toBeFalsy(); }); it('shows timeline ticks', () => { diff --git a/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/index.test.tsx b/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/index.test.tsx index 260ec358c33..ece47433797 100644 --- a/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/index.test.tsx +++ b/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/index.test.tsx @@ -144,13 +144,13 @@ describe('', () => { it('renders the span tags', async () => { render(); - await userEvent.click(screen.getByRole('switch', { name: /Attributes/ })); + await userEvent.click(screen.getByRole('switch', { name: /Span Attributes/ })); expect(props.tagsToggle).toHaveBeenLastCalledWith(span.spanID); }); it('renders the process tags', async () => { render(); - await userEvent.click(screen.getByRole('switch', { name: /Resource/ })); + await userEvent.click(screen.getByRole('switch', { name: /Resource Attributes/ })); expect(props.processToggle).toHaveBeenLastCalledWith(span.spanID); }); diff --git a/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/index.tsx b/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/index.tsx index f11a77dc230..cac62dc44c0 100644 --- a/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/index.tsx +++ b/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/index.tsx @@ -250,7 +250,7 @@ export default function SpanDetail(props: SpanDetailProps) {
tagsToggle(spanID)} @@ -259,7 +259,7 @@ export default function SpanDetail(props: SpanDetailProps) { processToggle(spanID)} From ae1a85b5ad4bbe85c0cb01c6a33f27a3d1f8850c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 5 May 2023 17:00:33 +0200 Subject: [PATCH 020/179] Drawer: Position under nav & minor redesign (#67824) * Drawer: Redesign WIP * Fix double margin * Teak pos * align paddings * Hide toolbar actions when drawer is open * deprecate expandable * Updated test * remove expandable var * Added clickaway --- .betterer.results | 4 +- e2e/various-suite/inspect-drawer.spec.ts | 26 ---- .../src/components/Drawer/Drawer.tsx | 120 ++++++++++-------- .../core/components/AppChrome/AppChrome.tsx | 3 +- .../AppChrome/NavToolbar/NavToolbar.tsx | 6 +- .../components/HelpWizard/HelpWizard.tsx | 1 - .../components/Inspector/InspectContent.tsx | 1 - .../SaveDashboard/SaveDashboardDrawer.tsx | 1 - 8 files changed, 75 insertions(+), 87 deletions(-) diff --git a/.betterer.results b/.betterer.results index 22be1f2f322..8a02d1a063f 100644 --- a/.betterer.results +++ b/.betterer.results @@ -942,9 +942,7 @@ exports[`better eslint`] = { [0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "2"] ], "packages/grafana-ui/src/components/Drawer/Drawer.tsx:5381": [ - [0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "0"], - [0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "1"], - [0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "2"] + [0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "0"] ], "packages/grafana-ui/src/components/Dropdown/ButtonSelect.tsx:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] diff --git a/e2e/various-suite/inspect-drawer.spec.ts b/e2e/various-suite/inspect-drawer.spec.ts index 155f1c7cd12..7d19f3686b4 100644 --- a/e2e/various-suite/inspect-drawer.spec.ts +++ b/e2e/various-suite/inspect-drawer.spec.ts @@ -41,8 +41,6 @@ e2e.scenario({ expectDrawerTabsAndContent(); - expectDrawerExpandAndContract(viewPortWidth); - expectDrawerClose(); expectSubMenuScenario('Data'); @@ -107,30 +105,6 @@ const expectDrawerClose = () => { e2e.components.Drawer.General.title(`Inspect: ${PANEL_UNDER_TEST}`).should('not.exist'); }; -const expectDrawerExpandAndContract = (viewPortWidth: number) => { - // try expand button - // drawer should take up half the screen - e2e.components.Drawer.General.rcContentWrapper() - .should('be.visible') - .should('have.css', 'width', `${viewPortWidth / 2}px`); - - e2e.components.Drawer.General.expand().click(); - e2e.components.Drawer.General.contract().should('be.visible'); - - // drawer should take up the whole screen - e2e.components.Drawer.General.rcContentWrapper() - .should('be.visible') - .should('have.css', 'width', `${viewPortWidth}px`); - - // try contract button - e2e.components.Drawer.General.contract().click(); - e2e.components.Drawer.General.expand().should('be.visible'); - - e2e.components.Drawer.General.rcContentWrapper() - .should('be.visible') - .should('have.css', 'width', `${viewPortWidth / 2}px`); -}; - const expectSubMenuScenario = (subMenu: string, tabTitle?: string) => { tabTitle = tabTitle ?? subMenu; // testing opening inspect drawer from sub menus under Inspect in header menu diff --git a/packages/grafana-ui/src/components/Drawer/Drawer.tsx b/packages/grafana-ui/src/components/Drawer/Drawer.tsx index 1aad5038d33..80fea394303 100644 --- a/packages/grafana-ui/src/components/Drawer/Drawer.tsx +++ b/packages/grafana-ui/src/components/Drawer/Drawer.tsx @@ -3,14 +3,17 @@ import { useDialog } from '@react-aria/dialog'; import { FocusScope } from '@react-aria/focus'; import { useOverlay } from '@react-aria/overlays'; import RcDrawer from 'rc-drawer'; -import React, { ReactNode, useState, useEffect } from 'react'; +import React, { ReactNode, useEffect } from 'react'; +import { useClickAway } from 'react-use'; import { GrafanaTheme2 } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; import { useStyles2 } from '../../themes'; +import { Button } from '../Button'; import { CustomScrollbar } from '../CustomScrollbar/CustomScrollbar'; -import { IconButton } from '../IconButton/IconButton'; +//import { IconButton } from '../IconButton/IconButton'; +import { Text } from '../Text/Text'; export interface Props { children: ReactNode; @@ -26,7 +29,9 @@ export interface Props { * @deprecated use the size property instead **/ width?: number | string; - /** Should the Drawer be expandable to full width */ + /** + * @deprecated use a large size instead if high width is needed + **/ expandable?: boolean; /** * Specifies the width and min-width. @@ -52,39 +57,35 @@ export function Drawer({ subtitle, width, size = 'md', - expandable = false, tabs, }: Props) { const styles = useStyles2(getStyles); - const [isExpanded, setIsExpanded] = useState(false); - const [isOpen, setIsOpen] = useState(false); const overlayRef = React.useRef(null); const { dialogProps, titleProps } = useDialog({}, overlayRef); const { overlayProps } = useOverlay( { isDismissable: false, - isOpen, + isOpen: true, onClose, }, overlayRef ); - // RcDrawer v4.x needs to be mounted in advance for animations to play. - useEffect(() => { - setIsOpen(true); - }, []); + // Adds body class while open so the toolbar nav can hide some actions while drawer is open + useBodyClassWhileOpen(); + useClickAway(overlayRef, onClose); - // deprecated width prop now defaults to empty string which make the size prop take over - const fixedWidth = isExpanded ? '100%' : width ?? ''; - const rootClass = cx(styles.drawer, !fixedWidth && styles.sizes[size]); + // Apply size styles (unless deprecated width prop is used) + const rootClass = cx(styles.drawer, !width && styles.sizes[size]); const content =
{children}
; return ( {typeof title === 'string' && ( -
+
- {expandable && !isExpanded && ( - setIsExpanded(true)} - aria-label={selectors.components.Drawer.General.expand} - /> - )} - {expandable && isExpanded && ( - setIsExpanded(false)} - aria-label={selectors.components.Drawer.General.contract} - /> - )} -
-

{title}

- {typeof subtitle === 'string' &&
{subtitle}
} - {typeof subtitle !== 'string' && subtitle} + + {title} + + {subtitle &&
{subtitle}
} {tabs &&
{tabs}
}
@@ -155,6 +142,20 @@ export function Drawer({ ); } +function useBodyClassWhileOpen() { + useEffect(() => { + if (!document.body) { + return; + } + + document.body.classList.add('body-drawer-open'); + + return () => { + document.body.classList.remove('body-drawer-open'); + }; + }, []); +} + const getStyles = (theme: GrafanaTheme2) => { return { container: css` @@ -164,6 +165,14 @@ const getStyles = (theme: GrafanaTheme2) => { flex: 1 1 0; `, drawer: css` + .main-view & { + top: 81px; + } + + .main-view--search-bar-hidden & { + top: 41px; + } + .rc-drawer-content-wrapper { box-shadow: ${theme.shadows.z3}; @@ -185,7 +194,7 @@ const getStyles = (theme: GrafanaTheme2) => { '.rc-drawer-content-wrapper': { label: 'drawer-md', width: '50vw', - minWidth: theme.spacing(66), + minWidth: theme.spacing(60), }, }), lg: css({ @@ -233,21 +242,26 @@ const getStyles = (theme: GrafanaTheme2) => { } } `, - header: css` - background-color: ${theme.colors.background.canvas}; - flex-grow: 0; - padding-top: ${theme.spacing(0.5)}; - `, - actions: css` - display: flex; - align-items: baseline; - justify-content: flex-end; - `, + header: css({ + flexGrow: 0, + padding: theme.spacing(3, 2), + borderBottom: `1px solid ${theme.colors.border.weak}`, + }), + headerWithTabs: css({ + borderBottom: 'none', + }), + actions: css({ + position: 'absolute', + right: theme.spacing(1), + top: theme.spacing(2), + }), titleWrapper: css` - margin-bottom: ${theme.spacing(3)}; - padding: ${theme.spacing(0, 1, 0, 3)}; overflow-wrap: break-word; `, + subtitle: css({ + color: theme.colors.text.secondary, + paddingTop: theme.spacing(1), + }), content: css({ padding: theme.spacing(2), height: '100%', @@ -259,7 +273,7 @@ const getStyles = (theme: GrafanaTheme2) => { }), tabsWrapper: css({ paddingLeft: theme.spacing(2), - margin: theme.spacing(3, -1, -3, -3), + margin: theme.spacing(2, -1, -3, -3), }), }; }; diff --git a/public/app/core/components/AppChrome/AppChrome.tsx b/public/app/core/components/AppChrome/AppChrome.tsx index f140eaa06a9..72489c28fa2 100644 --- a/public/app/core/components/AppChrome/AppChrome.tsx +++ b/public/app/core/components/AppChrome/AppChrome.tsx @@ -1,4 +1,5 @@ import { css, cx } from '@emotion/css'; +import classNames from 'classnames'; import React, { PropsWithChildren } from 'react'; import { GrafanaTheme2, PageLayoutType } from '@grafana/data'; @@ -33,7 +34,7 @@ export function AppChrome({ children }: Props) { // doesn't get re-mounted when chromeless goes from true to false. return ( -
+
{!state.chromeless && (
{!searchBarHidden && } diff --git a/public/app/core/components/AppChrome/NavToolbar/NavToolbar.tsx b/public/app/core/components/AppChrome/NavToolbar/NavToolbar.tsx index fc7f789ca75..2dbb4f82c90 100644 --- a/public/app/core/components/AppChrome/NavToolbar/NavToolbar.tsx +++ b/public/app/core/components/AppChrome/NavToolbar/NavToolbar.tsx @@ -82,7 +82,6 @@ const getStyles = (theme: GrafanaTheme2) => { display: 'flex', padding: theme.spacing(0, 1, 0, 2), alignItems: 'center', - justifyContent: 'space-between', }), menuButton: css({ display: 'flex', @@ -90,6 +89,7 @@ const getStyles = (theme: GrafanaTheme2) => { marginRight: theme.spacing(1), }), actions: css({ + label: 'NavToolbar-actions', display: 'flex', alignItems: 'center', flexWrap: 'nowrap', @@ -98,6 +98,10 @@ const getStyles = (theme: GrafanaTheme2) => { flexGrow: 1, gap: theme.spacing(0.5), minWidth: 0, + + '.body-drawer-open &': { + display: 'none', + }, }), }; }; diff --git a/public/app/features/dashboard/components/HelpWizard/HelpWizard.tsx b/public/app/features/dashboard/components/HelpWizard/HelpWizard.tsx index 541dc7bba11..4f9fa35d360 100644 --- a/public/app/features/dashboard/components/HelpWizard/HelpWizard.tsx +++ b/public/app/features/dashboard/components/HelpWizard/HelpWizard.tsx @@ -74,7 +74,6 @@ export function HelpWizard({ panel, plugin, onClose }: Props) { title={`Get help with this panel`} size="lg" onClose={onClose} - expandable scrollableContent subtitle={ diff --git a/public/app/features/dashboard/components/Inspector/InspectContent.tsx b/public/app/features/dashboard/components/Inspector/InspectContent.tsx index af5446806b9..5b2bbf9956d 100644 --- a/public/app/features/dashboard/components/Inspector/InspectContent.tsx +++ b/public/app/features/dashboard/components/Inspector/InspectContent.tsx @@ -69,7 +69,6 @@ export const InspectContent = ({ title={title} subtitle={data && formatStats(data)} onClose={onClose} - expandable scrollableContent tabs={ diff --git a/public/app/features/dashboard/components/SaveDashboard/SaveDashboardDrawer.tsx b/public/app/features/dashboard/components/SaveDashboard/SaveDashboardDrawer.tsx index 5b02cbd731e..18a2c6157fb 100644 --- a/public/app/features/dashboard/components/SaveDashboard/SaveDashboardDrawer.tsx +++ b/public/app/features/dashboard/components/SaveDashboard/SaveDashboardDrawer.tsx @@ -140,7 +140,6 @@ export const SaveDashboardDrawer = ({ dashboard, onDismiss, onSaveSuccess, isCop )} } - expandable scrollableContent > {renderSaveBody()} From 15e34505e2213b47a47d922a0cd306e071565d12 Mon Sep 17 00:00:00 2001 From: linoman <2051016+linoman@users.noreply.github.com> Date: Fri, 5 May 2023 17:17:18 +0200 Subject: [PATCH 021/179] Render analytics identifiers (#67860) * Append analytics identifier upon authenticate session * Add id and module upon syncing user to identity * Add authModule & id to `IdentityFromSignedInUser` * Allow req calls in test to use basic auth * Add `intercom_secret` to grafana config in tests * Add test for analytics render in html view --- pkg/services/authn/authn.go | 2 + .../authn/authnimpl/sync/user_sync.go | 2 + pkg/services/contexthandler/contexthandler.go | 24 ++++++++- pkg/tests/testinfra/testinfra.go | 5 ++ pkg/tests/web/index_view_test.go | 51 +++++++++++++++++-- 5 files changed, 78 insertions(+), 6 deletions(-) diff --git a/pkg/services/authn/authn.go b/pkg/services/authn/authn.go index 02eef4fe10b..500507620ee 100644 --- a/pkg/services/authn/authn.go +++ b/pkg/services/authn/authn.go @@ -326,6 +326,8 @@ func IdentityFromSignedInUser(id string, usr *user.SignedInUser, params ClientPa Teams: usr.Teams, ClientParams: params, Permissions: usr.Permissions, + AuthModule: usr.ExternalAuthModule, + AuthID: usr.ExternalAuthID, } } diff --git a/pkg/services/authn/authnimpl/sync/user_sync.go b/pkg/services/authn/authnimpl/sync/user_sync.go index b4b38e2dbcc..20c120f262f 100644 --- a/pkg/services/authn/authnimpl/sync/user_sync.go +++ b/pkg/services/authn/authnimpl/sync/user_sync.go @@ -392,6 +392,8 @@ func syncSignedInUserToIdentity(usr *user.SignedInUser, identity *authn.Identity identity.LastSeenAt = usr.LastSeenAt identity.IsDisabled = usr.IsDisabled identity.IsGrafanaAdmin = &usr.IsGrafanaAdmin + identity.AuthID = usr.ExternalAuthID + identity.AuthModule = usr.ExternalAuthModule } func shouldUpdateLastSeen(t time.Time) bool { diff --git a/pkg/services/contexthandler/contexthandler.go b/pkg/services/contexthandler/contexthandler.go index b0b96273094..5c3760af292 100644 --- a/pkg/services/contexthandler/contexthandler.go +++ b/pkg/services/contexthandler/contexthandler.go @@ -3,6 +3,9 @@ package contexthandler import ( "context" + "crypto/hmac" + "crypto/sha256" + "encoding/hex" "errors" "fmt" "net/http" @@ -110,6 +113,25 @@ func FromContext(c context.Context) *contextmodel.ReqContext { return nil } +func hashUserIdentifier(identifier string, secret string) string { + key := []byte(secret) + h := hmac.New(sha256.New, key) + h.Write([]byte(identifier)) + return hex.EncodeToString(h.Sum(nil)) +} + +func setSignedInUser(reqContext *contextmodel.ReqContext, identity *authn.Identity, intercomSecret string) { + reqContext.SignedInUser = identity.SignedInUser() + if identity.AuthID != "" { + reqContext.SignedInUser.Analytics.Identifier = identity.AuthID + } else { + reqContext.SignedInUser.Analytics.Identifier = identity.Email + "@" + setting.AppUrl + } + if intercomSecret != "" { + reqContext.SignedInUser.Analytics.IntercomIdentifier = hashUserIdentifier(identity.AuthID, intercomSecret) + } +} + // Middleware provides a middleware to initialize the request context. func (h *ContextHandler) Middleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -151,7 +173,7 @@ func (h *ContextHandler) Middleware(next http.Handler) http.Handler { reqContext.LookupTokenErr = err } else { reqContext.UserToken = identity.SessionToken - reqContext.SignedInUser = identity.SignedInUser() + setSignedInUser(reqContext, identity, h.Cfg.IntercomSecret) reqContext.IsSignedIn = !identity.IsAnonymous reqContext.AllowAnonymous = identity.IsAnonymous reqContext.IsRenderCall = identity.AuthModule == login.RenderModule diff --git a/pkg/tests/testinfra/testinfra.go b/pkg/tests/testinfra/testinfra.go index b4302fc482a..535cd1ac89e 100644 --- a/pkg/tests/testinfra/testinfra.go +++ b/pkg/tests/testinfra/testinfra.go @@ -216,6 +216,11 @@ func CreateGrafDir(t *testing.T, opts ...GrafanaOpts) (string, string) { _, err = rbacSect.NewKey("permission_cache", "false") require.NoError(t, err) + analyticsSect, err := cfg.NewSection("analytics") + require.NoError(t, err) + _, err = analyticsSect.NewKey("intercom_secret", "intercom_secret_at_config") + require.NoError(t, err) + getOrCreateSection := func(name string) (*ini.Section, error) { section, err := cfg.GetSection(name) if err != nil { diff --git a/pkg/tests/web/index_view_test.go b/pkg/tests/web/index_view_test.go index 8286abde819..7a17a816dff 100644 --- a/pkg/tests/web/index_view_test.go +++ b/pkg/tests/web/index_view_test.go @@ -1,6 +1,7 @@ package web import ( + "encoding/json" "fmt" "io" "net/http" @@ -10,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/tests/testinfra" ) @@ -27,7 +29,7 @@ func TestIntegrationIndexView(t *testing.T) { addr, _ := testinfra.StartGrafana(t, grafDir, cfgPath) // nolint:bodyclose - resp, html := makeRequest(t, addr) + resp, html := makeRequest(t, addr, "", "") assert.Regexp(t, `script-src 'self' 'unsafe-eval' 'unsafe-inline' 'strict-dynamic' 'nonce-[^']+';object-src 'none';font-src 'self';style-src 'self' 'unsafe-inline' blob:;img-src \* data:;base-uri 'self';connect-src 'self' grafana.com ws://localhost:3000/ wss://localhost:3000/;manifest-src 'self';media-src 'none';form-action 'self';`, resp.Header.Get("Content-Security-Policy")) assert.Regexp(t, ` -# Plugin migration guides +# Plugin migration guide The following guides help you identify the steps required to update a plugin following changes between versions of Grafana. diff --git a/docs/sources/developers/plugins/publish-a-plugin.md b/docs/sources/developers/plugins/publish-a-plugin.md index 687dda6c910..9f12dc73408 100644 --- a/docs/sources/developers/plugins/publish-a-plugin.md +++ b/docs/sources/developers/plugins/publish-a-plugin.md @@ -1,162 +1,60 @@ --- aliases: - share-a-plugin/ - - package-a-plugin/ description: Learn how to package and share your plugin. title: Publish a plugin type: docs --- -# Publish a plugin +# Publish or update a plugin + +You've just built your plugin; now you want to share it with the world. -You've just built your first plugin, and now you want to share it with the world. In this guide, you'll learn how to package and share your plugin with others. -For Grafana to be able to load a plugin, it first needs to be built. -When you build a plugin from source, a `dist` directory is created that contains the production build, or _plugin assets_, for your plugin. - -When the Grafana server starts, it recursively looks in the plugin directory for any directory that contains a `plugin.json` file and tries to load the plugin assets in the same directory. - -There are three steps needed to package a plugin: - -- Building the plugin -- Signing the plugin -- Packaging the plugin - -1. Build the plugin - - ``` - yarn install --pure-lockfile - yarn build - ``` - -1. (Optional) If your data source plugin has a backend plugin, build it as well. - - ``` - mage - ``` - - make sure that all the binaries are executable and have a `0755` (`-rwxr-xr-x`) permission - -1. [Sign the plugin]({{< relref "./sign-a-plugin" >}}). - -1. Rename `dist` directory to match your plugin id and create a ZIP archive - - ``` - mv dist/ myorg-simple-panel - zip myorg-simple-panel-1.0.0.zip myorg-simple-panel -r - ``` - -1. (Optional) Verify your plugin is packaged correctly using [zipinfo](https://linux.die.net/man/1/zipinfo). - It should look like this: - -``` -zipinfo grafana-clickhouse-datasource-1.1.2.zip - -Archive: grafana-clickhouse-datasource-1.1.2.zip -Zip file size: 34324077 bytes, number of entries: 22 -drwxr-xr-x 0 bx stor 22-Mar-24 23:23 grafana-clickhouse-datasource/ --rw-r--r-- 1654 bX defN 22-Mar-24 23:23 grafana-clickhouse-datasource/CHANGELOG.md --rw-r--r-- 11357 bX defN 22-Mar-24 23:23 grafana-clickhouse-datasource/LICENSE --rw-r--r-- 2468 bX defN 22-Mar-24 23:23 grafana-clickhouse-datasource/MANIFEST.txt --rw-r--r-- 8678 bX defN 22-Mar-24 23:23 grafana-clickhouse-datasource/README.md -drwxr-xr-x 0 bx stor 22-Mar-24 23:23 grafana-clickhouse-datasource/dashboards/ --rw-r--r-- 42973 bX defN 22-Mar-24 23:23 grafana-clickhouse-datasource/dashboards/cluster-analysis.json --rw-r--r-- 56759 bX defN 22-Mar-24 23:23 grafana-clickhouse-datasource/dashboards/data-analysis.json --rw-r--r-- 39406 bX defN 22-Mar-24 23:23 grafana-clickhouse-datasource/dashboards/query-analysis.json --rwxr-xr-x 16469136 bX defN 22-Mar-24 23:23 grafana-clickhouse-datasource/gpx_clickhouse_darwin_amd64 --rwxr-xr-x 16397666 bX defN 22-Mar-24 23:23 grafana-clickhouse-datasource/gpx_clickhouse_darwin_arm64 --rwxr-xr-x 14942208 bX defN 22-Mar-24 23:23 grafana-clickhouse-datasource/gpx_clickhouse_linux_amd64 --rwxr-xr-x 14155776 bX defN 22-Mar-24 23:23 grafana-clickhouse-datasource/gpx_clickhouse_linux_arm --rwxr-xr-x 14548992 bX defN 22-Mar-24 23:23 grafana-clickhouse-datasource/gpx_clickhouse_linux_arm64 --rwxr-xr-x 15209472 bX defN 22-Mar-24 23:23 grafana-clickhouse-datasource/gpx_clickhouse_windows_amd64.exe -drwxr-xr-x 0 bx stor 22-Mar-24 23:23 grafana-clickhouse-datasource/img/ --rw-r--r-- 304 bX defN 22-Mar-24 23:23 grafana-clickhouse-datasource/img/logo.png --rw-r--r-- 1587 bX defN 22-Mar-24 23:23 grafana-clickhouse-datasource/img/logo.svg --rw-r--r-- 138400 bX defN 22-Mar-24 23:23 grafana-clickhouse-datasource/module.js --rw-r--r-- 808 bX defN 22-Mar-24 23:23 grafana-clickhouse-datasource/module.js.LICENSE.txt --rw-r--r-- 487395 bX defN 22-Mar-24 23:23 grafana-clickhouse-datasource/module.js.map --rw-r--r-- 1616 bX defN 22-Mar-24 23:23 grafana-clickhouse-datasource/plugin.json -22 files, 92516655 bytes uncompressed, 34319591 bytes compressed: 62.9% -``` - -## Publish your plugin on Grafana.com - -The best way to share your plugin with the world is to publish it on [Grafana Plugins](/plugins). +The best way to share your plugin with the world is to publish it in the [Grafana plugin catalog]{{< relref "/plugins" >}}). By having your plugin published on Grafana.com, more users will be able to discover your plugin. -Before you submit your plugin, we ask that you read our guidelines and frequently asked questions. +## Before you begin -### Guidelines +When you build a plugin from source, a `dist` folder is created. This folder contains the production build or _plugin assets_ for your plugin. + +To package a plugin, refer to [Package a plugin]{{< relref "package-a-plugin.md" >}}). + +### Follow our guidelines + +Get familiar with our plugin [publishing and signing criteria]({{< relref "./publishing-and-signing-criteria" >}}) + +### Do this for best results To speed up the time it takes to review your plugin: -- Get familiar with our plugin [publishing and signing criteria]({{< relref "./publishing-and-signing-criteria" >}}) - Check that your plugin is ready for review using the [plugin validator](https://github.com/grafana/plugin-validator). - Read our [6 tips for improving your Grafana plugin before you publish](/blog/2021/01/21/6-tips-for-improving-your-grafana-plugin-before-you-publish/). - Refer to [plugin-examples](https://github.com/grafana/grafana-plugin-examples) to review best practices for building your plugin. -### Frequently Asked Questions +## Publish your plugin -**Do I need to submit a private plugin?** - -- No. Please only submit plugins that you wish to make publicly available for the Grafana community. - -**How long does it take to review my submission?** - -- We're not able to give an estimate at this time, though we're constantly working on improving the time it takes to review a plugin. - -**Can I decide a date when my plugin will be published?** - -- No. We cannot guarantee specific publishing dates, as plugins are immediately published after a review based on our internal prioritization. - -**Can I see metrics of my plugin installs, downloads or usage?** - -- No. We don't offer this information at the moment to plugin authors. - -**How can I update my plugin's catalog page?** - -- The plugin's catalog page content is extracted from the plugin README file. - To update the plugin's catalog page, it is necessary to submit an updated plugin with the new content included in the README file. - -**Can I unlist my plugin from the Grafana Plugin's Catalog in case of a bug?** - -- In the event of a bug, unlisting the plugin from the Grafana Plugin's Catalog may be possible in exceptional cases, such as security concerns. - However, we do not have control over the specific instances where the plugin is installed. - -**Can I distribute my plugin somewhere else than the Grafana Catalog?** - -- The official method for distributing Grafana plugins is through the Grafana Catalog. - Alternative methods, such as installing private or development plugins on local Grafana instances, are available as per the guidelines provided in [this guide]({{< relref "../../administration/plugin-management#install-plugin-on-local-grafana" >}}). - -**Can I still use Angular for my plugin?** - -- No. We will not accept any new plugin submissions written in angular. - Please take a look at our [angular support deprecation documentation]({{< relref "../angular_deprecation" >}}). - -### Publishing your plugin for the first time - -**Do plugin signatures expire?** - -- Plugin signatures do not currently expire. - -{{< figure src="/static/img/docs/plugins/plugins-submission-create2.png" class="docs-image--no-shadow" max-width="650px" >}} +Follow these steps to publish your plugin for the first time. 1. [Sign in](/auth/sign-in) to your Grafana Cloud account. 1. In the left menu, under **Org settings**, click **My Plugins**. -1. Click **Submit Plugin**. +1. Click **Submit Plugin**. The Create Plugin Submission form appears. + + {{< figure src="/static/img/docs/plugins/plugins-submission-create2.png" class="docs-image--no-shadow" max-width="650px" >}} + 1. Enter the information requested by the form. - **OS & Architecture:** - Select **Single** if your plugin archive contains binaries for multiple architectures. - Select **Multiple** if you'd like to submit separate plugin archives for each architecture. This can lead to faster downloads since users can select the specific architecture on which they want to install the plugin. - **URL:** A URL that points to a ZIP archive of your packaged plugin. - - **Source Code URL:** A URL that points to a public git repository or ZIP archive of your complete plugin source code. + - **Source Code URL:** A URL that points to a public Git repository or ZIP archive of your complete plugin source code. - **MD5:** The MD5 hash of the plugin specified by the **URL**. - The remaining questions help us determine the [signature level]({{< relref "./sign-a-plugin#plugin-signature-levels" >}}) for your plugin. 1. Click **Submit**. - After you have submitted your plugin, we run an automated validation to make sure it adheres to our guidelines. - Once your submission passes the validation, it's placed in a review queue. + After you submit your plugin, we run an automated validation to make sure it adheres to our guidelines. + Your submission is placed in the review queue after the submission is validated. All submissions are manually inspected by a plugin reviewer. For every new plugin, we perform a manual review that includes the following checks: @@ -165,21 +63,69 @@ For every new plugin, we perform a manual review that includes the following che If you're unable to make the source code publicly available, let us know in a comment on your plugin submission. - **Tests:** We install your plugin on one of our Grafana instances to test it for basic use. For more advanced plugins, we may ask you to assist us in configuring a test environment for the plugin. - This test environment will be used whenever you submit an plugin update. + We use the test environment whenever you submit a plugin update. -### Submit a plugin update +## Update your plugin To submit an **update** for an already published plugin: 1. [Sign in](/auth/sign-in) to your Grafana Cloud account. 1. In the left menu, under **Org settings**, click **My Plugins**. -1. Click **Submit Update** for the plugin you want to update. +1. Click **Submit Update** for the plugin you want to update. The Create Plugin Submission form appears. + + {{< figure src="/static/img/docs/plugins/plugins-submission-create2.png" class="docs-image--no-shadow" max-width="650px" >}} + 1. Enter the information requested by the form. - **OS & Architecture:** - Select **Single** if your plugin archive contains binaries for multiple architectures. - Select **Multiple** if you'd like to submit separate plugin archives for each architecture. This can lead to faster downloads since users can select the specific architecture they want to install the plugin on. - **URL:** A URL that points to a ZIP archive of your packaged plugin. - - **Source Code URL:** A URL that points to a public git repository or ZIP archive of your complete plugin source code. + - **Source Code URL:** A URL that points to a public Git repository or ZIP archive of your complete plugin source code. See [examples](#what-source-code-url-formats-are-supported). - **MD5:** The MD5 hash of the plugin specified by the **URL**. 1. Click **Submit**. + +## Frequently asked questions + +### Do I need to submit a private plugin? + +- No. Please only submit plugins that you wish to make publicly available for the Grafana community. + +### How long does it take to review my submission? + +- We're not able to give an estimate at this time, though we're constantly working on improving the time it takes to review a plugin. + +### Can I decide a date when my plugin will be published? + +- No. We cannot guarantee specific publishing dates, as plugins are immediately published after a review based on our internal prioritization. + +### Can I see metrics of my plugin installs, downloads or usage? + +- No. We don't offer this information at the moment to plugin authors. + +### How can I update my plugin's catalog page? + +- The plugin's catalog page content is extracted from the plugin README file. + To update the plugin's catalog page, submit an updated plugin with the new content included in the README file. + +### Can I unlist my plugin from the Grafana plugin catalog in case of a bug? + +- In the event of a bug, unlisting the plugin from our catalog may be possible in exceptional cases, such as security concerns. However, we don't have control over the specific instances where the plugin is installed. + +### Can I distribute my plugin somewhere else other than the Grafana plugin catalog? + +- The official method for distributing Grafana plugins is through our catalog. Alternative methods, such as installing private or development plugins on local Grafana instances, are available as per the guidelines provided in [this guide]({{< relref "../../administration/plugin-management#install-plugin-on-local-grafana" >}}). + +### Can I still use Angular for my plugin? + +- No. We will not accept any new plugin submissions written in Angular. For more information, refer to our [Angular support deprecation documentation]({{< relref "../angular_deprecation" >}}). + +### Do plugin signatures expire? + +- Plugin signatures do not currently expire. + +### What source code URL formats are supported? + +- Using a tag or branch: `https://github.com/grafana/clock-panel/tree/v2.1.3` +- A tag or branch and the code is in a subdirectory (important for mono repos): `https://github.com/grafana/clock-panel/tree/v2.1.3/plugin/` (here, the plugin contains the plugin code) +- Using the latest main or master branch commit: `https://github.com/grafana/clock-panel/` (not recommended, it's better to pass a tag or branch) diff --git a/docs/sources/developers/plugins/publish-a-plugin/_index.md b/docs/sources/developers/plugins/publish-a-plugin/_index.md new file mode 100644 index 00000000000..7dcd5996f49 --- /dev/null +++ b/docs/sources/developers/plugins/publish-a-plugin/_index.md @@ -0,0 +1,11 @@ +--- +description: Topics related to plugin publishing +title: Publish a plugin +--- + +# Publish a Grafana plugin + +This section contains topics related to publishing Grafana plugins. + +- [Publishing and signing criteria]({{< relref "publishing-and-signing-criteria.md">}}) +- [Publish a plugin]({{< relref "publish-a-plugin.md">}}) diff --git a/docs/sources/developers/plugins/publishing-and-signing-criteria.md b/docs/sources/developers/plugins/publishing-and-signing-criteria.md index 8cc7742ea17..38325a0d31f 100644 --- a/docs/sources/developers/plugins/publishing-and-signing-criteria.md +++ b/docs/sources/developers/plugins/publishing-and-signing-criteria.md @@ -4,28 +4,13 @@ title: Plugin publishing and signing criteria # Plugin publishing and signing criteria -Grafana plugins must adhere to the following criteria when being reviewed for publishing and signing. +Grafana plugins must adhere to the Grafana Labs [Plugin Policy](https://grafana.com/legal/plugins/). Our review process for publishing and signing will examine your compliance with this policy. -## Privacy and security - -- Plugins cannot collect usage or user information. Violations of this include but is not limited to: - - Directly collecting installation and user statistics - - Sending data to 3rd parties for analytics purposes - - Embedding tracking code -- Data at rest: sensitive data such as credentials and user information, must be encrypted using industry standards. - - Use secureJsonData to store data source credentials - - Secrets cannot be stored in panel options -- Data transmission: secure methods that meet industry standard encryption levels should be used, such as Secure Sockets Layer (SSL) or Transport Layer Security (TLS) -- Abuse: plugins should not perform actions beyond the scope of the intended use. - - Do not include hidden files - - Do not manipulate the underlying environment, privileges, or related processes -- Security: - - Should not access the filesystem - - Should not access environment variables +> **Important:** Grafana Labs reserves the right to decline or remove any plugin at its discretion. Failure to comply with publishing and signing criteria may result in immediate removal from the Grafana plugin catalog. ## Plugin licensing -Plugins must be licensed under one of the following AGPL compliant licenses for publishing to the catalog: +Plugins must be licensed under one of the following AGPL compliant licenses for publishing to the Grafana plugin catalog: - AGPL-3.0 - Apache-2.0 @@ -35,9 +20,3 @@ Plugins must be licensed under one of the following AGPL compliant licenses for - MIT If contributing a plugin on behalf of an organization, be sure to seek guidance from your legal team. - -## Commercial - -- Usage of 3rd party software or dependencies within the plugin must be licensed for the intended use. For example, using open source dependencies must be credited/licensed; embedding logos or trademarks; - -Grafana Labs reserves the right to decline or remove any plugin at its discretion. Failure to comply with publishing and signing criteria may result in immediate removal from the Grafana plugin catalog. diff --git a/docs/sources/developers/plugins/reference/_index.md b/docs/sources/developers/plugins/reference/_index.md new file mode 100644 index 00000000000..4398fc2fe61 --- /dev/null +++ b/docs/sources/developers/plugins/reference/_index.md @@ -0,0 +1,10 @@ +--- +description: Reference topics. +title: Reference +--- + +# Reference + +This section contains a metadata reference for plugin development. + +- [{{< relref "plugin.json">}}](metadata.md) diff --git a/docs/sources/developers/plugins/sign-a-plugin.md b/docs/sources/developers/plugins/sign-a-plugin.md index ebcff5a678a..0797650bab2 100644 --- a/docs/sources/developers/plugins/sign-a-plugin.md +++ b/docs/sources/developers/plugins/sign-a-plugin.md @@ -4,17 +4,19 @@ title: Sign a plugin # Sign a plugin -Signing a plugin allows Grafana to verify the authenticity of the plugin with [signature verification]({{< relref "../../administration/plugin-management#plugin-signatures" >}}). This gives users a way to make sure plugins haven't been tampered with. By [default]({{< relref "../../administration/plugin-management#allow-unsigned-plugins" >}}), Grafana **requires** all plugins to be signed in order for them to be loaded. All Grafana Labs-authored backend plugins, including Enterprise plugins, are signed. +Grafana requires all plugins to be signed so that we can verify their authenticity with [signature verification]({{< relref "../../administration/plugin-management#plugin-signatures" >}}). + +All Grafana Labs-authored backend plugins, including Enterprise plugins, are signed. By [default]({{< relref "../../administration/plugin-management#allow-unsigned-plugins" >}}), Grafana **requires** all plugins to be signed in order for them to be loaded. Before you can sign your plugin, you need to decide whether you want to sign it as a _public_ or a _private_ plugin. -If you want to make your plugin publicly available outside of your organization, you need to sign your plugin under a _community_ or _commercial_ [signature level](#plugin-signature-levels). Public plugins are available from [grafana.com/plugins](https://grafana.com/plugins) and can be installed by anyone. +To make your plugin publicly available outside of your organization, sign your plugin under a _community_ or _commercial_ [signature level](#plugin-signature-levels). Public plugins are available from the [Grafana plugin catalog](https://grafana.com/plugins) and can be installed by anyone. -If you intend to only use the plugin within your organization, you can sign it under a _private_ [signature level](#plugin-signature-levels). +If you intend to only use the plugin within your organization, sign it under a _private_ [signature level](#plugin-signature-levels). ## Generate an API key -To verify ownership of your plugin, you need to generate an API key that you'll use every time you need to sign a new version of your plugin. +To verify ownership of your plugin, generate an API key that you'll use every time you need to sign a new version of your plugin. 1. [Create a Grafana Cloud account](https://grafana.com/signup). @@ -29,8 +31,8 @@ To verify ownership of your plugin, you need to generate an API key that you'll Public plugins need to be reviewed by the Grafana team before you can sign them. 1. Submit your plugin for [review]({{< relref "publish-a-plugin/#publishing-your-plugin-for-the-first-time" >}}) -2. When your plugin is approved, you're granted a plugin signature level. **Without a plugin signature level, you won't be able to sign your plugin**. -3. In your plugin directory, sign the plugin with the API key you just created. Grafana Sign Plugin creates a [MANIFEST.txt](#plugin-manifest) file in the `dist` directory of your plugin. +1. If we approve your plugin, you're granted a plugin signature level. You need this signature level to proceed. +1. In your plugin directory, sign the plugin with the API key you just created. Grafana Sign Plugin creates a [MANIFEST.txt](#plugin-manifest) file in the `dist` directory of your plugin: ```bash export GRAFANA_API_KEY= @@ -41,24 +43,24 @@ Public plugins need to be reviewed by the Grafana team before you can sign them. 1. In your plugin directory, sign the plugin with the API key you just created. Grafana Sign Plugin creates a [MANIFEST.txt](#plugin-manifest) file in the `dist` directory of your plugin. - The `rootUrls` flag accepts a comma-separated list of URLs to the Grafana instances where you intend to install the plugin. - ```bash export GRAFANA_API_KEY= npx @grafana/sign-plugin@latest --rootUrls https://example.com/grafana ``` +1. After the `rootUrls` flag, enter a comma-separated list of URLs for the Grafana instances where you intend to install the plugin. + ## Plugin signature levels -To sign a plugin, you need to decide the _signature level_ you want to sign it under. The signature level of your plugin determines how you can distribute it. +To sign a plugin, you need to select the _signature level_ that you want to sign it under. The signature level of your plugin determines how you can distribute it. -You can sign your plugin under three different _signature levels_. +You can sign your plugin under three different _signature levels_: _private_, _community_, and _commercial_. -| **Plugin Level** | **Paid Subscription Required?** | **Description** | -| ---------------- | ----------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Private | No;
Free of charge |

You can create and sign a Private Plugin for any technology at no charge.

Private Plugins are for use on your own Grafana. They may not be distributed to the Grafana community, and are not published in the Grafana catalog.

| -| Community | No;
Free of charge |

You can create, sign and distribute plugins at no charge, provided that all dependent technologies are open source and not for profit.

Community Plugins are published in the official Grafana catalog, and are available to the Grafana community.

| -| Commercial | Yes;
Commercial Plugin Subscription required |

You can create, sign and distribute plugins with dependent technologies that are closed source or commercially backed, by entering into a Commercial Plugin Subscription with Grafana Labs.

Commercial Plugins are published on the official Grafana catalog, and are available to the Grafana community.

| +| **Plugin Level** | **Paid Subscription Required?** | **Description** | +| ---------------- | ----------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Private | No;
Free of charge |

You can create and sign a Private plugin for any technology at no charge.

Private plugins are intended for use on your own installation of Grafana. They may not be distributed to the Grafana community, and they are not published in the Grafana plugin catalog.

| +| Community | No;
Free of charge |

You can create, sign, and distribute plugins at no charge, provided that all dependent technologies are open source and not for profit.

Community plugins are published in the official Grafana catalog, and are available to the entire Grafana community.

| +| Commercial | Yes;
Commercial plugin subscription required |

You can create, sign, and distribute plugins with dependent technologies that are closed source or commercially backed. To do so, enter into a Commercial plugin subscription with Grafana Labs.

Commercial plugins are published on the Grafana plugin catalog, and are available to the entire Grafana community.

| For instructions on how to sign a plugin under the Community and Commercial signature level, refer to [Sign a public plugin](#sign-a-public-plugin). @@ -66,12 +68,12 @@ For instructions on how to sign a plugin under the Private signature level, refe ## Plugin manifest -For Grafana to verify the digital signature of a plugin, the plugin must include a signed manifest file, _MANIFEST.txt_. The signed manifest file contains two sections: +For Grafana to verify the digital signature of a plugin, the plugin must include a signed manifest file, `MANIFEST.txt`. The signed manifest file contains two sections: -- **Signed message -** The signed message contains plugin metadata and plugin files with their respective checksums (SHA256). -- **Digital signature -** The digital signature is created by encrypting the signed message using a private key. Grafana has a public key built-in that can be used to verify that the digital signature have been encrypted using expected private key. +- **Signed message -** Contains plugin metadata and plugin files with their respective checksums (SHA256). +- **Digital signature -** Created by encrypting the signed message using a private key. Grafana has a public key built-in that can be used to verify that the digital signature has been encrypted using the expected private key. -**Example manifest file:** +**Example** ```txt -----BEGIN PGP SIGNED MESSAGE----- @@ -107,16 +109,18 @@ T6scfmuhWC/TOcm83EVoCzIV3R5dOTKHqkjIUg== -----END PGP SIGNATURE----- ``` -## Troubleshooting issues while signing your plugin +## Troubleshooting -### Why am I getting a "Modified signature" in Grafana? +### Why do I get a "Modified signature" error? -Due to an issue when signing the plugin on Windows, in some cases an invalid MANIFEST.txt is being generated. You can fix this by replacing all double backslashes, `\\`, with a forward slash, `/` in the MANIFEST.txt file. You need to do this every time you sign your plugin. +In some cases an invalid `MANIFEST.txt` is generated because of an issue when signing the plugin on Windows. You can fix this by replacing all double backslashes, `\\`, with a forward slash, `/`, in the `MANIFEST.txt` file. You need to do this every time you sign your plugin. -### Error signing manifest: Field is required: rootUrls +### Why do I get a "Field is required: `rootUrls`" error for my public plugin? -If you're trying to sign a **public** plugin, this means that your plugin doesn't have a plugin signature level assigned to it yet. A Grafana team member will assign a signature level to your plugin once it has been reviewed and approved. For more information, refer to [Sign a public plugin](#sign-a-public-plugin). +With a **public** plugin, your plugin doesn't have a plugin signature level assigned to it yet. A Grafana team member will assign a signature level to your plugin once it has been reviewed and approved. For more information, refer to [Sign a public plugin](#sign-a-public-plugin). -If you're trying to sign a **private** plugin, this means that you need to add a `rootUrls` flag to the `plugin:sign` command. The `rootUrls` must match the [root_url]({{< relref "../../setup-grafana/configure-grafana/#root_url" >}}) configuration. For more information, refer to [Sign a private plugin](#sign-a-private-plugin). +### Why do I get a "Field is required: `rootUrls`" error for my private plugin? + +With a **private** plugin, you need to add a `rootUrls` flag to the `plugin:sign` command. The `rootUrls` must match the [root_url]({{< relref "../../setup-grafana/configure-grafana/#root_url" >}}) configuration. For more information, refer to [Sign a private plugin](#sign-a-private-plugin). If you still get this error, make sure that the API key was generated by a Grafana Cloud account that matches the first part of the plugin ID. From 8e51c4895d64022284e4be43de482b6e0937462c Mon Sep 17 00:00:00 2001 From: brendamuir <100768211+brendamuir@users.noreply.github.com> Date: Tue, 9 May 2023 08:53:12 +0200 Subject: [PATCH 054/179] Docs: Fix routes in provisioning (#67970) * Fix routes in provisioning * fixes spacing --- .../provision-alerting-resources/file-provisioning/index.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/sources/alerting/set-up/provision-alerting-resources/file-provisioning/index.md b/docs/sources/alerting/set-up/provision-alerting-resources/file-provisioning/index.md index 9abfaf4cc94..d5234e6bc3f 100644 --- a/docs/sources/alerting/set-up/provision-alerting-resources/file-provisioning/index.md +++ b/docs/sources/alerting/set-up/provision-alerting-resources/file-provisioning/index.md @@ -548,7 +548,8 @@ policies: repeat_interval: 4h # Zero or more child routes # routes: - # ... + # - Another recursively nested policy... + # ... ``` Here is an example of a configuration file for resetting notification policies. From 6ae952c7c5d7510977a335a7be2ab13968433291 Mon Sep 17 00:00:00 2001 From: Dimitris Sotirakis Date: Tue, 9 May 2023 11:22:18 +0300 Subject: [PATCH 055/179] CI: Remove custom enterprise2 pipelines (#68056) Remove custom enterprise2 pipelines --- .drone.star | 4 - .drone.yml | 253 +--------------------------------------------------- 2 files changed, 1 insertion(+), 256 deletions(-) diff --git a/.drone.star b/.drone.star index 17e9ddceef5..ec10d154fba 100644 --- a/.drone.star +++ b/.drone.star @@ -46,10 +46,6 @@ def main(_ctx): oss_pipelines() + enterprise_pipelines() + enterprise2_pipelines() + - enterprise2_pipelines( - prefix = "custom-", - trigger = {"event": ["custom"]}, - ) + publish_image_pipelines_public() + publish_image_pipelines_security() + publish_github_pipeline("public") + diff --git a/.drone.yml b/.drone.yml index ac5e04ea77a..dbeddd1e2fd 100644 --- a/.drone.yml +++ b/.drone.yml @@ -3843,257 +3843,6 @@ volumes: temp: medium: memory --- -clone: - disable: true -depends_on: [] -environment: - EDITION: enterprise2 -image_pull_secrets: -- dockerconfigjson -kind: pipeline -name: custom-release-enterprise2-build-e2e-publish -node: - type: no-parallel -platform: - arch: amd64 - os: linux -services: [] -steps: -- commands: - - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.30/grabpl - - chmod +x bin/grabpl - image: byrnedo/alpine-curl:0.1.8 - name: grabpl -- commands: - - echo $DRONE_RUNNER_NAME - image: alpine:3.17.1 - name: identify-runner -- commands: - - git clone "https://$${GITHUB_TOKEN}@github.com/grafana/grafana-enterprise.git" - - cd grafana-enterprise - - git checkout ${DRONE_TAG} - environment: - GITHUB_TOKEN: - from_secret: github_token - image: grafana/build-container:1.7.4 - name: clone-enterprise -- commands: - - mv bin/grabpl /tmp/ - - rmdir bin - - mv grafana-enterprise /tmp/ - - /tmp/grabpl init-enterprise --github-token $${GITHUB_TOKEN} /tmp/grafana-enterprise - ${DRONE_TAG} - - mv /tmp/grafana-enterprise/deployment_tools_config.json deployment_tools_config.json - - mkdir bin - - mv /tmp/grabpl bin/ - depends_on: - - clone-enterprise - - grabpl - environment: - GITHUB_TOKEN: - from_secret: github_token - image: grafana/build-container:1.7.4 - name: init-enterprise -- commands: - - go build -o ./bin/build -ldflags '-extldflags -static' ./pkg/build/cmd - depends_on: - - init-enterprise - environment: - CGO_ENABLED: 0 - image: golang:1.20.4 - name: compile-build-cmd -- commands: - - make gen-go - depends_on: - - init-enterprise - image: grafana/build-container:1.7.4 - name: wire-install -- commands: - - yarn install --immutable - depends_on: - - init-enterprise - image: grafana/build-container:1.7.4 - name: yarn-install -- commands: - - '# It is required that code generated from Thema/CUE be committed and in sync - with its inputs.' - - '# The following command will fail if running code generators produces any diff - in output.' - - CODEGEN_VERIFY=1 make gen-cue - depends_on: - - init-enterprise - image: grafana/build-container:1.7.4 - name: verify-gen-cue -- commands: - - ./bin/build build-frontend --jobs 8 --edition enterprise ${DRONE_TAG} - depends_on: - - compile-build-cmd - - yarn-install - environment: - NODE_OPTIONS: --max_old_space_size=8192 - image: grafana/build-container:1.7.4 - name: build-frontend -- commands: - - ./bin/build build-frontend-packages --jobs 8 --edition enterprise ${DRONE_TAG} - depends_on: - - compile-build-cmd - - yarn-install - environment: - NODE_OPTIONS: --max_old_space_size=8192 - image: grafana/build-container:1.7.4 - name: build-frontend-packages -- commands: - - ./bin/build build-plugins --jobs 8 --edition enterprise - depends_on: - - compile-build-cmd - - yarn-install - environment: - GRAFANA_API_KEY: - from_secret: grafana_api_key - image: grafana/build-container:1.7.4 - name: build-plugins -- commands: - - ./bin/build build-backend --jobs 8 --edition enterprise2 ${DRONE_TAG} - depends_on: - - wire-install - - compile-build-cmd - image: grafana/build-container:1.7.4 - name: build-backend-enterprise2 -- commands: - - ./bin/build package --jobs 8 --edition enterprise2 --sign ${DRONE_TAG} - depends_on: - - build-plugins - - build-backend-enterprise2 - - build-frontend - - build-frontend-packages - environment: - GPG_KEY_PASSWORD: - from_secret: packages_gpg_passphrase - GPG_PRIV_KEY: - from_secret: packages_gpg_private_key - GPG_PUB_KEY: - from_secret: packages_gpg_public_key - GRAFANA_API_KEY: - from_secret: grafana_api_key - image: grafana/build-container:1.7.4 - name: package-enterprise2 -- commands: - - ./bin/build upload-cdn --edition enterprise2 - depends_on: - - package-enterprise2 - environment: - ENTERPRISE2_CDN_PATH: - from_secret: enterprise2-cdn-path - GCP_KEY: - from_secret: gcp_key - PRERELEASE_BUCKET: - from_secret: prerelease_bucket - image: grafana/grafana-ci-deploy:1.3.3 - name: upload-cdn-assets-enterprise2 -- commands: - - ls dist/*.tar.gz* - - cp dist/*.tar.gz* packaging/docker/ - depends_on: - - package-enterprise2 - image: grafana/build-container:1.7.4 - name: copy-packages-for-docker -- commands: - - ./bin/build build-docker --edition enterprise2 --shouldSave - depends_on: - - copy-packages-for-docker - - compile-build-cmd - environment: - DOCKER_ENTERPRISE2_REPO: - from_secret: docker_enterprise2_repo - GCP_KEY: - from_secret: gcp_key - image: google/cloud-sdk - name: build-docker-images - volumes: - - name: docker - path: /var/run/docker.sock -- commands: - - ./bin/build build-docker --edition enterprise2 --shouldSave --ubuntu - depends_on: - - copy-packages-for-docker - - compile-build-cmd - environment: - DOCKER_ENTERPRISE2_REPO: - from_secret: docker_enterprise2_repo - GCP_KEY: - from_secret: gcp_key - image: google/cloud-sdk - name: build-docker-images-ubuntu - volumes: - - name: docker - path: /var/run/docker.sock -- commands: - - ./bin/build artifacts docker fetch --edition enterprise2 - depends_on: - - build-docker-images - - build-docker-images-ubuntu - environment: - DOCKER_ENTERPRISE2_REPO: - from_secret: docker_enterprise2_repo - DOCKER_PASSWORD: - from_secret: docker_password - DOCKER_USER: - from_secret: docker_username - GCP_KEY: - from_secret: gcp_key - image: google/cloud-sdk - name: fetch-images-enterprise2 - volumes: - - name: docker - path: /var/run/docker.sock -- commands: - - ./bin/build artifacts docker publish-enterprise2 --dockerhub-repo $${DOCKER_ENTERPRISE2_REPO} - depends_on: - - fetch-images-enterprise2 - environment: - DOCKER_ENTERPRISE2_REPO: - from_secret: docker_enterprise2_repo - DOCKER_PASSWORD: - from_secret: docker_password - DOCKER_USER: - from_secret: docker_username - GCP_KEY: - from_secret: gcp_key_hg - image: google/cloud-sdk - name: publish-images-enterprise2 - volumes: - - name: docker - path: /var/run/docker.sock -- commands: - - ./bin/build upload-packages --edition enterprise2 - depends_on: - - package-enterprise2 - environment: - GCP_KEY: - from_secret: gcp_key - PRERELEASE_BUCKET: - from_secret: prerelease_bucket - image: grafana/grafana-ci-deploy:1.3.3 - name: upload-packages-enterprise2 -trigger: - event: - - custom -type: docker -volumes: -- host: - path: /var/run/docker.sock - name: docker -- name: postgres - temp: - medium: memory -- name: mysql57 - temp: - medium: memory -- name: mysql80 - temp: - medium: memory ---- clone: retries: 3 depends_on: [] @@ -7594,6 +7343,6 @@ kind: secret name: github_token --- kind: signature -hmac: de8e1e63e1e22d145970d335bb1140c68c77e3833d54748f0a34720ab72d7be2 +hmac: dc3090ab1f4561e295654e1aa01e61a3fbb8bbe17eb5c935f3e808f545a75b39 ... From 8bb62a831623ae156b4574e01b9acedac5bbe234 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Philippe=20Qu=C3=A9m=C3=A9ner?= Date: Tue, 9 May 2023 10:32:23 +0200 Subject: [PATCH 056/179] Alerting: Add option for memberlist label (#67982) --- conf/defaults.ini | 5 +++++ conf/sample.ini | 5 +++++ go.mod | 8 ++++---- go.sum | 17 +++++++++-------- .../ngalert/notifier/multiorg_alertmanager.go | 1 + pkg/setting/setting_unified_alerting.go | 2 ++ 6 files changed, 26 insertions(+), 12 deletions(-) diff --git a/conf/defaults.ini b/conf/defaults.ini index 989d2324458..30bd9c64886 100644 --- a/conf/defaults.ini +++ b/conf/defaults.ini @@ -1013,6 +1013,11 @@ ha_peers = "" # The interval string is a possibly signed sequence of decimal numbers, followed by a unit suffix (ms, s, m, h, d), e.g. 30s or 1m. ha_peer_timeout = 15s +# The label is an optional string to include on each packet and stream. +# It uniquely identifies the cluster and prevents cross-communication +# issues when sending gossip messages in an enviromenet with multiple clusters. +ha_label = + # The interval between sending gossip messages. By lowering this value (more frequent) gossip messages are propagated # across cluster more quickly at the expense of increased bandwidth usage. # The interval string is a possibly signed sequence of decimal numbers, followed by a unit suffix (ms, s, m, h, d), e.g. 30s or 1m. diff --git a/conf/sample.ini b/conf/sample.ini index 0d875d2b438..edef7dc3872 100644 --- a/conf/sample.ini +++ b/conf/sample.ini @@ -983,6 +983,11 @@ # The interval string is a possibly signed sequence of decimal numbers, followed by a unit suffix (ms, s, m, h, d), e.g. 30s or 1m. ;ha_peer_timeout = "15s" +# The label is an optional string to include on each packet and stream. +# It uniquely identifies the cluster and prevents cross-communication +# issues when sending gossip messages in an enviromenet with multiple clusters. +;ha_label = + # The interval between sending gossip messages. By lowering this value (more frequent) gossip messages are propagated # across cluster more quickly at the expense of increased bandwidth usage. # The interval string is a possibly signed sequence of decimal numbers, followed by a unit suffix (ms, s, m, h, d), e.g. 30s or 1m. diff --git a/go.mod b/go.mod index 967b0c8b1a7..782a4fdb6c8 100644 --- a/go.mod +++ b/go.mod @@ -34,7 +34,7 @@ require ( github.com/VividCortex/mysqlerr v0.0.0-20170204212430-6c6b55f8796f github.com/aws/aws-sdk-go v1.44.234 github.com/beevik/etree v1.1.0 - github.com/benbjohnson/clock v1.3.0 + github.com/benbjohnson/clock v1.3.3 github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b github.com/centrifugal/centrifuge v0.29.1 github.com/crewjam/saml v0.4.12 @@ -82,7 +82,7 @@ require ( github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect github.com/pkg/errors v0.9.1 github.com/prometheus/alertmanager v0.25.0 - github.com/prometheus/client_golang v1.14.0 + github.com/prometheus/client_golang v1.15.0 github.com/prometheus/client_model v0.3.0 github.com/prometheus/common v0.42.0 github.com/prometheus/prometheus v1.8.2-0.20211217191541-41f1a8125e66 @@ -199,7 +199,7 @@ require ( github.com/prometheus/exporter-toolkit v0.9.1 // indirect github.com/prometheus/procfs v0.9.0 // indirect github.com/protocolbuffers/txtpbfmt v0.0.0-20220428173112-74888fd59c2b // indirect - github.com/rs/cors v1.8.3 // indirect + github.com/rs/cors v1.9.0 // indirect github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect github.com/segmentio/encoding v0.3.6 // indirect github.com/sergi/go-diff v1.2.0 // indirect @@ -413,7 +413,7 @@ replace xorm.io/xorm => github.com/grafana/xorm v0.8.3-0.20220614223926-2fcda756 // Use our fork of the upstream alertmanagers. // This is required in order to get notification delivery errors from the receivers API. -replace github.com/prometheus/alertmanager => github.com/grafana/prometheus-alertmanager v0.25.1-0.20230426142720-803b73a2f1f7 +replace github.com/prometheus/alertmanager => github.com/grafana/prometheus-alertmanager v0.25.1-0.20230508090422-7d5630522a53 // grpc v1.46.0 removed "WithBalancerName()" API, still in use by weaveworks/commons. replace google.golang.org/grpc => google.golang.org/grpc v1.45.0 diff --git a/go.sum b/go.sum index 1d845a72423..e607e3eedff 100644 --- a/go.sum +++ b/go.sum @@ -400,8 +400,8 @@ github.com/axiomhq/hyperloglog v0.0.0-20191112132149-a4c4c47bc57f/go.mod h1:2stg github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs= github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= -github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= -github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/benbjohnson/clock v1.3.3 h1:g+rSsSaAzhHJYcIQE78hJ3AhyjjtQvleKDjlhdBnIhc= +github.com/benbjohnson/clock v1.3.3/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/benbjohnson/immutable v0.2.1/go.mod h1:uc6OHo6PN2++n98KHLxW8ef4W42ylHiQSENghE1ezxI= github.com/benbjohnson/tmpl v1.0.0/go.mod h1:igT620JFIi44B6awvU9IsDhR77IXWtFigTLil/RPdps= github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= @@ -1304,8 +1304,8 @@ github.com/grafana/kindsys v0.0.0-20230427152021-bb328815be7a h1:i2YhC6eTyDp+7Ft github.com/grafana/kindsys v0.0.0-20230427152021-bb328815be7a/go.mod h1:GNcfpy5+SY6RVbNGQW264gC0r336Dm+0zgQ5vt6+M8Y= github.com/grafana/phlare/api v0.1.4-0.20230426005640-f90edba05413 h1:bBzCezZNRyYlJpXTkyZdY4fpPxHZUdyeyRWzhtw/P6I= github.com/grafana/phlare/api v0.1.4-0.20230426005640-f90edba05413/go.mod h1:IvwuGG9xa/h96UH/exgvsfy3zE+ZpctkNT9o5aaGdrU= -github.com/grafana/prometheus-alertmanager v0.25.1-0.20230426142720-803b73a2f1f7 h1:ebffH74Mxh6WLnoShty88jWQ5iLlI/d/qOxS/wdclMg= -github.com/grafana/prometheus-alertmanager v0.25.1-0.20230426142720-803b73a2f1f7/go.mod h1:YYDJFReDm1zvLZnY0qE4mYuBzSnUFZy+qGDM79/C4Y0= +github.com/grafana/prometheus-alertmanager v0.25.1-0.20230508090422-7d5630522a53 h1:X3Jl4PBIGCtlPSMa6Uiu2+3FDNWmddSjivp+1DDznQs= +github.com/grafana/prometheus-alertmanager v0.25.1-0.20230508090422-7d5630522a53/go.mod h1:QtbJShIif+fPgjXVPy3mEJaNebBHSU5NkoFtzsUS1c0= github.com/grafana/saml v0.4.13-0.20230331080031-67cbfa09c7b6 h1:oHn/OOUkECNX06DPHksS7R3UY5Qdye04b/sBj2/OJ5E= github.com/grafana/saml v0.4.13-0.20230331080031-67cbfa09c7b6/go.mod h1:igEejV+fihTIlHXYP8zOec3V5A8y3lws5bQBFsTm4gA= github.com/grafana/sqlds/v2 v2.3.10 h1:HWKhE0vR6LoEiE+Is8CSZOgaB//D1yqb2ntkass9Fd4= @@ -1996,8 +1996,9 @@ github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqr github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= github.com/prometheus/client_golang v1.13.0/go.mod h1:vTeo+zgvILHsnnj/39Ou/1fPN5nJFOEMgftOUOmlvYQ= -github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw= github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= +github.com/prometheus/client_golang v1.15.0 h1:5fCgGYogn0hFdhyhLbw7hEsWxufKtY9klyvdNfFlFhM= +github.com/prometheus/client_golang v1.15.0/go.mod h1:e9yaBhRPU2pPNsZwE+JdQl0KEt1N9XgF6zxWmaC0xOk= github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= @@ -2087,8 +2088,8 @@ github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6po github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rs/cors v1.8.0/go.mod h1:EBwu+T5AvHOcXwvZIkQFjUN6s8Czyqw12GL/Y0tUyRM= -github.com/rs/cors v1.8.3 h1:O+qNyWn7Z+F9M0ILBHgMVPuB1xTOucVd5gtaYyXBpRo= -github.com/rs/cors v1.8.3/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= +github.com/rs/cors v1.9.0 h1:l9HGsTsHJcvW14Nk7J9KFz8bzeAWXn3CG6bgt7LsrAE= +github.com/rs/cors v1.9.0/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= @@ -3224,7 +3225,7 @@ gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= -gopkg.in/telebot.v3 v3.1.2/go.mod h1:GJKwwWqp9nSkIVN51eRKU78aB5f5OnQuWdwiIZfPbko= +gopkg.in/telebot.v3 v3.1.3/go.mod h1:GJKwwWqp9nSkIVN51eRKU78aB5f5OnQuWdwiIZfPbko= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= diff --git a/pkg/services/ngalert/notifier/multiorg_alertmanager.go b/pkg/services/ngalert/notifier/multiorg_alertmanager.go index 8b687c2d9ac..c2c41b854a0 100644 --- a/pkg/services/ngalert/notifier/multiorg_alertmanager.go +++ b/pkg/services/ngalert/notifier/multiorg_alertmanager.go @@ -120,6 +120,7 @@ func (moa *MultiOrgAlertmanager) setupClustering(cfg *setting.Cfg) error { cluster.DefaultProbeInterval, nil, true, + cfg.UnifiedAlerting.HALabel, ) if err != nil { diff --git a/pkg/setting/setting_unified_alerting.go b/pkg/setting/setting_unified_alerting.go index 63b75b51578..88d3dd63674 100644 --- a/pkg/setting/setting_unified_alerting.go +++ b/pkg/setting/setting_unified_alerting.go @@ -71,6 +71,7 @@ type UnifiedAlertingSettings struct { HAPeerTimeout time.Duration HAGossipInterval time.Duration HAPushPullInterval time.Duration + HALabel string HARedisAddr string HARedisPeerName string HARedisPrefix string @@ -230,6 +231,7 @@ func (cfg *Cfg) ReadUnifiedAlertingSettings(iniFile *ini.File) error { } uaCfg.HAListenAddr = ua.Key("ha_listen_address").MustString(alertmanagerDefaultClusterAddr) uaCfg.HAAdvertiseAddr = ua.Key("ha_advertise_address").MustString("") + uaCfg.HALabel = ua.Key("ha_label").MustString("") uaCfg.HARedisAddr = ua.Key("ha_redis_address").MustString("") uaCfg.HARedisPeerName = ua.Key("ha_redis_peer_name").MustString("") uaCfg.HARedisPrefix = ua.Key("ha_redis_prefix").MustString("") From 8a33e18d2e88a5e796a9ae23b8ce05f0757ecfb8 Mon Sep 17 00:00:00 2001 From: Jack Westbrook Date: Tue, 9 May 2023 10:40:06 +0200 Subject: [PATCH 057/179] Chore: Bump yaml package >=2.0.0 to 2.2.2 (#67812) chore(npm): bump yaml package >=2.0.0 to 2.2.2 --- yarn.lock | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/yarn.lock b/yarn.lock index de2e1c7be14..30e257a576a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -34949,17 +34949,10 @@ __metadata: languageName: node linkType: hard -"yaml@npm:^2.0.0": - version: 2.1.0 - resolution: "yaml@npm:2.1.0" - checksum: 59323a8b51b10d9ad0eab951e7a1f315f1076c123b08ffe60441add1df4fa3433b1d5783b21c50a65536e9d853b23fa567921dbd4bc0d711be2dbf14a06be03b - languageName: node - linkType: hard - -"yaml@npm:^2.1.3": - version: 2.1.3 - resolution: "yaml@npm:2.1.3" - checksum: 91316062324a93f9cb547469092392e7d004ff8f70c40fecb420f042a4870b2181557350da56c92f07bd44b8f7a252b0be26e6ade1f548e1f4351bdd01c9d3c7 +"yaml@npm:^2.0.0, yaml@npm:^2.1.3": + version: 2.2.2 + resolution: "yaml@npm:2.2.2" + checksum: d90c235e099e30094dcff61ba3350437aef53325db4a6bcd04ca96e1bfe7e348b191f6a7a52b5211e2dbc4eeedb22a00b291527da030de7c189728ef3f2b4eb3 languageName: node linkType: hard From 8779526f15360a13b7333d9446ea76f1380a56b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Farkas?= Date: Tue, 9 May 2023 10:42:37 +0200 Subject: [PATCH 058/179] elasticsearch: improved deprecation warning (#67909) * elasticsearch: improved deprecation warning * adjusted warning-level --- .../QueryEditor/MetricAggregationsEditor/index.tsx | 12 ++++++------ .../QueryEditor/QueryEditorSpecialMetricRow.tsx | 8 +------- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/index.tsx b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/index.tsx index a916ef77ba9..e104465fb31 100644 --- a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/index.tsx +++ b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/index.tsx @@ -1,5 +1,7 @@ import React from 'react'; +import { Alert } from '@grafana/ui'; + import { useDispatch } from '../../../hooks/useStatelessReducer'; import { IconButton } from '../../IconButton'; import { useQuery } from '../ElasticsearchQueryContext'; @@ -30,12 +32,10 @@ export const MetricAggregationsEditor = ({ nextId }: Props) => { return ; case 'raw_document': return ( - + <> + + + ); default: return ( diff --git a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/QueryEditorSpecialMetricRow.tsx b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/QueryEditorSpecialMetricRow.tsx index 67f30d472e7..6820e012391 100644 --- a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/QueryEditorSpecialMetricRow.tsx +++ b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/QueryEditorSpecialMetricRow.tsx @@ -9,10 +9,9 @@ import { SettingsEditor } from './MetricAggregationsEditor/SettingsEditor'; type Props = { name: string; metric: MetricAggregation; - info?: string; }; -export const QueryEditorSpecialMetricRow = ({ name, metric, info }: Props) => { +export const QueryEditorSpecialMetricRow = ({ name, metric }: Props) => { // this widget is only used in scenarios when there is only a single // metric, so the array of "previousMetrics" (meaning all the metrics // before the current metric), is an ampty-array @@ -26,11 +25,6 @@ export const QueryEditorSpecialMetricRow = ({ name, metric, info }: Props) => { - {info != null && ( - - {info} - - )} ); }; From 1d537086c433a075a0b9b4e1b03149788f35e420 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 9 May 2023 11:01:09 +0200 Subject: [PATCH 059/179] DashboardPage: Remove file drop code (#67954) --- .../dashboard/containers/DashboardPage.tsx | 154 ++---------------- 1 file changed, 11 insertions(+), 143 deletions(-) diff --git a/public/app/features/dashboard/containers/DashboardPage.tsx b/public/app/features/dashboard/containers/DashboardPage.tsx index 5e5c7d09306..3f9e07827bc 100644 --- a/public/app/features/dashboard/containers/DashboardPage.tsx +++ b/public/app/features/dashboard/containers/DashboardPage.tsx @@ -1,23 +1,11 @@ -import { css, cx } from '@emotion/css'; +import { cx } from '@emotion/css'; import React, { PureComponent } from 'react'; -import DropZone, { FileRejection, DropEvent, ErrorCode } from 'react-dropzone'; import { connect, ConnectedProps } from 'react-redux'; -import { - NavModel, - NavModelItem, - TimeRange, - PageLayoutType, - locationUtil, - dataFrameToJSON, - DataFrameJSON, - GrafanaTheme2, - getValueFormat, - formattedValueToString, -} from '@grafana/data'; +import { NavModel, NavModelItem, TimeRange, PageLayoutType, locationUtil } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; -import { config, locationService, reportInteraction } from '@grafana/runtime'; -import { Icon, Themeable2, withTheme2 } from '@grafana/ui'; +import { locationService } from '@grafana/runtime'; +import { Themeable2, withTheme2 } from '@grafana/ui'; import { notifyApp } from 'app/core/actions'; import { Page } from 'app/core/components/Page/Page'; import { EntityNotFound } from 'app/core/components/PageNotFound/EntityNotFound'; @@ -27,10 +15,8 @@ import { getKioskMode } from 'app/core/navigation/kiosk'; import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; import { getNavModel } from 'app/core/selectors/navModel'; import { PanelModel } from 'app/features/dashboard/state'; -import * as DFImport from 'app/features/dataframe-import'; import { dashboardWatcher } from 'app/features/live/dashboard/dashboardWatcher'; import { getPageNavFromSlug, getRootContentNavModel } from 'app/features/storage/StorageFolderPage'; -import { GrafanaQueryType } from 'app/plugins/datasource/grafana/types'; import { DashboardRoutes, KioskMode, StoreState } from 'app/types'; import { PanelEditEnteredEvent, PanelEditExitedEvent } from 'app/types/events'; @@ -113,70 +99,6 @@ export class UnthemedDashboardPage extends PureComponent { private forceRouteReloadCounter = 0; state: State = this.getCleanState(); - onFileDrop = (acceptedFiles: File[], fileRejections: FileRejection[], event: DropEvent) => { - const grafanaDS = { - type: 'grafana', - uid: 'grafana', - }; - DFImport.filesToDataframes(acceptedFiles).subscribe((next) => { - const snapshot: DataFrameJSON[] = []; - next.dataFrames.forEach((df) => { - const dataframeJson = dataFrameToJSON(df); - snapshot.push(dataframeJson); - }); - this.props.dashboard?.addPanel({ - type: 'table', - gridPos: { x: 0, y: 0, w: 12, h: 8 }, - title: next.file.name, - datasource: grafanaDS, - targets: [ - { - queryType: GrafanaQueryType.Snapshot, - snapshot, - file: { name: next.file.name, size: next.file.size }, - datasource: grafanaDS, - }, - ], - }); - }); - - fileRejections.forEach((fileRejection) => { - const errors = fileRejection.errors.map((error) => { - switch (error.code) { - case ErrorCode.FileTooLarge: - const formattedSize = getValueFormat('decbytes')(DFImport.maxFileSize); - return `File size must be less than ${formattedValueToString(formattedSize)}.`; - case ErrorCode.FileInvalidType: - return `File type must be one of the following types ${DFImport.formatFileTypes(DFImport.acceptedFiles)}.`; - default: - return error.message; - } - }); - this.props.notifyApp( - createErrorNotification( - `Failed to load ${fileRejection.file.name}`, - undefined, - undefined, -
    - {errors.map((err) => { - return
  • {err}
  • ; - })} -
- ) - ); - }); - - reportInteraction('dashboards_dropped_files', { - number_of_files: fileRejections.length + acceptedFiles.length, - accepted_files: acceptedFiles.map((a) => { - return { type: a.type, size: a.size }; - }), - rejected_files: fileRejections.map((r) => { - return { type: r.file.type, size: r.file.size }; - }), - }); - }; - getCleanState(): State { return { editPanel: null, @@ -463,41 +385,13 @@ export class UnthemedDashboardPage extends PureComponent { )} - {config.featureToggles.editPanelCSVDragAndDrop ? ( - - {({ getRootProps, isDragActive }) => { - const styles = getStyles(this.props.theme, isDragActive); - return ( -
-
-
- -

Create tables from spreadsheets

-
-
- -
- ); - }} -
- ) : ( - - )} + + {inspectPanel && } {editPanel && ( @@ -585,32 +479,6 @@ function updateStatePageNavFromProps(props: Props, state: State): State { }; } -function getStyles(theme: GrafanaTheme2, isDragActive: boolean) { - return { - dropZone: css` - height: 100%; - `, - dropOverlay: css` - background-color: ${isDragActive ? theme.colors.action.hover : `inherit`}; - border: ${isDragActive ? `2px dashed ${theme.colors.border.medium}` : 0}; - position: absolute; - display: ${isDragActive ? 'flex' : 'none'}; - z-index: ${theme.zIndex.modal}; - top: 0px; - left: 0px; - height: 100%; - width: 100%; - align-items: center; - justify-content: center; - `, - dropHint: css` - align-items: center; - display: flex; - flex-direction: column; - `, - }; -} - export const DashboardPage = withTheme2(UnthemedDashboardPage); DashboardPage.displayName = 'DashboardPage'; export default connector(DashboardPage); From 6aab5e35d585c93b2685bca8e159a7fd8ffeb685 Mon Sep 17 00:00:00 2001 From: Vardan Torosyan Date: Tue, 9 May 2023 11:03:09 +0200 Subject: [PATCH 060/179] Docs: Clarify precedence of Grafana settings (#68020) --- .../configure-authentication/saml-ui/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/setup-grafana/configure-security/configure-authentication/saml-ui/index.md b/docs/sources/setup-grafana/configure-security/configure-authentication/saml-ui/index.md index 885638a9bc3..88849088cef 100644 --- a/docs/sources/setup-grafana/configure-security/configure-authentication/saml-ui/index.md +++ b/docs/sources/setup-grafana/configure-security/configure-authentication/saml-ui/index.md @@ -18,7 +18,7 @@ The Grafana SAML UI provides the following advantages over configuring SAML in t - It doesn't require Grafana to be restarted after a configuration update - Access to the SAML UI only requires access to authentication settings, so it can be used by users with limited access to Grafana's configuration -> **Note:** Configuration in the UI takes precedence over the configuration in the Grafana configuration file. SAML settings from the UI will override any SAML configuration set in the Grafana configuration file. +> **Note:** Any configuration changes made through the Grafana user interface (UI) will take precedence over settings specified in the Grafana configuration file or through environment variables. This means that if you modify any configuration settings in the UI, they will override any corresponding settings set via environment variables or defined in the configuration file. For more information on how Grafana determines the order of precedence for its settings, please refer to the [Settings update at runtime]({{< relref "../../configure-grafana/settings-updates-at-runtime/" >}}). > **Note:** Disabling the UI does not affect any configuration settings that were previously set up through the UI. Those settings will continue to function as intended even with the UI disabled. From b8093bd0d98d81e8365d0336da69e5d187414a61 Mon Sep 17 00:00:00 2001 From: Josh Hunt Date: Tue, 9 May 2023 09:44:14 +0000 Subject: [PATCH 061/179] Chore: Remove nth-child warning from dev mode (#67918) --- packages/grafana-ui/src/themes/GlobalStyles/page.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/packages/grafana-ui/src/themes/GlobalStyles/page.ts b/packages/grafana-ui/src/themes/GlobalStyles/page.ts index 1c301bc188d..4629736a8fd 100644 --- a/packages/grafana-ui/src/themes/GlobalStyles/page.ts +++ b/packages/grafana-ui/src/themes/GlobalStyles/page.ts @@ -88,12 +88,7 @@ export function getPageStyles(theme: GrafanaTheme2) { margin-bottom: ${theme.spacing(2)}; display: flex; align-items: flex-start; - - > a, - > button, - > div:nth-child(2) { - margin-left: ${theme.spacing(2)}; - } + gap: ${theme.spacing(2)}; } .page-action-bar--narrow { From 04df92ab47119abcdafea0642b24462b13cdacdf Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Tue, 9 May 2023 11:47:20 +0200 Subject: [PATCH 062/179] Docs: Update backend plugin forwarding of headers (#67913) Co-authored-by: Will Browne Co-authored-by: Joseph Perez <45749060+josmperez@users.noreply.github.com> --- .../plugins/add-authentication-for-data-source-plugins.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/sources/developers/plugins/add-authentication-for-data-source-plugins.md b/docs/sources/developers/plugins/add-authentication-for-data-source-plugins.md index 829220f4408..aac94865702 100644 --- a/docs/sources/developers/plugins/add-authentication-for-data-source-plugins.md +++ b/docs/sources/developers/plugins/add-authentication-for-data-source-plugins.md @@ -291,7 +291,7 @@ To allow Grafana to pass the access token to the plugin, update the data source When configured, Grafana can forward authorization HTTP headers such as `Authorization` or `X-ID-Token` to a backend data source. This information is available across the `QueryData`, `CallResource` and `CheckHealth` requests. -To get Grafana to forward the headers, create a HTTP client using the [Grafana Plugin SDK](https://pkg.go.dev/github.com/grafana/grafana-plugin-sdk-go/backend/httpclient). This package exposes request information which can be subsequently forwarded downstream and/or used directly within the plugin. +To get Grafana to forward the headers, create a HTTP client using the [Grafana plugin SDK for Go](https://pkg.go.dev/github.com/grafana/grafana-plugin-sdk-go/backend/httpclient) and set the `ForwardHTTPHeaders` option to `true` (by default, it's set to `false`). This package exposes request information which can be subsequently forwarded downstream and/or used directly within the plugin. ```go func NewDatasource(settings backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) { @@ -299,6 +299,9 @@ func NewDatasource(settings backend.DataSourceInstanceSettings) (instancemgmt.In if err != nil { return nil, fmt.Errorf("http client options: %w", err) } + + opts.ForwardHTTPHeaders = true + // Important to reuse the same client for each query, to avoid using all available connections on a host cl, err := httpclient.New(opts) if err != nil { From 8c6b5a431937be60eea91970d1c12b53bba8a716 Mon Sep 17 00:00:00 2001 From: Gabriel MABILLE Date: Tue, 9 May 2023 13:19:38 +0200 Subject: [PATCH 063/179] RBAC: Add a function to save external service roles (#66299) * AuthN: Save external services RBAC roles * Add missing test * Placing roles in the same group * Split function to gen role and assignment * add test case and comments * Ensure we check external service roles are assigned once only * Update pkg/services/accesscontrol/models_test.go Co-authored-by: Misi --------- Co-authored-by: Misi --- pkg/services/accesscontrol/accesscontrol.go | 2 + pkg/services/accesscontrol/acimpl/service.go | 29 ++- .../accesscontrol/acimpl/service_test.go | 85 ++++++++ pkg/services/accesscontrol/actest/fake.go | 8 + .../accesscontrol/database/database.go | 9 +- .../database/externalservices.go | 198 ++++++++++++++++++ .../database/externalservices_test.go | 191 +++++++++++++++++ pkg/services/accesscontrol/errors.go | 1 + pkg/services/accesscontrol/mock/mock.go | 11 + pkg/services/accesscontrol/models.go | 63 ++++-- pkg/services/accesscontrol/models_test.go | 80 +++++++ 11 files changed, 657 insertions(+), 20 deletions(-) create mode 100644 pkg/services/accesscontrol/database/externalservices.go create mode 100644 pkg/services/accesscontrol/database/externalservices_test.go create mode 100644 pkg/services/accesscontrol/models_test.go diff --git a/pkg/services/accesscontrol/accesscontrol.go b/pkg/services/accesscontrol/accesscontrol.go index 16ca6324ee0..41de06d0ac8 100644 --- a/pkg/services/accesscontrol/accesscontrol.go +++ b/pkg/services/accesscontrol/accesscontrol.go @@ -38,6 +38,8 @@ type Service interface { // DeclareFixedRoles allows the caller to declare, to the service, fixed roles and their // assignments to organization roles ("Viewer", "Editor", "Admin") or "Grafana Admin" DeclareFixedRoles(registrations ...RoleRegistration) error + // SaveExternalServiceRole creates or updates an external service's role and assigns it to a given service account id. + SaveExternalServiceRole(ctx context.Context, cmd SaveExternalServiceRoleCommand) error //IsDisabled returns if access control is enabled or not IsDisabled() bool } diff --git a/pkg/services/accesscontrol/acimpl/service.go b/pkg/services/accesscontrol/acimpl/service.go index 9c64d8a9257..e2bcfd3fc94 100644 --- a/pkg/services/accesscontrol/acimpl/service.go +++ b/pkg/services/accesscontrol/acimpl/service.go @@ -62,6 +62,7 @@ type store interface { SearchUsersPermissions(ctx context.Context, orgID int64, options accesscontrol.SearchOptions) (map[int64][]accesscontrol.Permission, error) GetUsersBasicRoles(ctx context.Context, userFilter []int64, orgID int64) (map[int64][]string, error) DeleteUserPermissions(ctx context.Context, orgID, userID int64) error + SaveExternalServiceRole(ctx context.Context, cmd accesscontrol.SaveExternalServiceRoleCommand) error } // Service is the service implementing role based access control. @@ -107,11 +108,11 @@ func (s *Service) getUserPermissions(ctx context.Context, user *user.SignedInUse } dbPermissions, err := s.store.GetUserPermissions(ctx, accesscontrol.GetUserPermissionsQuery{ - OrgID: user.OrgID, - UserID: user.UserID, - Roles: accesscontrol.GetOrgRoles(user), - TeamIDs: user.Teams, - RolePrefix: accesscontrol.ManagedRolePrefix, + OrgID: user.OrgID, + UserID: user.UserID, + Roles: accesscontrol.GetOrgRoles(user), + TeamIDs: user.Teams, + RolePrefixes: []string{accesscontrol.ManagedRolePrefix, accesscontrol.ExternalServiceRolePrefix}, }) if err != nil { return nil, err @@ -414,3 +415,21 @@ func PermissionMatchesSearchOptions(permission accesscontrol.Permission, searchO } return strings.HasPrefix(permission.Action, searchOptions.ActionPrefix) } + +func (s *Service) SaveExternalServiceRole(ctx context.Context, cmd accesscontrol.SaveExternalServiceRoleCommand) error { + // If accesscontrol is disabled no need to save the external service role + if accesscontrol.IsDisabled(s.cfg) { + return nil + } + + if !s.features.IsEnabled(featuremgmt.FlagExternalServiceAuth) { + s.log.Debug("registering external service role is behind a feature flag, enable it to use this feature.") + return nil + } + + if err := cmd.Validate(); err != nil { + return err + } + + return s.store.SaveExternalServiceRole(ctx, cmd) +} diff --git a/pkg/services/accesscontrol/acimpl/service_test.go b/pkg/services/accesscontrol/acimpl/service_test.go index 06ab9b18d3d..f4fce35c319 100644 --- a/pkg/services/accesscontrol/acimpl/service_test.go +++ b/pkg/services/accesscontrol/acimpl/service_test.go @@ -740,3 +740,88 @@ func TestPermissionCacheKey(t *testing.T) { }) } } + +func TestService_SaveExternalServiceRole(t *testing.T) { + type run struct { + cmd accesscontrol.SaveExternalServiceRoleCommand + wantErr bool + } + tests := []struct { + name string + runs []run + }{ + { + name: "can create a role", + runs: []run{ + { + cmd: accesscontrol.SaveExternalServiceRoleCommand{ + OrgID: 2, + ServiceAccountID: 2, + ExternalServiceID: "App 1", + Permissions: []accesscontrol.Permission{{Action: "users:read", Scope: "users:id:1"}}, + }, + wantErr: false, + }, + }, + }, + { + name: "can update a role", + runs: []run{ + { + cmd: accesscontrol.SaveExternalServiceRoleCommand{ + Global: true, + ServiceAccountID: 2, + ExternalServiceID: "App 1", + Permissions: []accesscontrol.Permission{{Action: "users:read", Scope: "users:id:1"}}, + }, + wantErr: false, + }, + { + cmd: accesscontrol.SaveExternalServiceRoleCommand{ + Global: true, + ServiceAccountID: 2, + ExternalServiceID: "App 1", + Permissions: []accesscontrol.Permission{ + {Action: "users:write", Scope: "users:id:1"}, + {Action: "users:write", Scope: "users:id:2"}, + }, + }, + wantErr: false, + }, + }, + }, + { + name: "test command validity - no service account ID", + runs: []run{ + { + cmd: accesscontrol.SaveExternalServiceRoleCommand{ + OrgID: 2, + ExternalServiceID: "App 1", + Permissions: []accesscontrol.Permission{{Action: "users:read", Scope: "users:id:1"}}, + }, + wantErr: true, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx := context.Background() + ac := setupTestEnv(t) + ac.features = featuremgmt.WithFeatures(featuremgmt.FlagExternalServiceAuth) + for _, r := range tt.runs { + err := ac.SaveExternalServiceRole(ctx, r.cmd) + if r.wantErr { + require.Error(t, err) + continue + } + require.NoError(t, err) + + // Check that the permissions and assignment are stored correctly + perms, errGetPerms := ac.getUserPermissions(ctx, &user.SignedInUser{OrgID: r.cmd.OrgID, UserID: 2}, accesscontrol.Options{}) + require.NoError(t, errGetPerms) + assert.ElementsMatch(t, r.cmd.Permissions, perms) + } + }) + } +} diff --git a/pkg/services/accesscontrol/actest/fake.go b/pkg/services/accesscontrol/actest/fake.go index c394907ff07..f25f001a398 100644 --- a/pkg/services/accesscontrol/actest/fake.go +++ b/pkg/services/accesscontrol/actest/fake.go @@ -53,6 +53,10 @@ func (f FakeService) IsDisabled() bool { return f.ExpectedDisabled } +func (f FakeService) SaveExternalServiceRole(ctx context.Context, cmd accesscontrol.SaveExternalServiceRoleCommand) error { + return f.ExpectedErr +} + var _ accesscontrol.AccessControl = new(FakeAccessControl) type FakeAccessControl struct { @@ -95,6 +99,10 @@ func (f FakeStore) DeleteUserPermissions(ctx context.Context, orgID, userID int6 return f.ExpectedErr } +func (f FakeStore) SaveExternalServiceRole(ctx context.Context, cmd accesscontrol.SaveExternalServiceRoleCommand) error { + return f.ExpectedErr +} + var _ accesscontrol.PermissionsService = new(FakePermissionsService) type FakePermissionsService struct { diff --git a/pkg/services/accesscontrol/database/database.go b/pkg/services/accesscontrol/database/database.go index 2ecdecff5c9..612fa2651c6 100644 --- a/pkg/services/accesscontrol/database/database.go +++ b/pkg/services/accesscontrol/database/database.go @@ -35,9 +35,12 @@ func (s *AccessControlStore) GetUserPermissions(ctx context.Context, query acces INNER JOIN role ON role.id = permission.role_id ` + filter - if query.RolePrefix != "" { - q += " WHERE role.name LIKE ?" - params = append(params, query.RolePrefix+"%") + if len(query.RolePrefixes) > 0 { + q += " WHERE ( " + strings.Repeat("role.name LIKE ? OR ", len(query.RolePrefixes)) + q = q[:len(q)-4] + " )" // remove last " OR " + for i := range query.RolePrefixes { + params = append(params, query.RolePrefixes[i]+"%") + } } if err := sess.SQL(q, params...).Find(&result); err != nil { diff --git a/pkg/services/accesscontrol/database/externalservices.go b/pkg/services/accesscontrol/database/externalservices.go new file mode 100644 index 00000000000..bf724cd1572 --- /dev/null +++ b/pkg/services/accesscontrol/database/externalservices.go @@ -0,0 +1,198 @@ +package database + +import ( + "context" + "errors" + "fmt" + "time" + + "github.com/grafana/grafana/pkg/infra/db" + "github.com/grafana/grafana/pkg/services/accesscontrol" +) + +func (s *AccessControlStore) SaveExternalServiceRole(ctx context.Context, cmd accesscontrol.SaveExternalServiceRoleCommand) error { + role := genExternalServiceRole(cmd) + assignment := genExternalServiceAssignment(cmd) + + return s.sql.WithDbSession(ctx, func(sess *db.Session) error { + // Create or update the role + existingRole, errSaveRole := s.saveRole(ctx, sess, &role) + if errSaveRole != nil { + return errSaveRole + } + // Assign role to service account + // We update the assignment before the permissions to avoid an edge case. + // If the role is assigned to another service account (which can only happen if the services have the same ID) + // and permissions are updated before the assignment, this would result in the other service account acquiring + // a different set of permissions. + assignment.RoleID = existingRole.ID + errSaveAssign := s.saveUserAssignment(ctx, sess, assignment) + if errSaveAssign != nil { + return errSaveAssign + } + // Update permissions + return s.savePermissions(ctx, sess, existingRole.ID, cmd.Permissions) + }) +} + +func genExternalServiceRole(cmd accesscontrol.SaveExternalServiceRoleCommand) accesscontrol.Role { + role := accesscontrol.Role{ + OrgID: cmd.OrgID, + Version: 1, + Name: fmt.Sprintf("%s%s:permissions", accesscontrol.ExternalServiceRolePrefix, cmd.ExternalServiceID), + UID: fmt.Sprintf("%s%s_permissions", accesscontrol.ExternalServiceRoleUIDPrefix, cmd.ExternalServiceID), + DisplayName: fmt.Sprintf("External Service %s Permissions", cmd.ExternalServiceID), + Description: fmt.Sprintf("External Service %s permissions", cmd.ExternalServiceID), + Group: "External Service", + Hidden: true, + Created: time.Now(), + Updated: time.Now(), + } + if cmd.Global { + role.OrgID = accesscontrol.GlobalOrgID + } + return role +} + +func genExternalServiceAssignment(cmd accesscontrol.SaveExternalServiceRoleCommand) accesscontrol.UserRole { + assignment := accesscontrol.UserRole{ + OrgID: cmd.OrgID, + UserID: cmd.ServiceAccountID, + Created: time.Now(), + } + if cmd.Global { + assignment.OrgID = accesscontrol.GlobalOrgID + } + return assignment +} + +func getRoleByUID(ctx context.Context, sess *db.Session, uid string) (*accesscontrol.Role, error) { + var role accesscontrol.Role + has, err := sess.Where("uid = ?", uid).Get(&role) + if err != nil { + return nil, err + } + if !has { + return nil, accesscontrol.ErrRoleNotFound + } + return &role, nil +} + +func getRoleAssignments(ctx context.Context, sess *db.Session, roleID int64) ([]accesscontrol.UserRole, error) { + var assignements []accesscontrol.UserRole + if err := sess.Where("role_id = ?", roleID).Find(&assignements); err != nil { + return nil, err + } + return assignements, nil +} + +func getRolePermissions(ctx context.Context, sess *db.Session, id int64) ([]accesscontrol.Permission, error) { + var permissions []accesscontrol.Permission + if err := sess.Where("role_id = ?", id).Find(&permissions); err != nil { + return nil, err + } + return permissions, nil +} + +func permissionDiff(previous, new []accesscontrol.Permission) (added, removed []accesscontrol.Permission) { + type key struct{ Action, Scope string } + prevMap := map[key]int64{} + for i := range previous { + prevMap[key{previous[i].Action, previous[i].Scope}] = previous[i].ID + } + newMap := map[key]int64{} + for i := range new { + newMap[key{new[i].Action, new[i].Scope}] = 0 + } + for i := range new { + key := key{new[i].Action, new[i].Scope} + if _, already := prevMap[key]; !already { + added = append(added, new[i]) + } else { + delete(prevMap, key) + } + } + + for p, id := range prevMap { + removed = append(removed, accesscontrol.Permission{ID: id, Action: p.Action, Scope: p.Scope}) + } + + return added, removed +} + +func (*AccessControlStore) saveRole(ctx context.Context, sess *db.Session, role *accesscontrol.Role) (*accesscontrol.Role, error) { + existingRole, err := getRoleByUID(ctx, sess, role.UID) + if err != nil && !errors.Is(err, accesscontrol.ErrRoleNotFound) { + return nil, err + } + + if existingRole == nil { + if _, err := sess.Insert(role); err != nil { + return nil, err + } + } else { + role.ID = existingRole.ID + role.Created = existingRole.Created + if _, err := sess.Where("id = ?", existingRole.ID).MustCols("org_id").Update(role); err != nil { + return nil, err + } + } + return getRoleByUID(ctx, sess, role.UID) +} + +func (*AccessControlStore) savePermissions(ctx context.Context, sess *db.Session, roleID int64, permissions []accesscontrol.Permission) error { + now := time.Now() + storedPermissions, err := getRolePermissions(ctx, sess, roleID) + if err != nil { + return err + } + added, removed := permissionDiff(storedPermissions, permissions) + if len(added) > 0 { + for i := range added { + added[i].RoleID = roleID + added[i].Created = now + added[i].Updated = now + } + if _, err := sess.Insert(&added); err != nil { + return err + } + } + if len(removed) > 0 { + ids := make([]int64, len(removed)) + for i := range removed { + ids[i] = removed[i].ID + } + count, err := sess.In("id", ids).Delete(&accesscontrol.Permission{}) + if err != nil { + return err + } + if count != int64(len(removed)) { + return errors.New("failed to delete permissions that have been removed from role") + } + } + return nil +} + +func (*AccessControlStore) saveUserAssignment(ctx context.Context, sess *db.Session, assignment accesscontrol.UserRole) error { + // alreadyAssigned checks if the assignment already exists without accounting for the organization + assignments, errGetAssigns := getRoleAssignments(ctx, sess, assignment.RoleID) + if errGetAssigns != nil { + return errGetAssigns + } + + if len(assignments) == 0 { + if _, errInsert := sess.Insert(&assignment); errInsert != nil { + return errInsert + } + return nil + } + + // Ensure the role was assigned only to this service account + if len(assignments) > 1 || assignments[0].UserID != assignment.UserID { + return errors.New("external service role assigned to another user or service account") + } + + // Ensure the assignment is in the correct organization + _, errUpdate := sess.Where("role_id = ? AND user_id = ?", assignment.RoleID, assignment.UserID).MustCols("org_id").Update(&assignment) + return errUpdate +} diff --git a/pkg/services/accesscontrol/database/externalservices_test.go b/pkg/services/accesscontrol/database/externalservices_test.go new file mode 100644 index 00000000000..fbf926ea784 --- /dev/null +++ b/pkg/services/accesscontrol/database/externalservices_test.go @@ -0,0 +1,191 @@ +package database + +import ( + "context" + "fmt" + "testing" + + "github.com/grafana/grafana/pkg/infra/db" + "github.com/grafana/grafana/pkg/services/accesscontrol" + "github.com/stretchr/testify/require" +) + +func TestAccessControlStore_SaveExternalServiceRole(t *testing.T) { + type run struct { + cmd accesscontrol.SaveExternalServiceRoleCommand + wantErr bool + } + tests := []struct { + name string + runs []run + }{ + { + name: "create app role", + runs: []run{ + { + cmd: accesscontrol.SaveExternalServiceRoleCommand{ + ExternalServiceID: "app1", + Global: true, + ServiceAccountID: 1, + Permissions: []accesscontrol.Permission{ + {Action: "users:read", Scope: "users:id:1"}, + {Action: "users:read", Scope: "users:id:2"}, + }, + }, + wantErr: false, + }, + }, + }, + { + name: "update app role", + runs: []run{ + { + cmd: accesscontrol.SaveExternalServiceRoleCommand{ + ExternalServiceID: "app1", + Global: true, + ServiceAccountID: 1, + Permissions: []accesscontrol.Permission{ + {Action: "users:read", Scope: "users:id:1"}, + {Action: "users:read", Scope: "users:id:2"}, + }, + }, + }, + { + cmd: accesscontrol.SaveExternalServiceRoleCommand{ + ExternalServiceID: "app1", + Global: true, + ServiceAccountID: 1, + Permissions: []accesscontrol.Permission{ + {Action: "users:write", Scope: "users:id:1"}, + {Action: "users:write", Scope: "users:id:2"}, + }, + }, + }, + }, + }, + { + name: "allow switching role from local to global and back", + runs: []run{ + { + cmd: accesscontrol.SaveExternalServiceRoleCommand{ + ExternalServiceID: "app1", + OrgID: 1, + ServiceAccountID: 1, + Permissions: []accesscontrol.Permission{ + {Action: "users:read", Scope: "users:id:1"}, + {Action: "users:read", Scope: "users:id:2"}, + }, + }, + }, + { + cmd: accesscontrol.SaveExternalServiceRoleCommand{ + ExternalServiceID: "app1", + Global: true, + ServiceAccountID: 1, + Permissions: []accesscontrol.Permission{ + {Action: "users:read", Scope: "users:id:1"}, + {Action: "users:read", Scope: "users:id:2"}, + }, + }, + }, + { + cmd: accesscontrol.SaveExternalServiceRoleCommand{ + ExternalServiceID: "app1", + OrgID: 1, + ServiceAccountID: 1, + Permissions: []accesscontrol.Permission{ + {Action: "users:read", Scope: "users:id:1"}, + {Action: "users:read", Scope: "users:id:2"}, + }, + }, + }, + }, + }, + { + name: "edge case - remove all permissions", + runs: []run{ + { + cmd: accesscontrol.SaveExternalServiceRoleCommand{ + ExternalServiceID: "app1", + Global: true, + ServiceAccountID: 1, + Permissions: []accesscontrol.Permission{ + {Action: "users:read", Scope: "users:id:1"}, + {Action: "users:read", Scope: "users:id:2"}, + }, + }, + }, + { + cmd: accesscontrol.SaveExternalServiceRoleCommand{ + ExternalServiceID: "app1", + Global: true, + ServiceAccountID: 1, + Permissions: []accesscontrol.Permission{}, + }, + }, + }, + }, + { + name: "edge case - reassign to another service account", + runs: []run{ + { + cmd: accesscontrol.SaveExternalServiceRoleCommand{ + ExternalServiceID: "app1", + Global: true, + ServiceAccountID: 1, + }, + }, + { + cmd: accesscontrol.SaveExternalServiceRoleCommand{ + ExternalServiceID: "app1", + Global: true, + ServiceAccountID: 2, + }, + wantErr: true, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx := context.Background() + s := &AccessControlStore{ + sql: db.InitTestDB(t), + } + + for i := range tt.runs { + err := s.SaveExternalServiceRole(ctx, tt.runs[i].cmd) + if tt.runs[i].wantErr { + require.Error(t, err) + continue + } + require.NoError(t, err) + + errDBSession := s.sql.WithDbSession(ctx, func(sess *db.Session) error { + storedRole, err := getRoleByUID(ctx, sess, fmt.Sprintf("externalservice_%s_permissions", tt.runs[i].cmd.ExternalServiceID)) + require.NoError(t, err) + require.NotNil(t, storedRole) + require.Equal(t, tt.runs[i].cmd.Global, storedRole.Global(), "Incorrect global state of the role") + require.Equal(t, tt.runs[i].cmd.OrgID, storedRole.OrgID, "Incorrect OrgID of the role") + + storedPerm, err := getRolePermissions(ctx, sess, storedRole.ID) + require.NoError(t, err) + for i := range storedPerm { + storedPerm[i] = accesscontrol.Permission{Action: storedPerm[i].Action, Scope: storedPerm[i].Scope} + } + require.ElementsMatch(t, tt.runs[i].cmd.Permissions, storedPerm) + + var assignment accesscontrol.UserRole + has, err := sess.Where("role_id = ? AND user_id = ?", storedRole.ID, tt.runs[i].cmd.ServiceAccountID).Get(&assignment) + require.NoError(t, err) + require.True(t, has) + require.Equal(t, tt.runs[i].cmd.Global, assignment.OrgID == accesscontrol.GlobalOrgID, "Incorrect global state of the assignment") + require.Equal(t, tt.runs[i].cmd.OrgID, assignment.OrgID, "Incorrect OrgID for the role assignment") + + return nil + }) + require.NoError(t, errDBSession) + } + }) + } +} diff --git a/pkg/services/accesscontrol/errors.go b/pkg/services/accesscontrol/errors.go index 27047abb596..5616b8583ab 100644 --- a/pkg/services/accesscontrol/errors.go +++ b/pkg/services/accesscontrol/errors.go @@ -11,6 +11,7 @@ var ( ErrInvalidScope = errors.New("invalid scope") ErrResolverNotFound = errors.New("no resolver found") ErrPluginIDRequired = errors.New("plugin ID is required") + ErrRoleNotFound = errors.New("role not found") ) type ErrorInvalidRole struct{} diff --git a/pkg/services/accesscontrol/mock/mock.go b/pkg/services/accesscontrol/mock/mock.go index 1ae451c0069..4b7bf250ccb 100644 --- a/pkg/services/accesscontrol/mock/mock.go +++ b/pkg/services/accesscontrol/mock/mock.go @@ -30,6 +30,7 @@ type Calls struct { DeleteUserPermissions []interface{} SearchUsersPermissions []interface{} SearchUserPermissions []interface{} + SaveExternalServiceRole []interface{} } type Mock struct { @@ -56,6 +57,7 @@ type Mock struct { DeleteUserPermissionsFunc func(context.Context, int64) error SearchUsersPermissionsFunc func(context.Context, *user.SignedInUser, int64, accesscontrol.SearchOptions) (map[int64][]accesscontrol.Permission, error) SearchUserPermissionsFunc func(ctx context.Context, orgID int64, searchOptions accesscontrol.SearchOptions) ([]accesscontrol.Permission, error) + SaveExternalServiceRoleFunc func(ctx context.Context, cmd accesscontrol.SaveExternalServiceRoleCommand) error scopeResolvers accesscontrol.Resolvers } @@ -235,3 +237,12 @@ func (m *Mock) SearchUserPermissions(ctx context.Context, orgID int64, searchOpt } return nil, nil } + +func (m *Mock) SaveExternalServiceRole(ctx context.Context, cmd accesscontrol.SaveExternalServiceRoleCommand) error { + m.Calls.SaveExternalServiceRole = append(m.Calls.SaveExternalServiceRole, []interface{}{ctx, cmd}) + // Use override if provided + if m.SaveExternalServiceRoleFunc != nil { + return m.SaveExternalServiceRoleFunc(ctx, cmd) + } + return nil +} diff --git a/pkg/services/accesscontrol/models.go b/pkg/services/accesscontrol/models.go index ea688c67635..aadd63aea7f 100644 --- a/pkg/services/accesscontrol/models.go +++ b/pkg/services/accesscontrol/models.go @@ -2,10 +2,12 @@ package accesscontrol import ( "encoding/json" + "errors" "fmt" "strings" "time" + "github.com/grafana/grafana/pkg/infra/slugify" "github.com/grafana/grafana/pkg/services/annotations" "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/util/errutil" @@ -129,6 +131,10 @@ func (r *RoleDTO) IsBasic() bool { return strings.HasPrefix(r.Name, BasicRolePrefix) || strings.HasPrefix(r.UID, BasicRoleUIDPrefix) } +func (r *RoleDTO) IsExternalService() bool { + return strings.HasPrefix(r.Name, ExternalServiceRolePrefix) || strings.HasPrefix(r.UID, ExternalServiceRoleUIDPrefix) +} + func (r RoleDTO) MarshalJSON() ([]byte, error) { type Alias RoleDTO @@ -188,11 +194,11 @@ func (p Permission) OSSPermission() Permission { } type GetUserPermissionsQuery struct { - OrgID int64 - UserID int64 - Roles []string - TeamIDs []int64 - RolePrefix string + OrgID int64 + UserID int64 + Roles []string + TeamIDs []int64 + RolePrefixes []string } // ResourcePermission is structure that holds all actions that either a team / user / builtin-role @@ -245,14 +251,47 @@ type SetResourcePermissionCommand struct { Permission string `json:"permission"` } +type SaveExternalServiceRoleCommand struct { + OrgID int64 + Global bool + ExternalServiceID string + ServiceAccountID int64 + Permissions []Permission +} + +func (cmd *SaveExternalServiceRoleCommand) Validate() error { + if cmd.ExternalServiceID == "" { + return errors.New("external service id not specified") + } + + // slugify the external service id ID for the role to have correct name and uid + cmd.ExternalServiceID = slugify.Slugify(cmd.ExternalServiceID) + + if (cmd.OrgID == GlobalOrgID) != cmd.Global { + return fmt.Errorf("invalid org id %d for global role %t", cmd.OrgID, cmd.Global) + } + + if cmd.Permissions == nil || len(cmd.Permissions) == 0 { + return errors.New("no permissions provided") + } + + if cmd.ServiceAccountID <= 0 { + return fmt.Errorf("invalid service account id %d", cmd.ServiceAccountID) + } + + return nil +} + const ( - GlobalOrgID = 0 - FixedRolePrefix = "fixed:" - ManagedRolePrefix = "managed:" - BasicRolePrefix = "basic:" - PluginRolePrefix = "plugins:" - BasicRoleUIDPrefix = "basic_" - RoleGrafanaAdmin = "Grafana Admin" + GlobalOrgID = 0 + FixedRolePrefix = "fixed:" + ManagedRolePrefix = "managed:" + BasicRolePrefix = "basic:" + PluginRolePrefix = "plugins:" + ExternalServiceRolePrefix = "externalservice:" + BasicRoleUIDPrefix = "basic_" + ExternalServiceRoleUIDPrefix = "externalservice_" + RoleGrafanaAdmin = "Grafana Admin" GeneralFolderUID = "general" diff --git a/pkg/services/accesscontrol/models_test.go b/pkg/services/accesscontrol/models_test.go new file mode 100644 index 00000000000..a8503442f38 --- /dev/null +++ b/pkg/services/accesscontrol/models_test.go @@ -0,0 +1,80 @@ +package accesscontrol + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestSaveExternalServiceRoleCommand_Validate(t *testing.T) { + tests := []struct { + name string + cmd SaveExternalServiceRoleCommand + wantID string + wantErr bool + }{ + { + name: "invalid global statement", + cmd: SaveExternalServiceRoleCommand{ + OrgID: 1, + Global: true, + ExternalServiceID: "app 1", + ServiceAccountID: 2, + Permissions: []Permission{{Action: "users:read", Scope: "users:id:1"}}, + }, + wantErr: true, + }, + { + name: "invalid no permissions", + cmd: SaveExternalServiceRoleCommand{ + OrgID: 1, + ExternalServiceID: "app 1", + ServiceAccountID: 2, + Permissions: []Permission{}, + }, + wantErr: true, + }, + { + name: "invalid service account id", + cmd: SaveExternalServiceRoleCommand{ + OrgID: 1, + ExternalServiceID: "app 1", + ServiceAccountID: -1, + Permissions: []Permission{{Action: "users:read", Scope: "users:id:1"}}, + }, + wantErr: true, + }, + { + name: "invalid no Ext Service ID", + cmd: SaveExternalServiceRoleCommand{ + OrgID: 1, + ServiceAccountID: 2, + Permissions: []Permission{{Action: "users:read", Scope: "users:id:1"}}, + }, + wantErr: true, + }, + { + name: "slugify the external service ID correctly", + cmd: SaveExternalServiceRoleCommand{ + ExternalServiceID: "ThisIs a Very Strange ___ App Name?", + Global: true, + ServiceAccountID: 2, + Permissions: []Permission{{Action: "users:read", Scope: "users:id:1"}}, + }, + wantErr: false, + wantID: "thisis-a-very-strange-app-name", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := tt.cmd.Validate() + if tt.wantErr { + require.Error(t, err) + return + } + require.NoError(t, err) + + require.Equal(t, tt.wantID, tt.cmd.ExternalServiceID) + }) + } +} From 6137e45fe156a969972ccd0719f7464fc81b7db3 Mon Sep 17 00:00:00 2001 From: Polina Boneva <13227501+polibb@users.noreply.github.com> Date: Tue, 9 May 2023 15:31:30 +0300 Subject: [PATCH 064/179] Template Variables: Input field in dashboard does not send network requests for query var with ${__searchFilter} (#67839) Co-authored-by: Ivan Ortega Alba --- .../variables/pickers/OptionsPicker/actions.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/public/app/features/variables/pickers/OptionsPicker/actions.ts b/public/app/features/variables/pickers/OptionsPicker/actions.ts index a4c5b3bd134..00e27452a66 100644 --- a/public/app/features/variables/pickers/OptionsPicker/actions.ts +++ b/public/app/features/variables/pickers/OptionsPicker/actions.ts @@ -64,20 +64,24 @@ export const filterOrSearchOptions = ( const { id, queryValue } = getVariablesState(rootStateKey, getState()).optionsPicker; const identifier: KeyedVariableIdentifier = { id, rootStateKey: rootStateKey, type: 'query' }; const variable = getVariable(identifier, getState()); - if (!hasOptions(variable)) { + + if (!('options' in variable)) { return; } - const { query, options } = variable; dispatch(toKeyedAction(rootStateKey, updateSearchQuery(searchQuery))); if (trim(queryValue) === trim(searchQuery)) { return; } - if (containsSearchFilter(query)) { + const { query, options } = variable; + + const queryTarget = typeof query === 'string' ? query : query.target; + if (containsSearchFilter(queryTarget)) { return searchForOptionsWithDebounce(dispatch, getState, searchQuery, rootStateKey); } + return dispatch(toKeyedAction(rootStateKey, updateOptionsAndFilter(options))); }; }; From ea1c1b9fb89775523b3f2292e713b2d8d86542ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 9 May 2023 14:33:41 +0200 Subject: [PATCH 065/179] DashboardGrid: Fixes moving panels on first load (#67953) * DashboardGrid: Fixes moving panels on first load * Minor tweak * Use theme breakpoint and add media query --- .../dashboard/dashgrid/DashboardEmpty.tsx | 5 ++ .../dashboard/dashgrid/DashboardGrid.tsx | 90 +++++++------------ public/sass/components/_dashboard_grid.scss | 6 +- 3 files changed, 39 insertions(+), 62 deletions(-) diff --git a/public/app/features/dashboard/dashgrid/DashboardEmpty.tsx b/public/app/features/dashboard/dashgrid/DashboardEmpty.tsx index 47c5ec2146a..bf0e1df1825 100644 --- a/public/app/features/dashboard/dashgrid/DashboardEmpty.tsx +++ b/public/app/features/dashboard/dashgrid/DashboardEmpty.tsx @@ -107,6 +107,11 @@ function getStyles(theme: GrafanaTheme2) { flexDirection: 'column', maxWidth: '890px', gap: theme.spacing.gridSize * 4, + paddingTop: theme.spacing(2), + + [theme.breakpoints.up('sm')]: { + paddingTop: theme.spacing(12), + }, }), containerBox: css({ label: 'container-box', diff --git a/public/app/features/dashboard/dashgrid/DashboardGrid.tsx b/public/app/features/dashboard/dashgrid/DashboardGrid.tsx index 532f1e3b824..90e2a3e33d4 100644 --- a/public/app/features/dashboard/dashgrid/DashboardGrid.tsx +++ b/public/app/features/dashboard/dashgrid/DashboardGrid.tsx @@ -24,12 +24,7 @@ export interface Props { viewPanel: PanelModel | null; hidePanelMenus?: boolean; } - -export interface State { - isLayoutInitialized: boolean; -} - -export class DashboardGrid extends PureComponent { +export class DashboardGrid extends PureComponent { private panelMap: { [key: string]: PanelModel } = {}; private eventSubs = new Subscription(); private windowHeight = 1200; @@ -37,13 +32,10 @@ export class DashboardGrid extends PureComponent { private gridWidth = 0; /** Used to keep track of mobile panel layout position */ private lastPanelBottom = 0; + private isLayoutInitialized = false; constructor(props: Props) { super(props); - - this.state = { - isLayoutInitialized: false, - }; } componentDidMount() { @@ -93,15 +85,14 @@ export class DashboardGrid extends PureComponent { onLayoutChange = (newLayout: ReactGridLayout.Layout[]) => { for (const newPos of newLayout) { - this.panelMap[newPos.i!].updateGridPos(newPos, this.state.isLayoutInitialized); + this.panelMap[newPos.i!].updateGridPos(newPos, this.isLayoutInitialized); + } + + if (this.isLayoutInitialized) { + this.isLayoutInitialized = true; } this.props.dashboard.sortPanelsByGridPos(); - - // This is called on grid mount as it can correct invalid initial grid positions - if (!this.state.isLayoutInitialized) { - this.setState({ isLayoutInitialized: true }); - } }; triggerForceUpdate = () => { @@ -210,9 +201,24 @@ export class DashboardGrid extends PureComponent { ); } + /** + * Without this hack the move animations are triggered on initial load and all panels fly into position. + * This can be quite distracting and make the dashboard appear to less snappy. + */ + onGetWrapperDivRef = (ref: HTMLDivElement | null) => { + if (ref) { + setTimeout(() => { + ref.classList.add('react-grid-layout--enable-move-animations'); + }, 50); + } + }; + render() { - const { dashboard, isEditable } = this.props; - const hasPanels = dashboard.panels && dashboard.panels.length > 0; + const { isEditable, dashboard } = this.props; + + if (config.featureToggles.emptyDashboardPage && dashboard.panels.length === 0) { + return ; + } /** * We have a parent with "flex: 1 1 0" we need to reset it to "flex: 1 1 auto" to have the AutoSizer @@ -227,59 +233,23 @@ export class DashboardGrid extends PureComponent { return null; } - const draggable = width <= 769 ? false : isEditable; + // Disable draggable if mobile device, solving an issue with unintentionally + // moving panels. https://github.com/grafana/grafana/issues/18497 + const draggable = width <= config.theme2.breakpoints.values.md ? false : isEditable; - /* - Disable draggable if mobile device, solving an issue with unintentionally - moving panels. https://github.com/grafana/grafana/issues/18497 - theme.breakpoints.md = 769 - */ - return config.featureToggles.emptyDashboardPage ? ( - hasPanels ? ( - /** - * The children is using a width of 100% so we need to guarantee that it is wrapped - * in an element that has the calculated size given by the AutoSizer. The AutoSizer - * has a width of 0 and will let its content overflow its div. - */ -
- - {this.renderPanels(width, draggable)} - -
- ) : ( -
- -
- ) - ) : ( + return ( /** * The children is using a width of 100% so we need to guarantee that it is wrapped * in an element that has the calculated size given by the AutoSizer. The AutoSizer * has a width of 0 and will let its content overflow its div. */ -
+
Date: Tue, 9 May 2023 14:51:02 +0200 Subject: [PATCH 066/179] AngularDeprecation: Show warnings in panel edit for angular panels (#67891) * AngularDeprecation: Show warnings in panel edit for angular panels * update wording * Update public/app/features/dashboard/components/PanelEditor/AngularPanelPluginWarning.tsx Co-authored-by: Dominik Prokop * Update public/app/features/dashboard/components/PanelEditor/AngularPanelPluginWarning.tsx --------- Co-authored-by: Dominik Prokop --- .../PanelEditor/AngularPanelPluginWarning.tsx | 48 +++++++++++++++++++ .../PanelEditor/OptionsPaneOptions.tsx | 2 + .../components/PanelEditor/PanelEditor.tsx | 5 ++ 3 files changed, 55 insertions(+) create mode 100644 public/app/features/dashboard/components/PanelEditor/AngularPanelPluginWarning.tsx diff --git a/public/app/features/dashboard/components/PanelEditor/AngularPanelPluginWarning.tsx b/public/app/features/dashboard/components/PanelEditor/AngularPanelPluginWarning.tsx new file mode 100644 index 00000000000..7351b628f19 --- /dev/null +++ b/public/app/features/dashboard/components/PanelEditor/AngularPanelPluginWarning.tsx @@ -0,0 +1,48 @@ +import { css } from '@emotion/css'; +import React from 'react'; + +import { GrafanaTheme2, PanelPlugin } from '@grafana/data'; +import { Alert, useStyles2 } from '@grafana/ui'; + +export interface Props { + plugin: PanelPlugin; +} + +export function AngularPanelPluginWarning({ plugin }: Props) { + const styles = useStyles2(getStyles); + + return ( +
+ +
+

The selected panel plugin is using deprecated plugin APIs.

+ +
+
+
+ ); +} + +export function getStyles(theme: GrafanaTheme2) { + return { + wrapper: css({ + padding: theme.spacing(1), + }), + }; +} diff --git a/public/app/features/dashboard/components/PanelEditor/OptionsPaneOptions.tsx b/public/app/features/dashboard/components/PanelEditor/OptionsPaneOptions.tsx index 5c2eb9e7189..8fec5bc3feb 100644 --- a/public/app/features/dashboard/components/PanelEditor/OptionsPaneOptions.tsx +++ b/public/app/features/dashboard/components/PanelEditor/OptionsPaneOptions.tsx @@ -7,6 +7,7 @@ import { CustomScrollbar, FilterInput, RadioButtonGroup, useStyles2 } from '@gra import { isPanelModelLibraryPanel } from '../../../library-panels/guard'; import { AngularPanelOptions } from './AngularPanelOptions'; +import { AngularPanelPluginWarning } from './AngularPanelPluginWarning'; import { OptionsPaneCategory } from './OptionsPaneCategory'; import { OptionsPaneCategoryDescriptor } from './OptionsPaneCategoryDescriptor'; import { getFieldOverrideCategories } from './getFieldOverrideElements'; @@ -100,6 +101,7 @@ export const OptionsPaneOptions = (props: OptionPaneRenderProps) => { return (
+ {panel.isAngularPlugin() && }
diff --git a/public/app/features/dashboard/components/PanelEditor/PanelEditor.tsx b/public/app/features/dashboard/components/PanelEditor/PanelEditor.tsx index 772301165aa..4a5b75a58ff 100644 --- a/public/app/features/dashboard/components/PanelEditor/PanelEditor.tsx +++ b/public/app/features/dashboard/components/PanelEditor/PanelEditor.tsx @@ -538,6 +538,11 @@ export const getStyles = stylesFactory((theme: GrafanaTheme2, props: Props) => { justify-content: space-between; flex-wrap: wrap; `, + angularWarning: css` + display: flex; + height: theme.spacing(4); + align-items: center; + `, toolbarLeft: css` padding-left: ${theme.spacing(1)}; `, From 8ec5527479e25ae4a12ff7b5f83f987ab27c46d9 Mon Sep 17 00:00:00 2001 From: Juan Cabanas Date: Tue, 9 May 2023 09:52:29 -0300 Subject: [PATCH 067/179] DataSourceModal: responsive behavior (#67944) --- .../components/picker/DataSourceModal.tsx | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/public/app/features/datasources/components/picker/DataSourceModal.tsx b/public/app/features/datasources/components/picker/DataSourceModal.tsx index e1494321b58..fb41b1492f0 100644 --- a/public/app/features/datasources/components/picker/DataSourceModal.tsx +++ b/public/app/features/datasources/components/picker/DataSourceModal.tsx @@ -171,11 +171,19 @@ function getDataSourceModalStyles(theme: GrafanaTheme2) { height: 80%; max-width: 1200px; max-height: 900px; + + ${theme.breakpoints.down('md')} { + width: 100%; + } `, modalContent: css` display: flex; flex-direction: row; height: 100%; + + ${theme.breakpoints.down('md')} { + flex-direction: column; + } `, leftColumn: css` display: flex; @@ -184,6 +192,15 @@ function getDataSourceModalStyles(theme: GrafanaTheme2) { height: 100%; padding-right: ${theme.spacing(4)}; border-right: 1px solid ${theme.colors.border.weak}; + + ${theme.breakpoints.down('md')} { + width: 100%; + height: 47%; + border-right: 0; + padding-right: 0; + border-bottom: 1px solid ${theme.colors.border.weak}; + padding-bottom: ${theme.spacing(4)}; + } `, rightColumn: css` display: flex; @@ -193,6 +210,13 @@ function getDataSourceModalStyles(theme: GrafanaTheme2) { justify-items: space-evenly; align-items: stretch; padding-left: ${theme.spacing(4)}; + + ${theme.breakpoints.down('md')} { + width: 100%; + height: 53%; + padding-left: 0; + padding-top: ${theme.spacing(4)}; + } `, builtInDataSources: css` flex: 1; @@ -209,6 +233,10 @@ function getDataSourceModalStyles(theme: GrafanaTheme2) { flex-direction: row; width: 100%; justify-content: flex-end; + + ${theme.breakpoints.down('md')} { + padding-bottom: ${theme.spacing(3)}; + } `, searchInput: css` width: 100%; From ee05e3675a204511b3da958dc3e2c158273dddcd Mon Sep 17 00:00:00 2001 From: Gilles De Mey Date: Tue, 9 May 2023 15:18:38 +0200 Subject: [PATCH 068/179] Alerting: Fixes Alert list panel "ungrouped" regression (#68018) --- .../unified-alerting/GroupedView.test.tsx | 46 +++++++++++++++++++ .../unified-alerting/GroupedView.tsx | 29 ++++++++---- 2 files changed, 65 insertions(+), 10 deletions(-) create mode 100644 public/app/plugins/panel/alertlist/unified-alerting/GroupedView.test.tsx diff --git a/public/app/plugins/panel/alertlist/unified-alerting/GroupedView.test.tsx b/public/app/plugins/panel/alertlist/unified-alerting/GroupedView.test.tsx new file mode 100644 index 00000000000..148c0c9f485 --- /dev/null +++ b/public/app/plugins/panel/alertlist/unified-alerting/GroupedView.test.tsx @@ -0,0 +1,46 @@ +import { render, screen } from '@testing-library/react'; +import React from 'react'; + +import { CombinedRuleWithLocation } from 'app/types/unified-alerting'; +import { PromRuleType } from 'app/types/unified-alerting-dto'; + +import { UnifiedAlertListOptions } from '../types'; + +import GroupedView, { UNGROUPED_KEY } from './GroupedView'; + +describe('Grouped view', () => { + const rules: CombinedRuleWithLocation[] = [ + { + promRule: { + type: PromRuleType.Alerting, + alerts: [ + // @ts-ignore + { labels: { job: 'job-1', severity: 'high' } }, + // @ts-ignore + { labels: { job: 'job-2', severity: 'low' } }, + ], + }, + }, + { + promRule: { + type: PromRuleType.Alerting, + alerts: [ + // @ts-ignore + { labels: { foo: 'bar', severity: 'low' } }, + ], + }, + }, + ]; + + it('should group instances by label(s) correctly', () => { + // @ts-ignore + const options: UnifiedAlertListOptions = { + groupBy: ['job', 'severity'], + }; + + render(); + expect(screen.getByTestId('job=job-1&severity=high')).toBeInTheDocument(); + expect(screen.getByTestId('job=job-2&severity=low')).toBeInTheDocument(); + expect(screen.getByTestId(UNGROUPED_KEY)).toBeInTheDocument(); + }); +}); diff --git a/public/app/plugins/panel/alertlist/unified-alerting/GroupedView.tsx b/public/app/plugins/panel/alertlist/unified-alerting/GroupedView.tsx index 8c8ab6131f3..cdc28794cbf 100644 --- a/public/app/plugins/panel/alertlist/unified-alerting/GroupedView.tsx +++ b/public/app/plugins/panel/alertlist/unified-alerting/GroupedView.tsx @@ -5,7 +5,7 @@ import { AlertLabel } from 'app/features/alerting/unified/components/AlertLabel' import { getAlertingRule } from 'app/features/alerting/unified/utils/rules'; import { Alert } from 'app/types/unified-alerting'; -import { AlertingRule, CombinedRuleWithLocation } from '../../../../types/unified-alerting'; +import { CombinedRuleWithLocation } from '../../../../types/unified-alerting'; import { AlertInstances } from '../AlertInstances'; import { getStyles } from '../UnifiedAlertList'; import { GroupedRules, UnifiedAlertListOptions } from '../types'; @@ -16,9 +16,10 @@ type Props = { options: UnifiedAlertListOptions; }; +export const UNGROUPED_KEY = '__ungrouped__'; + const GroupedModeView = ({ rules, options }: Props) => { const styles = useStyles2(getStyles); - const groupBy = options.groupBy; const groupedRules = useMemo(() => { @@ -27,16 +28,23 @@ const GroupedModeView = ({ rules, options }: Props) => { const hasInstancesWithMatchingLabels = (rule: CombinedRuleWithLocation) => groupBy ? alertHasEveryLabelForCombinedRules(rule, groupBy) : true; - const matchingRules = rules.filter(hasInstancesWithMatchingLabels); - matchingRules.forEach((rule: CombinedRuleWithLocation) => { - const alertingRule: AlertingRule | null = getAlertingRule(rule); + rules.forEach((rule) => { + const alertingRule = getAlertingRule(rule); + const hasInstancesMatching = hasInstancesWithMatchingLabels(rule); + (alertingRule?.alerts ?? []).forEach((alert) => { - const mapKey = createMapKey(groupBy, alert.labels); + const mapKey = hasInstancesMatching ? createMapKey(groupBy, alert.labels) : UNGROUPED_KEY; + const existingAlerts = groupedRules.get(mapKey) ?? []; groupedRules.set(mapKey, [...existingAlerts, alert]); }); }); + // move the "UNGROUPED" key to the last item in the Map, items are shown in insertion order + const ungrouped = groupedRules.get(UNGROUPED_KEY) ?? []; + groupedRules.delete(UNGROUPED_KEY); + groupedRules.set(UNGROUPED_KEY, ungrouped); + // Remove groups having no instances // This is different from filtering Rules without instances that we do in UnifiedAlertList const filteredGroupedRules = Array.from(groupedRules.entries()).reduce((acc, [groupKey, groupAlerts]) => { @@ -54,12 +62,13 @@ const GroupedModeView = ({ rules, options }: Props) => { return ( <> {Array.from(groupedRules).map(([key, alerts]) => ( -
  • +
  • - {key && parseMapKey(key).map(([key, value]) => )} - {!key && 'No grouping'} + {key !== UNGROUPED_KEY && + parseMapKey(key).map(([key, value]) => )} + {key === UNGROUPED_KEY && 'No grouping'}
    @@ -79,7 +88,7 @@ function parseMapKey(key: string): Array<[string, string]> { } function alertHasEveryLabelForCombinedRules(rule: CombinedRuleWithLocation, groupByKeys: string[]) { - const alertingRule: AlertingRule | null = getAlertingRule(rule); + const alertingRule = getAlertingRule(rule); return groupByKeys.every((key) => { return (alertingRule?.alerts ?? []).some((alert) => alert.labels[key]); }); From 2489e3524df33a68444cd7f76073e4faba25b1a5 Mon Sep 17 00:00:00 2001 From: Victor Colomb <51762123+VictorColomb@users.noreply.github.com> Date: Tue, 9 May 2023 15:21:28 +0200 Subject: [PATCH 069/179] Variables: Add support for `$__timezone` template variable (#66785) Co-authored-by: Isabel <76437239+imatwawana@users.noreply.github.com> --- .../variables/add-template-variables/index.md | 6 ++++++ .../features/templating/macroRegistry.test.ts | 18 +++++++++++++++++- .../app/features/templating/macroRegistry.ts | 10 +++++++++- 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/docs/sources/dashboards/variables/add-template-variables/index.md b/docs/sources/dashboards/variables/add-template-variables/index.md index deda7868f77..b6872f1f358 100644 --- a/docs/sources/dashboards/variables/add-template-variables/index.md +++ b/docs/sources/dashboards/variables/add-template-variables/index.md @@ -315,6 +315,12 @@ This is used in several places, including: - SQL queries in MySQL, Postgres, and MSSQL. - The `$__timeFilter` variable is used in the MySQL data source. +### $\_\_timezone + +The `$__timezone` variable returns the currently selected time zone, either `utc` or an entry of the IANA time zone database (for example, `America/New_York`). + +If the currently selected time zone is _Browser Time_, Grafana will try to determine your browser time zone. + ## Chained variables _Chained variables_, also called _linked variables_ or _nested variables_, are query variables with one or more other variables in their variable query. This section explains how chained variables work and provides links to example dashboards that use chained variables. diff --git a/public/app/features/templating/macroRegistry.test.ts b/public/app/features/templating/macroRegistry.test.ts index 2e212c5f940..5b5a25e7687 100644 --- a/public/app/features/templating/macroRegistry.test.ts +++ b/public/app/features/templating/macroRegistry.test.ts @@ -3,7 +3,8 @@ import { initTemplateSrv } from 'test/helpers/initTemplateSrv'; import { DataLinkBuiltInVars } from '@grafana/data'; import { getTemplateSrv, setTemplateSrv } from '@grafana/runtime'; -import { setTimeSrv } from '../dashboard/services/TimeSrv'; +import { setTimeSrv, TimeSrv } from '../dashboard/services/TimeSrv'; +import { TimeModel } from '../dashboard/state/TimeModel'; import { variableAdapters } from '../variables/adapters'; import { createQueryVariableAdapter } from '../variables/query/adapter'; @@ -52,3 +53,18 @@ describe('__url_time_range', () => { expect(out).toBe('/d/1?from=1607687293000&to=1607687293100'); }); }); + +describe('__timezone', () => { + beforeAll(() => { + setTimeSrv({ + timeModel: { + getTimezone: () => 'Pacific/Noumea', + } as TimeModel, + } as TimeSrv); + }); + + it('should interpolate to time zone', () => { + const out = getTemplateSrv().replace(`TIMEZONE('$__timezone', '2023-04-19 00:00:00')`); + expect(out).toBe(`TIMEZONE('Pacific/Noumea', '2023-04-19 00:00:00')`); + }); +}); diff --git a/public/app/features/templating/macroRegistry.ts b/public/app/features/templating/macroRegistry.ts index 65ede4f22b6..ddd0303e587 100644 --- a/public/app/features/templating/macroRegistry.ts +++ b/public/app/features/templating/macroRegistry.ts @@ -1,4 +1,6 @@ -import { DataLinkBuiltInVars, ScopedVars, urlUtil } from '@grafana/data'; +import moment from 'moment-timezone'; + +import { DataLinkBuiltInVars, getTimeZone, ScopedVars, urlUtil } from '@grafana/data'; import { getTimeSrv } from '../dashboard/services/TimeSrv'; import { getVariablesUrlParams } from '../variables/getAllVariableValuesForUrl'; @@ -13,6 +15,7 @@ export const macroRegistry: Record = { ['__field']: fieldMacro, [DataLinkBuiltInVars.includeVars]: includeVarsMacro, [DataLinkBuiltInVars.keepTime]: urlTimeRangeMacro, + ['__timezone']: timeZoneMacro, }; function includeVarsMacro(match: string, fieldPath?: string, scopedVars?: ScopedVars) { @@ -23,3 +26,8 @@ function includeVarsMacro(match: string, fieldPath?: string, scopedVars?: Scoped function urlTimeRangeMacro() { return urlUtil.toUrlParams(getTimeSrv().timeRangeForUrl()); } + +function timeZoneMacro() { + const timeZone = getTimeZone({ timeZone: getTimeSrv().timeModel?.getTimezone() }); + return timeZone === 'browser' ? moment.tz.guess() : timeZone; +} From a650ddfecde8288c595a9c620c87173e7bf1e567 Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Tue, 9 May 2023 07:04:49 -0700 Subject: [PATCH 070/179] Dashboards: Make the home dashboard non-editable (by default) (#68044) --- public/dashboards/home.json | 1 + 1 file changed, 1 insertion(+) diff --git a/public/dashboards/home.json b/public/dashboards/home.json index 187a393b662..821a7eaa35a 100644 --- a/public/dashboards/home.json +++ b/public/dashboards/home.json @@ -62,6 +62,7 @@ "from": "now-6h", "to": "now" }, + "editable": false, "timepicker": { "hidden": true, "refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"], From a7cbb72664575ee62279f0a8e4c25577cbd05936 Mon Sep 17 00:00:00 2001 From: Ashley Harrison Date: Tue, 9 May 2023 15:22:23 +0100 Subject: [PATCH 071/179] Accessibility: Add `Skip to content` link (#68065) * user essentials mob! :trident: lastFile:public/app/core/components/AppChrome/AppChrome.tsx * user essentials mob! :trident: lastFile:public/app/core/components/AppChrome/AppChrome.test.tsx * only show skiplink when page has app chrome --------- Co-authored-by: Joao Silva --- .../components/AppChrome/AppChrome.test.tsx | 27 +++++++++- .../core/components/AppChrome/AppChrome.tsx | 49 ++++++++++++------- 2 files changed, 58 insertions(+), 18 deletions(-) diff --git a/public/app/core/components/AppChrome/AppChrome.test.tsx b/public/app/core/components/AppChrome/AppChrome.test.tsx index 92390c093f8..a4c4a40e43b 100644 --- a/public/app/core/components/AppChrome/AppChrome.test.tsx +++ b/public/app/core/components/AppChrome/AppChrome.test.tsx @@ -1,4 +1,5 @@ -import { render, screen } from '@testing-library/react'; +import { render, screen, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import { KBarProvider } from 'kbar'; import React, { ReactNode } from 'react'; import { TestProvider } from 'test/helpers/TestProvider'; @@ -109,4 +110,28 @@ describe('AppChrome', () => { expect(screen.getByRole('tab', { name: 'Tab Child1' })).toBeInTheDocument(); expect(screen.getByRole('tab', { name: 'Tab pageNav child1' })).toBeInTheDocument(); }); + + it('should create a skip link to skip to main content', async () => { + setup(Children); + expect(await screen.findByRole('link', { name: 'Skip to main content' })).toBeInTheDocument(); + }); + + it('should focus the skip link on initial tab before carrying on with normal tab order', async () => { + setup(Children); + await userEvent.keyboard('{tab}'); + const skipLink = await screen.findByRole('link', { name: 'Skip to main content' }); + expect(skipLink).toHaveFocus(); + await userEvent.keyboard('{tab}'); + expect(await screen.findByRole('link', { name: 'Go to home' })).toHaveFocus(); + }); + + it('should not render a skip link if the page is chromeless', async () => { + const { context } = setup(Children); + context.chrome.update({ + chromeless: true, + }); + waitFor(() => { + expect(screen.queryByRole('link', { name: 'Skip to main content' })).not.toBeInTheDocument(); + }); + }); }); diff --git a/public/app/core/components/AppChrome/AppChrome.tsx b/public/app/core/components/AppChrome/AppChrome.tsx index 72489c28fa2..59939ebe768 100644 --- a/public/app/core/components/AppChrome/AppChrome.tsx +++ b/public/app/core/components/AppChrome/AppChrome.tsx @@ -3,7 +3,7 @@ import classNames from 'classnames'; import React, { PropsWithChildren } from 'react'; import { GrafanaTheme2, PageLayoutType } from '@grafana/data'; -import { useStyles2 } from '@grafana/ui'; +import { useStyles2, LinkButton } from '@grafana/ui'; import { useGrafana } from 'app/core/context/GrafanaContext'; import { CommandPalette } from 'app/features/commandPalette/CommandPalette'; import { KioskMode } from 'app/types'; @@ -34,34 +34,39 @@ export function AppChrome({ children }: Props) { // doesn't get re-mounted when chromeless goes from true to false. return ( -
    +
    {!state.chromeless && ( -
    - {!searchBarHidden && } - -
    + <> + + Skip to main content + +
    + {!searchBarHidden && } + +
    + )} -
    +
    {state.layout === PageLayoutType.Standard && state.sectionNav && }
    {children}
    -
    +
    {!state.chromeless && ( <> chrome.setMegaMenu(false)} /> )} -
  • +
    ); } @@ -112,5 +117,15 @@ const getStyles = (theme: GrafanaTheme2) => { flexGrow: 1, minHeight: 0, }), + skipLink: css({ + position: 'absolute', + top: -1000, + + ':focus': { + left: theme.spacing(1), + top: theme.spacing(1), + zIndex: theme.zIndex.portal, + }, + }), }; }; From 65c1505415a98b4f9e34ea75374887bce7195011 Mon Sep 17 00:00:00 2001 From: Andreas Christou Date: Tue, 9 May 2023 15:24:20 +0100 Subject: [PATCH 072/179] AzureMonitor: Ensure trace exceptions are correctly displayed (#68079) Ensure trace exceptions are correctly displayed --- .../azure-log-analytics-datasource.go | 7 +-- .../azure-log-analytics-datasource_test.go | 60 ++++++++++--------- pkg/tsdb/azuremonitor/loganalytics/consts.go | 2 + 3 files changed, 36 insertions(+), 33 deletions(-) diff --git a/pkg/tsdb/azuremonitor/loganalytics/azure-log-analytics-datasource.go b/pkg/tsdb/azuremonitor/loganalytics/azure-log-analytics-datasource.go index 6ad2b8b5bae..f1933302893 100644 --- a/pkg/tsdb/azuremonitor/loganalytics/azure-log-analytics-datasource.go +++ b/pkg/tsdb/azuremonitor/loganalytics/azure-log-analytics-datasource.go @@ -729,10 +729,7 @@ func buildTracesQuery(operationId string, traceTypes []string, filters []types.T propertiesFunc = fmt.Sprintf("bag_merge(bag_pack_columns(%s), customDimensions, customMeasurements)", strings.Join(tags, ",")) } - errorProperty := "" - if slices.Contains(filteredTypes, "exceptions") { - errorProperty = `| extend error = iff(itemType == "exceptions", true, false)` - } + errorProperty := `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` baseQuery := fmt.Sprintf(`set truncationmaxrecords=10000; set truncationmaxsize=67108864; union isfuzzy=true %s | where $__timeFilter()`, strings.Join(filteredTypes, ",")) propertiesStaticQuery := `| extend duration = iff(isnull(column_ifexists("duration", real(null))), toreal(0), column_ifexists("duration", real(null)))` + @@ -744,7 +741,7 @@ func buildTracesQuery(operationId string, traceTypes []string, filters []types.T projectClause := `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + `| order by startTime asc` - return baseQuery + whereClause + propertiesStaticQuery + propertiesQuery + errorProperty + filtersClause + projectClause + return baseQuery + whereClause + propertiesStaticQuery + errorProperty + propertiesQuery + filtersClause + projectClause } func buildTracesLogsQuery(operationId *string) string { diff --git a/pkg/tsdb/azuremonitor/loganalytics/azure-log-analytics-datasource_test.go b/pkg/tsdb/azuremonitor/loganalytics/azure-log-analytics-datasource_test.go index 98ae108cd25..97b1053dba2 100644 --- a/pkg/tsdb/azuremonitor/loganalytics/azure-log-analytics-datasource_test.go +++ b/pkg/tsdb/azuremonitor/loganalytics/azure-log-analytics-datasource_test.go @@ -256,6 +256,7 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) { `| extend operationName = iff(isempty(column_ifexists("name", "")), column_ifexists("problemId", ""), column_ifexists("name", ""))` + `| extend serviceName = cloud_RoleName` + `| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` + + `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + `| extend tags = bag_merge(customDimensions, customMeasurements)` + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + @@ -270,6 +271,7 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) { `| extend operationName = iff(isempty(column_ifexists("name", "")), column_ifexists("problemId", ""), column_ifexists("name", ""))` + `| extend serviceName = cloud_RoleName` + `| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` + + `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + `| extend tags = bag_merge(customDimensions, customMeasurements)` + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + @@ -318,6 +320,7 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) { `| extend operationName = iff(isempty(column_ifexists("name", "")), column_ifexists("problemId", ""), column_ifexists("name", ""))` + `| extend serviceName = cloud_RoleName` + `| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` + + `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + `| extend tags = bag_merge(customDimensions, customMeasurements)` + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + @@ -332,6 +335,7 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) { `| extend operationName = iff(isempty(column_ifexists("name", "")), column_ifexists("problemId", ""), column_ifexists("name", ""))` + `| extend serviceName = cloud_RoleName` + `| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` + + `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + `| extend tags = bag_merge(customDimensions, customMeasurements)` + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + @@ -377,8 +381,8 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) { `| extend operationName = iff(isempty(column_ifexists("name", "")), column_ifexists("problemId", ""), column_ifexists("name", ""))` + `| extend serviceName = cloud_RoleName` + `| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` + - `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + - `| extend error = iff(itemType == "exceptions", true, false)` + + `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,error,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + `| order by startTime asc`, @@ -392,8 +396,8 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) { `| extend operationName = iff(isempty(column_ifexists("name", "")), column_ifexists("problemId", ""), column_ifexists("name", ""))` + `| extend serviceName = cloud_RoleName` + `| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` + - `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + - `| extend error = iff(itemType == "exceptions", true, false)` + + `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,error,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + `| order by startTime asc`, @@ -441,8 +445,8 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) { `| extend operationName = iff(isempty(column_ifexists("name", "")), column_ifexists("problemId", ""), column_ifexists("name", ""))` + `| extend serviceName = cloud_RoleName` + `| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` + - `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + - `| extend error = iff(itemType == "exceptions", true, false)` + + `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,error,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + `| order by startTime asc`, @@ -456,8 +460,8 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) { `| extend operationName = iff(isempty(column_ifexists("name", "")), column_ifexists("problemId", ""), column_ifexists("name", ""))` + `| extend serviceName = cloud_RoleName` + `| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` + - `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + - `| extend error = iff(itemType == "exceptions", true, false)` + + `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,error,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + `| order by startTime asc`, @@ -507,8 +511,8 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) { `| extend operationName = iff(isempty(column_ifexists("name", "")), column_ifexists("problemId", ""), column_ifexists("name", ""))` + `| extend serviceName = cloud_RoleName` + `| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` + - `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + - `| extend error = iff(itemType == "exceptions", true, false)` + + `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,error,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + `| where appId in ("test-app-id")` + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + @@ -523,8 +527,8 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) { `| extend operationName = iff(isempty(column_ifexists("name", "")), column_ifexists("problemId", ""), column_ifexists("name", ""))` + `| extend serviceName = cloud_RoleName` + `| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` + - `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + - `| extend error = iff(itemType == "exceptions", true, false)` + + `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,error,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + `| where appId in ("test-app-id")` + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + @@ -575,8 +579,8 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) { `| extend operationName = iff(isempty(column_ifexists("name", "")), column_ifexists("problemId", ""), column_ifexists("name", ""))` + `| extend serviceName = cloud_RoleName` + `| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` + - `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + - `| extend error = iff(itemType == "exceptions", true, false)` + + `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,error,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + `| where appId !in ("test-app-id")` + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + @@ -591,8 +595,8 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) { `| extend operationName = iff(isempty(column_ifexists("name", "")), column_ifexists("problemId", ""), column_ifexists("name", ""))` + `| extend serviceName = cloud_RoleName` + `| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` + - `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + - `| extend error = iff(itemType == "exceptions", true, false)` + + `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,error,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + `| where appId !in ("test-app-id")` + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + @@ -643,8 +647,8 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) { `| extend operationName = iff(isempty(column_ifexists("name", "")), column_ifexists("problemId", ""), column_ifexists("name", ""))` + `| extend serviceName = cloud_RoleName` + `| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` + - `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + - `| extend error = iff(itemType == "exceptions", true, false)` + + `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,error,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + `| where appId !in ("test-app-id")| where clientId in ("test-client-id")` + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + @@ -659,8 +663,8 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) { `| extend operationName = iff(isempty(column_ifexists("name", "")), column_ifexists("problemId", ""), column_ifexists("name", ""))` + `| extend serviceName = cloud_RoleName` + `| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` + - `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + - `| extend error = iff(itemType == "exceptions", true, false)` + + `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,error,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + `| where appId !in ("test-app-id")| where clientId in ("test-client-id")` + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + @@ -706,8 +710,8 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) { `| extend operationName = iff(isempty(column_ifexists("name", "")), column_ifexists("problemId", ""), column_ifexists("name", ""))` + `| extend serviceName = cloud_RoleName` + `| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` + - `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + - `| extend error = iff(itemType == "exceptions", true, false)` + + `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,error,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + `| order by startTime asc`, @@ -721,8 +725,8 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) { `| extend operationName = iff(isempty(column_ifexists("name", "")), column_ifexists("problemId", ""), column_ifexists("name", ""))` + `| extend serviceName = cloud_RoleName` + `| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` + - `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + - `| extend error = iff(itemType == "exceptions", true, false)` + + `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,error,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + `| order by startTime asc`, @@ -770,8 +774,8 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) { `| extend operationName = iff(isempty(column_ifexists("name", "")), column_ifexists("problemId", ""), column_ifexists("name", ""))` + `| extend serviceName = cloud_RoleName` + `| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` + - `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + - `| extend error = iff(itemType == "exceptions", true, false)` + + `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,error,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + `| order by startTime asc`, @@ -785,8 +789,8 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) { `| extend operationName = iff(isempty(column_ifexists("name", "")), column_ifexists("problemId", ""), column_ifexists("name", ""))` + `| extend serviceName = cloud_RoleName` + `| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` + - `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + - `| extend error = iff(itemType == "exceptions", true, false)` + + `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,error,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + `| order by startTime asc`, diff --git a/pkg/tsdb/azuremonitor/loganalytics/consts.go b/pkg/tsdb/azuremonitor/loganalytics/consts.go index 529c5fcc7da..08f4270c5ca 100644 --- a/pkg/tsdb/azuremonitor/loganalytics/consts.go +++ b/pkg/tsdb/azuremonitor/loganalytics/consts.go @@ -92,6 +92,8 @@ var ExceptionsSchema = copyCommonProperties(map[string]string{ "problemId": "ProblemId", "severityLevel": "SeverityLevel", "type": "ExceptionType", + // Grafana specific error tag + "error": "error", }, map[string]string{"duration": "duration", "id": "id", "name": "name", "performanceBucket": "performanceBucket", "success": "success"}) var TracesSchema = copyCommonProperties(map[string]string{ From 8e752439c69103f47bd8f5ae8fe5b2e52dac1ea3 Mon Sep 17 00:00:00 2001 From: Ashley Harrison Date: Tue, 9 May 2023 15:26:17 +0100 Subject: [PATCH 073/179] Graph: Fix legend overlapping graph area (#67912) use render callback instead of requestIdleTimeout --- public/app/plugins/panel/graph/Legend/Legend.tsx | 4 +++- public/app/plugins/panel/graph/graph.ts | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/public/app/plugins/panel/graph/Legend/Legend.tsx b/public/app/plugins/panel/graph/Legend/Legend.tsx index 670b892725e..984d2af5b8b 100644 --- a/public/app/plugins/panel/graph/Legend/Legend.tsx +++ b/public/app/plugins/panel/graph/Legend/Legend.tsx @@ -32,6 +32,7 @@ interface LegendDisplayProps { alignAsTable?: boolean; rightSide?: boolean; sideWidth?: number; + renderCallback?: () => void; } interface LegendValuesProps { @@ -176,6 +177,7 @@ export class GraphLegend extends PureComponent { avg, current, total, + renderCallback, } = this.props; const seriesValuesProps = { values, min, max, avg, current, total }; const hiddenSeries = this.state.hiddenSeries; @@ -205,7 +207,7 @@ export class GraphLegend extends PureComponent { }; return ( -
    +
    {this.props.alignAsTable ? : }
    ); diff --git a/public/app/plugins/panel/graph/graph.ts b/public/app/plugins/panel/graph/graph.ts index 57f4f2a8760..819fa92e37d 100644 --- a/public/app/plugins/panel/graph/graph.ts +++ b/public/app/plugins/panel/graph/graph.ts @@ -155,13 +155,13 @@ class GraphElement { onToggleSort: this.ctrl.onToggleSort, onColorChange: this.ctrl.onColorChange, onToggleAxis: this.ctrl.onToggleAxis, + renderCallback: this.renderPanel.bind(this), }; const legendReactElem = React.createElement(LegendWithThemeProvider, legendProps); // render callback isn't supported in react 18+, see: https://github.com/reactwg/react-18/discussions/5 this.legendElemRoot.render(legendReactElem); - requestIdleCallback(() => this.renderPanel()); } onGraphHover(evt: LegacyGraphHoverEventPayload | DataHoverPayload) { From 86228ba1a03fc7ae1774b868839dc8b3f77edeed Mon Sep 17 00:00:00 2001 From: Jev Forsberg <46619047+baldm0mma@users.noreply.github.com> Date: Tue, 9 May 2023 08:29:02 -0600 Subject: [PATCH 074/179] Datasource: Fix missing raw SQL query in Query Inspector when query returns zero rows (#67844) * baldm0mma/issue5799/ add empty frame with all meta data * baldm0mma/issue5799/ add null test * write an integration test around query results with empty rows * baldm0mma/issue5799/ add tests to mssql and postgres * remove use of apparently reserved keyword in mysql8 * baldm0mma/issue5799 * baldm0mma/issue5799/ update tests * baldm0mma/issue5799/ update test structs * baldm0mma/issue5799/ update annotation --------- Co-authored-by: Michael Mandrus --- pkg/tsdb/mssql/mssql_test.go | 44 ++++++++++++++++++++++++++++++ pkg/tsdb/mysql/mysql_test.go | 44 ++++++++++++++++++++++++++++++ pkg/tsdb/postgres/postgres_test.go | 44 ++++++++++++++++++++++++++++++ pkg/tsdb/sqleng/sql_engine.go | 8 ++++-- 4 files changed, 138 insertions(+), 2 deletions(-) diff --git a/pkg/tsdb/mssql/mssql_test.go b/pkg/tsdb/mssql/mssql_test.go index 55705b9a73a..a0e76ed5f78 100644 --- a/pkg/tsdb/mssql/mssql_test.go +++ b/pkg/tsdb/mssql/mssql_test.go @@ -1263,6 +1263,50 @@ func TestMSSQL(t *testing.T) { }) }) }) + + t.Run("Given an empty table", func(t *testing.T) { + type emptyObj struct { + EmptyKey string + EmptyVal int64 + } + + exists, err := sess.IsTableExist(emptyObj{}) + require.NoError(t, err) + if exists { + err := sess.DropTable(emptyObj{}) + require.NoError(t, err) + } + err = sess.CreateTable(emptyObj{}) + require.NoError(t, err) + + t.Run("When no rows are returned, should return an empty frame", func(t *testing.T) { + query := &backend.QueryDataRequest{ + Queries: []backend.DataQuery{ + { + JSON: []byte(`{ + "rawSql": "SELECT empty_key, empty_val FROM empty_obj", + "format": "table" + }`), + RefID: "A", + TimeRange: backend.TimeRange{ + From: time.Now(), + To: time.Now().Add(1 * time.Minute), + }, + }, + }, + } + + resp, err := endpoint.QueryData(context.Background(), query) + require.NoError(t, err) + queryResult := resp.Responses["A"] + + frames := queryResult.Frames + require.Len(t, frames, 1) + require.Equal(t, 0, frames[0].Rows()) + require.NotNil(t, frames[0].Fields) + require.Empty(t, frames[0].Fields) + }) + }) } func TestTransformQueryError(t *testing.T) { diff --git a/pkg/tsdb/mysql/mysql_test.go b/pkg/tsdb/mysql/mysql_test.go index 58142d23190..ef2b61d5ffc 100644 --- a/pkg/tsdb/mysql/mysql_test.go +++ b/pkg/tsdb/mysql/mysql_test.go @@ -1229,6 +1229,50 @@ func TestIntegrationMySQL(t *testing.T) { }) }) }) + + t.Run("Given an empty table", func(t *testing.T) { + type emptyObj struct { + EmptyKey string + EmptyVal int64 + } + + exists, err := sess.IsTableExist(emptyObj{}) + require.NoError(t, err) + if exists { + err := sess.DropTable(emptyObj{}) + require.NoError(t, err) + } + err = sess.CreateTable(emptyObj{}) + require.NoError(t, err) + + t.Run("When no rows are returned, should return an empty frame", func(t *testing.T) { + query := &backend.QueryDataRequest{ + Queries: []backend.DataQuery{ + { + JSON: []byte(`{ + "rawSql": "SELECT * FROM empty_obj", + "format": "table" + }`), + RefID: "A", + TimeRange: backend.TimeRange{ + From: time.Now(), + To: time.Now().Add(1 * time.Minute), + }, + }, + }, + } + + resp, err := exe.QueryData(context.Background(), query) + require.NoError(t, err) + queryResult := resp.Responses["A"] + + frames := queryResult.Frames + require.Len(t, frames, 1) + require.Equal(t, 0, frames[0].Rows()) + require.NotNil(t, frames[0].Fields) + require.Empty(t, frames[0].Fields) + }) + }) } func InitMySQLTestDB(t *testing.T) *xorm.Engine { diff --git a/pkg/tsdb/postgres/postgres_test.go b/pkg/tsdb/postgres/postgres_test.go index e48c79c5ef9..ecf9405eabf 100644 --- a/pkg/tsdb/postgres/postgres_test.go +++ b/pkg/tsdb/postgres/postgres_test.go @@ -1331,6 +1331,50 @@ func TestIntegrationPostgres(t *testing.T) { }) }) }) + + t.Run("Given an empty table", func(t *testing.T) { + type emptyObj struct { + EmptyKey string + EmptyVal int64 + } + + exists, err := sess.IsTableExist(emptyObj{}) + require.NoError(t, err) + if exists { + err := sess.DropTable(emptyObj{}) + require.NoError(t, err) + } + err = sess.CreateTable(emptyObj{}) + require.NoError(t, err) + + t.Run("When no rows are returned, should return an empty frame", func(t *testing.T) { + query := &backend.QueryDataRequest{ + Queries: []backend.DataQuery{ + { + JSON: []byte(`{ + "rawSql": "SELECT empty_key, empty_val FROM empty_obj", + "format": "table" + }`), + RefID: "A", + TimeRange: backend.TimeRange{ + From: time.Now(), + To: time.Now().Add(1 * time.Minute), + }, + }, + }, + } + + resp, err := exe.QueryData(context.Background(), query) + require.NoError(t, err) + queryResult := resp.Responses["A"] + + frames := queryResult.Frames + require.Len(t, frames, 1) + require.Equal(t, 0, frames[0].Rows()) + require.NotNil(t, frames[0].Fields) + require.Empty(t, frames[0].Fields) + }) + }) } func InitPostgresTestDB(t *testing.T) *xorm.Engine { diff --git a/pkg/tsdb/sqleng/sql_engine.go b/pkg/tsdb/sqleng/sql_engine.go index aeb93d411d1..02c91b9a424 100644 --- a/pkg/tsdb/sqleng/sql_engine.go +++ b/pkg/tsdb/sqleng/sql_engine.go @@ -312,9 +312,13 @@ func (e *DataSourceHandler) executeQuery(query backend.DataQuery, wg *sync.WaitG frame.Meta.ExecutedQueryString = interpolatedQuery - // If no rows were returned, no point checking anything else. + // If no rows were returned, clear any previously set `Fields` with a single empty `data.Field` slice. + // Then assign `queryResult.dataResponse.Frames` the current single frame with that single empty Field. + // This assures 1) our visualization doesn't display unwanted empty fields, and also that 2) + // additionally-needed frame data stays intact and is correctly passed to our visulization. if frame.Rows() == 0 { - queryResult.dataResponse.Frames = data.Frames{} + frame.Fields = []*data.Field{} + queryResult.dataResponse.Frames = data.Frames{frame} ch <- queryResult return } From 5ddc37ee21a5a10862ee44604674a4996e6ccd48 Mon Sep 17 00:00:00 2001 From: Andreas Christou Date: Tue, 9 May 2023 15:29:36 +0100 Subject: [PATCH 075/179] AzureMonitor: Appropriately handle trace correlation resources (#67934) * Appropriately handle correlation resources - Retrieve resources in buildQueries step - Build updated queries based off of correlation results - Update tests * Fix query building and tests * Fix lint * Ensure multiple resource queries will work - Correctly set resources to ensure there is no overlapping between multiple resource selection and correlated resources - Add tests for these specific cases - Remove unnecessary pointers - Build queries correctly --- .../azure-log-analytics-datasource.go | 102 +++-- .../azure-log-analytics-datasource_test.go | 412 ++++++++++++++++-- 2 files changed, 444 insertions(+), 70 deletions(-) diff --git a/pkg/tsdb/azuremonitor/loganalytics/azure-log-analytics-datasource.go b/pkg/tsdb/azuremonitor/loganalytics/azure-log-analytics-datasource.go index f1933302893..f48b7dd93a6 100644 --- a/pkg/tsdb/azuremonitor/loganalytics/azure-log-analytics-datasource.go +++ b/pkg/tsdb/azuremonitor/loganalytics/azure-log-analytics-datasource.go @@ -59,7 +59,7 @@ func (e *AzureLogAnalyticsDatasource) ResourceRequest(rw http.ResponseWriter, re func (e *AzureLogAnalyticsDatasource) ExecuteTimeSeriesQuery(ctx context.Context, logger log.Logger, originalQueries []backend.DataQuery, dsInfo types.DatasourceInfo, client *http.Client, url string, tracer tracing.Tracer) (*backend.QueryDataResponse, error) { result := backend.NewQueryDataResponse() ctxLogger := logger.FromContext(ctx) - queries, err := e.buildQueries(ctxLogger, originalQueries, dsInfo) + queries, err := e.buildQueries(ctx, ctxLogger, originalQueries, dsInfo, tracer) if err != nil { return nil, err } @@ -81,7 +81,7 @@ func getApiURL(resourceOrWorkspace string) string { } } -func (e *AzureLogAnalyticsDatasource) buildQueries(logger log.Logger, queries []backend.DataQuery, dsInfo types.DatasourceInfo) ([]*AzureLogAnalyticsQuery, error) { +func (e *AzureLogAnalyticsDatasource) buildQueries(ctx context.Context, logger log.Logger, queries []backend.DataQuery, dsInfo types.DatasourceInfo, tracer tracing.Tracer) ([]*AzureLogAnalyticsQuery, error) { azureLogAnalyticsQueries := []*AzureLogAnalyticsQuery{} for _, query := range queries { @@ -144,20 +144,32 @@ func (e *AzureLogAnalyticsDatasource) buildQueries(logger log.Logger, queries [] resources = azureTracesTarget.Resources resourceOrWorkspace = azureTracesTarget.Resources[0] - - operationId := "" - if queryJSONModel.AzureTraces.OperationId != nil { - operationId = *queryJSONModel.AzureTraces.OperationId + resourcesMap := make(map[string]bool, 0) + if len(resources) > 1 { + for _, resource := range resources { + resourcesMap[strings.ToLower(resource)] = true + } + // Remove the base resource as that's where the query is run anyway + delete(resourcesMap, strings.ToLower(resourceOrWorkspace)) } - queryString = buildTracesQuery(operationId, queryJSONModel.AzureTraces.TraceTypes, queryJSONModel.AzureTraces.Filters, &resultFormat) + operationId := "" + if queryJSONModel.AzureTraces.OperationId != nil && *queryJSONModel.AzureTraces.OperationId != "" { + operationId = *queryJSONModel.AzureTraces.OperationId + resourcesMap, err = getCorrelationWorkspaces(ctx, logger, resourceOrWorkspace, resourcesMap, dsInfo, operationId, tracer) + if err != nil { + return nil, fmt.Errorf("failed to retrieve correlation resources for operation ID - %s: %s", operationId, err) + } + } + + queryString = buildTracesQuery(operationId, queryJSONModel.AzureTraces.TraceTypes, queryJSONModel.AzureTraces.Filters, &resultFormat, resourcesMap) traceIdVariable := "${__data.fields.traceID}" if operationId == "" { - traceExploreQuery = buildTracesQuery(traceIdVariable, queryJSONModel.AzureTraces.TraceTypes, queryJSONModel.AzureTraces.Filters, nil) - traceLogsExploreQuery = buildTracesLogsQuery(&traceIdVariable) + traceExploreQuery = buildTracesQuery(traceIdVariable, queryJSONModel.AzureTraces.TraceTypes, queryJSONModel.AzureTraces.Filters, &resultFormat, resourcesMap) + traceLogsExploreQuery = buildTracesLogsQuery(traceIdVariable, resourcesMap) } else { traceExploreQuery = queryString - traceLogsExploreQuery = buildTracesLogsQuery(&operationId) + traceLogsExploreQuery = buildTracesLogsQuery(operationId, resourcesMap) } traceExploreQuery, err = macros.KqlInterpolate(logger, query, dsInfo, traceExploreQuery, "TimeGenerated") if err != nil { @@ -223,13 +235,6 @@ func (e *AzureLogAnalyticsDatasource) executeQuery(ctx context.Context, logger l } if query.QueryType == string(dataquery.AzureQueryTypeAzureTraces) { - if queryJSONModel.AzureTraces.OperationId != nil && *queryJSONModel.AzureTraces.OperationId != "" { - query, err = getCorrelationWorkspaces(ctx, logger, query, dsInfo, *queryJSONModel.AzureTraces.OperationId, tracer) - if err != nil { - dataResponse.Error = err - return dataResponse - } - } if dataquery.ResultFormat(query.ResultFormat) == (dataquery.ResultFormatTrace) && query.Query == "" { return dataResponseErrorWithExecuted(fmt.Errorf("cannot visualise trace events using the trace visualiser")) } @@ -484,9 +489,9 @@ func getTracesQueryUrl(resources []string, azurePortalUrl string) (string, error return portalUrl, nil } -func getCorrelationWorkspaces(ctx context.Context, logger log.Logger, query *AzureLogAnalyticsQuery, dsInfo types.DatasourceInfo, operationId string, tracer tracing.Tracer) (*AzureLogAnalyticsQuery, error) { +func getCorrelationWorkspaces(ctx context.Context, logger log.Logger, baseResource string, resourcesMap map[string]bool, dsInfo types.DatasourceInfo, operationId string, tracer tracing.Tracer) (map[string]bool, error) { azMonService := dsInfo.Services["Azure Monitor"] - correlationUrl := azMonService.URL + fmt.Sprintf("%s/providers/microsoft.insights/transactions/%s", query.Resources[0], operationId) + correlationUrl := azMonService.URL + fmt.Sprintf("%s/providers/microsoft.insights/transactions/%s", baseResource, operationId) callCorrelationAPI := func(url string) (AzureCorrelationAPIResponse, error) { req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewBuffer([]byte{})) @@ -539,6 +544,12 @@ func getCorrelationWorkspaces(ctx context.Context, logger log.Logger, query *Azu return AzureCorrelationAPIResponse{}, err } + for _, resource := range data.Properties.Resources { + lowerCaseResource := strings.ToLower(resource) + if _, ok := resourcesMap[lowerCaseResource]; !ok { + resourcesMap[lowerCaseResource] = true + } + } return data, nil } @@ -547,21 +558,21 @@ func getCorrelationWorkspaces(ctx context.Context, logger log.Logger, query *Azu correlationResponse, err := callCorrelationAPI(correlationUrl) if err != nil { - return query, err + return nil, err } nextLink = correlationResponse.Properties.NextLink - query.Resources = correlationResponse.Properties.Resources for nextLink != nil { correlationResponse, err := callCorrelationAPI(correlationUrl) if err != nil { - return query, err + return nil, err } - query.Resources = append(query.Resources, correlationResponse.Properties.Resources...) nextLink = correlationResponse.Properties.NextLink } - return query, nil + // Remove the base element as that's where the query is run anyway + delete(resourcesMap, strings.ToLower(baseResource)) + return resourcesMap, nil } // Error definition has been inferred from real data and other model definitions like @@ -663,7 +674,7 @@ func encodeQuery(rawQuery string) (string, error) { return base64.StdEncoding.EncodeToString(b.Bytes()), nil } -func buildTracesQuery(operationId string, traceTypes []string, filters []types.TracesFilters, resultFormat *string) string { +func buildTracesQuery(operationId string, traceTypes []string, filters []types.TracesFilters, resultFormat *string, resources map[string]bool) string { types := traceTypes if len(types) == 0 { types = Tables @@ -682,6 +693,19 @@ func buildTracesQuery(operationId string, traceTypes []string, filters []types.T return "" } + resourcesQuery := strings.Join(filteredTypes, ",") + if len(resources) > 0 { + intermediate := make([]string, 0) + for resource := range resources { + resourceSplit := strings.SplitAfter(resource, "/") + resourceName := resourceSplit[len(resourceSplit)-1] + for _, table := range filteredTypes { + intermediate = append(intermediate, fmt.Sprintf("app('%s').%s", resourceName, table)) + } + } + resourcesQuery += "," + strings.Join(intermediate, ",") + } + tagsMap := make(map[string]bool) var tags []string for _, t := range filteredTypes { @@ -731,7 +755,7 @@ func buildTracesQuery(operationId string, traceTypes []string, filters []types.T errorProperty := `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` - baseQuery := fmt.Sprintf(`set truncationmaxrecords=10000; set truncationmaxsize=67108864; union isfuzzy=true %s | where $__timeFilter()`, strings.Join(filteredTypes, ",")) + baseQuery := fmt.Sprintf(`set truncationmaxrecords=10000; set truncationmaxsize=67108864; union isfuzzy=true %s`, resourcesQuery) propertiesStaticQuery := `| extend duration = iff(isnull(column_ifexists("duration", real(null))), toreal(0), column_ifexists("duration", real(null)))` + `| extend spanID = iff(itemType == "pageView" or isempty(column_ifexists("id", "")), tostring(new_guid()), column_ifexists("id", ""))` + `| extend operationName = iff(isempty(column_ifexists("name", "")), column_ifexists("problemId", ""), column_ifexists("name", ""))` + @@ -744,9 +768,25 @@ func buildTracesQuery(operationId string, traceTypes []string, filters []types.T return baseQuery + whereClause + propertiesStaticQuery + errorProperty + propertiesQuery + filtersClause + projectClause } -func buildTracesLogsQuery(operationId *string) string { - tables := `union *, traces, customEvents, pageViews, requests, dependencies, exceptions, customMetrics, availabilityResults` - time := `| where $__timeFilter()` - operationIdFilter := fmt.Sprintf(`| where operation_Id == "%s"`, *operationId) - return strings.Join([]string{tables, time, operationIdFilter}, " \n") +func buildTracesLogsQuery(operationId string, resources map[string]bool) string { + types := Tables + sort.Strings(types) + selectors := "union " + strings.Join(types, ",\n") + "\n" + if len(resources) > 0 { + intermediate := make([]string, 0) + for resource := range resources { + resourceSplit := strings.SplitAfter(resource, "/") + resourceName := resourceSplit[len(resourceSplit)-1] + for _, table := range types { + intermediate = append(intermediate, fmt.Sprintf("app('%s').%s", resourceName, table)) + } + } + sort.Strings(intermediate) + types = intermediate + selectors = strings.Join(append([]string{"union *"}, types...), ",\n") + "\n" + } + + query := selectors + query += fmt.Sprintf(`| where operation_Id == "%s"`, operationId) + return query } diff --git a/pkg/tsdb/azuremonitor/loganalytics/azure-log-analytics-datasource_test.go b/pkg/tsdb/azuremonitor/loganalytics/azure-log-analytics-datasource_test.go index 97b1053dba2..02298dcb7ef 100644 --- a/pkg/tsdb/azuremonitor/loganalytics/azure-log-analytics-datasource_test.go +++ b/pkg/tsdb/azuremonitor/loganalytics/azure-log-analytics-datasource_test.go @@ -2,15 +2,18 @@ package loganalytics import ( "context" + "encoding/json" "fmt" "io" "net/http" + "net/http/httptest" "strings" "testing" "time" "github.com/google/go-cmp/cmp" "github.com/grafana/grafana-plugin-sdk-go/backend" + "github.com/grafana/grafana-plugin-sdk-go/backend/httpclient" "github.com/stretchr/testify/require" "github.com/grafana/grafana/pkg/infra/log" @@ -25,6 +28,71 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) { datasource := &AzureLogAnalyticsDatasource{} fromStart := time.Date(2018, 3, 15, 13, 0, 0, 0, time.UTC).In(time.Local) timeRange := backend.TimeRange{From: fromStart, To: fromStart.Add(34 * time.Minute)} + ctx := context.Background() + tracer := tracing.InitializeTracerForTest() + svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + var correlationRes AzureCorrelationAPIResponse + if strings.Contains(r.URL.Path, "test-op-id") { + correlationRes = AzureCorrelationAPIResponse{ + ID: "/subscriptions/r1", + Name: "guid-1", + Type: "microsoft.insights/transactions", + Properties: AzureCorrelationAPIResponseProperties{ + Resources: []string{ + "/subscriptions/r1", + }, + NextLink: nil, + }, + } + } else if strings.Contains(r.URL.Path, "op-id-multi") { + correlationRes = AzureCorrelationAPIResponse{ + ID: "/subscriptions/r1", + Name: "guid-1", + Type: "microsoft.insights/transactions", + Properties: AzureCorrelationAPIResponseProperties{ + Resources: []string{ + "/subscriptions/r1", + "/subscriptions/r2", + }, + NextLink: nil, + }, + } + } else if strings.Contains(r.URL.Path, "op-id-non-overlapping") { + correlationRes = AzureCorrelationAPIResponse{ + ID: "/subscriptions/r1", + Name: "guid-1", + Type: "microsoft.insights/transactions", + Properties: AzureCorrelationAPIResponseProperties{ + Resources: []string{ + "/subscriptions/r1", + "/subscriptions/r3", + }, + NextLink: nil, + }, + } + } + err := json.NewEncoder(w).Encode(correlationRes) + if err != nil { + t.Errorf("failed to encode correlation API response") + } + })) + + provider := httpclient.NewProvider(httpclient.ProviderOptions{Timeout: &httpclient.DefaultTimeoutOptions}) + client, err := provider.New() + if err != nil { + t.Errorf("failed to create fake client") + } + + dsInfo := types.DatasourceInfo{ + Services: map[string]types.DatasourceService{ + "Azure Monitor": {URL: svr.URL, HTTPClient: client}, + }, + JSONData: map[string]interface{}{ + "azureLogAnalyticsSameAs": false, + }, + } tests := []struct { name string @@ -249,7 +317,7 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) { "operationId": "test-op-id" } }`, dataquery.ResultFormatTable)), - Query: `set truncationmaxrecords=10000; set truncationmaxsize=67108864; union isfuzzy=true trace | where ['timestamp'] >= datetime('2018-03-15T13:00:00Z') and ['timestamp'] <= datetime('2018-03-15T13:34:00Z')` + + Query: `set truncationmaxrecords=10000; set truncationmaxsize=67108864; union isfuzzy=true trace` + `| where (operation_Id != '' and operation_Id == 'test-op-id') or (customDimensions.ai_legacyRootId != '' and customDimensions.ai_legacyRootId == 'test-op-id')` + `| extend duration = iff(isnull(column_ifexists("duration", real(null))), toreal(0), column_ifexists("duration", real(null)))` + `| extend spanID = iff(itemType == "pageView" or isempty(column_ifexists("id", "")), tostring(new_guid()), column_ifexists("id", ""))` + @@ -264,7 +332,7 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) { Resources: []string{"/subscriptions/r1"}, TimeRange: timeRange, QueryType: string(dataquery.AzureQueryTypeAzureTraces), - TraceExploreQuery: `set truncationmaxrecords=10000; set truncationmaxsize=67108864; union isfuzzy=true trace | where ['timestamp'] >= datetime('2018-03-15T13:00:00Z') and ['timestamp'] <= datetime('2018-03-15T13:34:00Z')` + + TraceExploreQuery: `set truncationmaxrecords=10000; set truncationmaxsize=67108864; union isfuzzy=true trace` + `| where (operation_Id != '' and operation_Id == 'test-op-id') or (customDimensions.ai_legacyRootId != '' and customDimensions.ai_legacyRootId == 'test-op-id')` + `| extend duration = iff(isnull(column_ifexists("duration", real(null))), toreal(0), column_ifexists("duration", real(null)))` + `| extend spanID = iff(itemType == "pageView" or isempty(column_ifexists("id", "")), tostring(new_guid()), column_ifexists("id", ""))` + @@ -276,8 +344,7 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) { `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + `| order by startTime asc`, - TraceLogsExploreQuery: "union *, traces, customEvents, pageViews, requests, dependencies, exceptions, customMetrics, availabilityResults \n" + - "| where ['timestamp'] >= datetime('2018-03-15T13:00:00Z') and ['timestamp'] <= datetime('2018-03-15T13:34:00Z') \n" + + TraceLogsExploreQuery: "union availabilityResults,\n" + "customEvents,\n" + "dependencies,\n" + "exceptions,\n" + "pageViews,\n" + "requests,\n" + "traces\n" + "| where operation_Id == \"test-op-id\"", }, }, @@ -313,7 +380,7 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) { "operationId": "test-op-id" } }`), - Query: `set truncationmaxrecords=10000; set truncationmaxsize=67108864; union isfuzzy=true trace | where ['timestamp'] >= datetime('2018-03-15T13:00:00Z') and ['timestamp'] <= datetime('2018-03-15T13:34:00Z')` + + Query: `set truncationmaxrecords=10000; set truncationmaxsize=67108864; union isfuzzy=true trace` + `| where (operation_Id != '' and operation_Id == 'test-op-id') or (customDimensions.ai_legacyRootId != '' and customDimensions.ai_legacyRootId == 'test-op-id')` + `| extend duration = iff(isnull(column_ifexists("duration", real(null))), toreal(0), column_ifexists("duration", real(null)))` + `| extend spanID = iff(itemType == "pageView" or isempty(column_ifexists("id", "")), tostring(new_guid()), column_ifexists("id", ""))` + @@ -328,7 +395,7 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) { Resources: []string{"/subscriptions/r1"}, TimeRange: timeRange, QueryType: string(dataquery.AzureQueryTypeAzureTraces), - TraceExploreQuery: `set truncationmaxrecords=10000; set truncationmaxsize=67108864; union isfuzzy=true trace | where ['timestamp'] >= datetime('2018-03-15T13:00:00Z') and ['timestamp'] <= datetime('2018-03-15T13:34:00Z')` + + TraceExploreQuery: `set truncationmaxrecords=10000; set truncationmaxsize=67108864; union isfuzzy=true trace` + `| where (operation_Id != '' and operation_Id == 'test-op-id') or (customDimensions.ai_legacyRootId != '' and customDimensions.ai_legacyRootId == 'test-op-id')` + `| extend duration = iff(isnull(column_ifexists("duration", real(null))), toreal(0), column_ifexists("duration", real(null)))` + `| extend spanID = iff(itemType == "pageView" or isempty(column_ifexists("id", "")), tostring(new_guid()), column_ifexists("id", ""))` + @@ -340,8 +407,7 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) { `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + `| order by startTime asc`, - TraceLogsExploreQuery: "union *, traces, customEvents, pageViews, requests, dependencies, exceptions, customMetrics, availabilityResults \n" + - "| where ['timestamp'] >= datetime('2018-03-15T13:00:00Z') and ['timestamp'] <= datetime('2018-03-15T13:34:00Z') \n" + + TraceLogsExploreQuery: "union availabilityResults,\n" + "customEvents,\n" + "dependencies,\n" + "exceptions,\n" + "pageViews,\n" + "requests,\n" + "traces\n" + "| where operation_Id == \"test-op-id\"", }, }, @@ -375,7 +441,7 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) { "resultFormat": "%s" } }`, dataquery.ResultFormatTable)), - Query: `set truncationmaxrecords=10000; set truncationmaxsize=67108864; union isfuzzy=true availabilityResults,customEvents,dependencies,exceptions,pageViews,requests,traces | where ['timestamp'] >= datetime('2018-03-15T13:00:00Z') and ['timestamp'] <= datetime('2018-03-15T13:34:00Z')` + + Query: `set truncationmaxrecords=10000; set truncationmaxsize=67108864; union isfuzzy=true availabilityResults,customEvents,dependencies,exceptions,pageViews,requests,traces` + `| extend duration = iff(isnull(column_ifexists("duration", real(null))), toreal(0), column_ifexists("duration", real(null)))` + `| extend spanID = iff(itemType == "pageView" or isempty(column_ifexists("id", "")), tostring(new_guid()), column_ifexists("id", ""))` + `| extend operationName = iff(isempty(column_ifexists("name", "")), column_ifexists("problemId", ""), column_ifexists("name", ""))` + @@ -389,7 +455,7 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) { Resources: []string{"/subscriptions/r1"}, TimeRange: timeRange, QueryType: string(dataquery.AzureQueryTypeAzureTraces), - TraceExploreQuery: `set truncationmaxrecords=10000; set truncationmaxsize=67108864; union isfuzzy=true availabilityResults,customEvents,dependencies,exceptions,pageViews,requests,traces | where ['timestamp'] >= datetime('2018-03-15T13:00:00Z') and ['timestamp'] <= datetime('2018-03-15T13:34:00Z')` + + TraceExploreQuery: `set truncationmaxrecords=10000; set truncationmaxsize=67108864; union isfuzzy=true availabilityResults,customEvents,dependencies,exceptions,pageViews,requests,traces` + `| where (operation_Id != '' and operation_Id == '${__data.fields.traceID}') or (customDimensions.ai_legacyRootId != '' and customDimensions.ai_legacyRootId == '${__data.fields.traceID}')` + `| extend duration = iff(isnull(column_ifexists("duration", real(null))), toreal(0), column_ifexists("duration", real(null)))` + `| extend spanID = iff(itemType == "pageView" or isempty(column_ifexists("id", "")), tostring(new_guid()), column_ifexists("id", ""))` + @@ -401,8 +467,7 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) { `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + `| order by startTime asc`, - TraceLogsExploreQuery: "union *, traces, customEvents, pageViews, requests, dependencies, exceptions, customMetrics, availabilityResults \n" + - "| where ['timestamp'] >= datetime('2018-03-15T13:00:00Z') and ['timestamp'] <= datetime('2018-03-15T13:34:00Z') \n" + + TraceLogsExploreQuery: "union availabilityResults,\n" + "customEvents,\n" + "dependencies,\n" + "exceptions,\n" + "pageViews,\n" + "requests,\n" + "traces\n" + "| where operation_Id == \"${__data.fields.traceID}\"", }, }, @@ -438,7 +503,7 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) { "operationId": "test-op-id" } }`, dataquery.ResultFormatTable)), - Query: `set truncationmaxrecords=10000; set truncationmaxsize=67108864; union isfuzzy=true availabilityResults,customEvents,dependencies,exceptions,pageViews,requests,traces | where ['timestamp'] >= datetime('2018-03-15T13:00:00Z') and ['timestamp'] <= datetime('2018-03-15T13:34:00Z')` + + Query: `set truncationmaxrecords=10000; set truncationmaxsize=67108864; union isfuzzy=true availabilityResults,customEvents,dependencies,exceptions,pageViews,requests,traces` + `| where (operation_Id != '' and operation_Id == 'test-op-id') or (customDimensions.ai_legacyRootId != '' and customDimensions.ai_legacyRootId == 'test-op-id')` + `| extend duration = iff(isnull(column_ifexists("duration", real(null))), toreal(0), column_ifexists("duration", real(null)))` + `| extend spanID = iff(itemType == "pageView" or isempty(column_ifexists("id", "")), tostring(new_guid()), column_ifexists("id", ""))` + @@ -453,7 +518,7 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) { Resources: []string{"/subscriptions/r1"}, TimeRange: timeRange, QueryType: string(dataquery.AzureQueryTypeAzureTraces), - TraceExploreQuery: `set truncationmaxrecords=10000; set truncationmaxsize=67108864; union isfuzzy=true availabilityResults,customEvents,dependencies,exceptions,pageViews,requests,traces | where ['timestamp'] >= datetime('2018-03-15T13:00:00Z') and ['timestamp'] <= datetime('2018-03-15T13:34:00Z')` + + TraceExploreQuery: `set truncationmaxrecords=10000; set truncationmaxsize=67108864; union isfuzzy=true availabilityResults,customEvents,dependencies,exceptions,pageViews,requests,traces` + `| where (operation_Id != '' and operation_Id == 'test-op-id') or (customDimensions.ai_legacyRootId != '' and customDimensions.ai_legacyRootId == 'test-op-id')` + `| extend duration = iff(isnull(column_ifexists("duration", real(null))), toreal(0), column_ifexists("duration", real(null)))` + `| extend spanID = iff(itemType == "pageView" or isempty(column_ifexists("id", "")), tostring(new_guid()), column_ifexists("id", ""))` + @@ -465,8 +530,7 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) { `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + `| order by startTime asc`, - TraceLogsExploreQuery: "union *, traces, customEvents, pageViews, requests, dependencies, exceptions, customMetrics, availabilityResults \n" + - "| where ['timestamp'] >= datetime('2018-03-15T13:00:00Z') and ['timestamp'] <= datetime('2018-03-15T13:34:00Z') \n" + + TraceLogsExploreQuery: "union availabilityResults,\n" + "customEvents,\n" + "dependencies,\n" + "exceptions,\n" + "pageViews,\n" + "requests,\n" + "traces\n" + "| where operation_Id == \"test-op-id\"", }, }, @@ -504,7 +568,7 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) { "filters": [{"filters": ["test-app-id"], "property": "appId", "operation": "eq"}] } }`, dataquery.ResultFormatTable)), - Query: `set truncationmaxrecords=10000; set truncationmaxsize=67108864; union isfuzzy=true availabilityResults,customEvents,dependencies,exceptions,pageViews,requests,traces | where ['timestamp'] >= datetime('2018-03-15T13:00:00Z') and ['timestamp'] <= datetime('2018-03-15T13:34:00Z')` + + Query: `set truncationmaxrecords=10000; set truncationmaxsize=67108864; union isfuzzy=true availabilityResults,customEvents,dependencies,exceptions,pageViews,requests,traces` + `| where (operation_Id != '' and operation_Id == 'test-op-id') or (customDimensions.ai_legacyRootId != '' and customDimensions.ai_legacyRootId == 'test-op-id')` + `| extend duration = iff(isnull(column_ifexists("duration", real(null))), toreal(0), column_ifexists("duration", real(null)))` + `| extend spanID = iff(itemType == "pageView" or isempty(column_ifexists("id", "")), tostring(new_guid()), column_ifexists("id", ""))` + @@ -520,7 +584,7 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) { Resources: []string{"/subscriptions/r1"}, TimeRange: timeRange, QueryType: string(dataquery.AzureQueryTypeAzureTraces), - TraceExploreQuery: `set truncationmaxrecords=10000; set truncationmaxsize=67108864; union isfuzzy=true availabilityResults,customEvents,dependencies,exceptions,pageViews,requests,traces | where ['timestamp'] >= datetime('2018-03-15T13:00:00Z') and ['timestamp'] <= datetime('2018-03-15T13:34:00Z')` + + TraceExploreQuery: `set truncationmaxrecords=10000; set truncationmaxsize=67108864; union isfuzzy=true availabilityResults,customEvents,dependencies,exceptions,pageViews,requests,traces` + `| where (operation_Id != '' and operation_Id == 'test-op-id') or (customDimensions.ai_legacyRootId != '' and customDimensions.ai_legacyRootId == 'test-op-id')` + `| extend duration = iff(isnull(column_ifexists("duration", real(null))), toreal(0), column_ifexists("duration", real(null)))` + `| extend spanID = iff(itemType == "pageView" or isempty(column_ifexists("id", "")), tostring(new_guid()), column_ifexists("id", ""))` + @@ -533,8 +597,7 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) { `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + `| order by startTime asc`, - TraceLogsExploreQuery: "union *, traces, customEvents, pageViews, requests, dependencies, exceptions, customMetrics, availabilityResults \n" + - "| where ['timestamp'] >= datetime('2018-03-15T13:00:00Z') and ['timestamp'] <= datetime('2018-03-15T13:34:00Z') \n" + + TraceLogsExploreQuery: "union availabilityResults,\n" + "customEvents,\n" + "dependencies,\n" + "exceptions,\n" + "pageViews,\n" + "requests,\n" + "traces\n" + "| where operation_Id == \"test-op-id\"", }, }, @@ -572,7 +635,7 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) { "filters": [{"filters": ["test-app-id"], "property": "appId", "operation": "ne"}] } }`, dataquery.ResultFormatTable)), - Query: `set truncationmaxrecords=10000; set truncationmaxsize=67108864; union isfuzzy=true availabilityResults,customEvents,dependencies,exceptions,pageViews,requests,traces | where ['timestamp'] >= datetime('2018-03-15T13:00:00Z') and ['timestamp'] <= datetime('2018-03-15T13:34:00Z')` + + Query: `set truncationmaxrecords=10000; set truncationmaxsize=67108864; union isfuzzy=true availabilityResults,customEvents,dependencies,exceptions,pageViews,requests,traces` + `| where (operation_Id != '' and operation_Id == 'test-op-id') or (customDimensions.ai_legacyRootId != '' and customDimensions.ai_legacyRootId == 'test-op-id')` + `| extend duration = iff(isnull(column_ifexists("duration", real(null))), toreal(0), column_ifexists("duration", real(null)))` + `| extend spanID = iff(itemType == "pageView" or isempty(column_ifexists("id", "")), tostring(new_guid()), column_ifexists("id", ""))` + @@ -588,7 +651,7 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) { Resources: []string{"/subscriptions/r1"}, TimeRange: timeRange, QueryType: string(dataquery.AzureQueryTypeAzureTraces), - TraceExploreQuery: `set truncationmaxrecords=10000; set truncationmaxsize=67108864; union isfuzzy=true availabilityResults,customEvents,dependencies,exceptions,pageViews,requests,traces | where ['timestamp'] >= datetime('2018-03-15T13:00:00Z') and ['timestamp'] <= datetime('2018-03-15T13:34:00Z')` + + TraceExploreQuery: `set truncationmaxrecords=10000; set truncationmaxsize=67108864; union isfuzzy=true availabilityResults,customEvents,dependencies,exceptions,pageViews,requests,traces` + `| where (operation_Id != '' and operation_Id == 'test-op-id') or (customDimensions.ai_legacyRootId != '' and customDimensions.ai_legacyRootId == 'test-op-id')` + `| extend duration = iff(isnull(column_ifexists("duration", real(null))), toreal(0), column_ifexists("duration", real(null)))` + `| extend spanID = iff(itemType == "pageView" or isempty(column_ifexists("id", "")), tostring(new_guid()), column_ifexists("id", ""))` + @@ -601,8 +664,7 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) { `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + `| order by startTime asc`, - TraceLogsExploreQuery: "union *, traces, customEvents, pageViews, requests, dependencies, exceptions, customMetrics, availabilityResults \n" + - "| where ['timestamp'] >= datetime('2018-03-15T13:00:00Z') and ['timestamp'] <= datetime('2018-03-15T13:34:00Z') \n" + + TraceLogsExploreQuery: "union availabilityResults,\n" + "customEvents,\n" + "dependencies,\n" + "exceptions,\n" + "pageViews,\n" + "requests,\n" + "traces\n" + "| where operation_Id == \"test-op-id\"", }, }, @@ -640,7 +702,7 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) { "filters": [{"filters": ["test-app-id"], "property": "appId", "operation": "ne"},{"filters": ["test-client-id"], "property": "clientId", "operation": "eq"}] } }`, dataquery.ResultFormatTable)), - Query: `set truncationmaxrecords=10000; set truncationmaxsize=67108864; union isfuzzy=true availabilityResults,customEvents,dependencies,exceptions,pageViews,requests,traces | where ['timestamp'] >= datetime('2018-03-15T13:00:00Z') and ['timestamp'] <= datetime('2018-03-15T13:34:00Z')` + + Query: `set truncationmaxrecords=10000; set truncationmaxsize=67108864; union isfuzzy=true availabilityResults,customEvents,dependencies,exceptions,pageViews,requests,traces` + `| where (operation_Id != '' and operation_Id == 'test-op-id') or (customDimensions.ai_legacyRootId != '' and customDimensions.ai_legacyRootId == 'test-op-id')` + `| extend duration = iff(isnull(column_ifexists("duration", real(null))), toreal(0), column_ifexists("duration", real(null)))` + `| extend spanID = iff(itemType == "pageView" or isempty(column_ifexists("id", "")), tostring(new_guid()), column_ifexists("id", ""))` + @@ -656,7 +718,7 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) { Resources: []string{"/subscriptions/r1"}, TimeRange: timeRange, QueryType: string(dataquery.AzureQueryTypeAzureTraces), - TraceExploreQuery: `set truncationmaxrecords=10000; set truncationmaxsize=67108864; union isfuzzy=true availabilityResults,customEvents,dependencies,exceptions,pageViews,requests,traces | where ['timestamp'] >= datetime('2018-03-15T13:00:00Z') and ['timestamp'] <= datetime('2018-03-15T13:34:00Z')` + + TraceExploreQuery: `set truncationmaxrecords=10000; set truncationmaxsize=67108864; union isfuzzy=true availabilityResults,customEvents,dependencies,exceptions,pageViews,requests,traces` + `| where (operation_Id != '' and operation_Id == 'test-op-id') or (customDimensions.ai_legacyRootId != '' and customDimensions.ai_legacyRootId == 'test-op-id')` + `| extend duration = iff(isnull(column_ifexists("duration", real(null))), toreal(0), column_ifexists("duration", real(null)))` + `| extend spanID = iff(itemType == "pageView" or isempty(column_ifexists("id", "")), tostring(new_guid()), column_ifexists("id", ""))` + @@ -669,8 +731,7 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) { `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + `| order by startTime asc`, - TraceLogsExploreQuery: "union *, traces, customEvents, pageViews, requests, dependencies, exceptions, customMetrics, availabilityResults \n" + - "| where ['timestamp'] >= datetime('2018-03-15T13:00:00Z') and ['timestamp'] <= datetime('2018-03-15T13:34:00Z') \n" + + TraceLogsExploreQuery: "union availabilityResults,\n" + "customEvents,\n" + "dependencies,\n" + "exceptions,\n" + "pageViews,\n" + "requests,\n" + "traces\n" + "| where operation_Id == \"test-op-id\"", }, }, @@ -704,7 +765,7 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) { "resultFormat": "%s" } }`, dataquery.ResultFormatTrace)), - Query: `set truncationmaxrecords=10000; set truncationmaxsize=67108864; union isfuzzy=true availabilityResults,customEvents,dependencies,exceptions,pageViews,requests | where ['timestamp'] >= datetime('2018-03-15T13:00:00Z') and ['timestamp'] <= datetime('2018-03-15T13:34:00Z')` + + Query: `set truncationmaxrecords=10000; set truncationmaxsize=67108864; union isfuzzy=true availabilityResults,customEvents,dependencies,exceptions,pageViews,requests` + `| extend duration = iff(isnull(column_ifexists("duration", real(null))), toreal(0), column_ifexists("duration", real(null)))` + `| extend spanID = iff(itemType == "pageView" or isempty(column_ifexists("id", "")), tostring(new_guid()), column_ifexists("id", ""))` + `| extend operationName = iff(isempty(column_ifexists("name", "")), column_ifexists("problemId", ""), column_ifexists("name", ""))` + @@ -718,7 +779,7 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) { Resources: []string{"/subscriptions/r1"}, TimeRange: timeRange, QueryType: string(dataquery.AzureQueryTypeAzureTraces), - TraceExploreQuery: `set truncationmaxrecords=10000; set truncationmaxsize=67108864; union isfuzzy=true availabilityResults,customEvents,dependencies,exceptions,pageViews,requests,traces | where ['timestamp'] >= datetime('2018-03-15T13:00:00Z') and ['timestamp'] <= datetime('2018-03-15T13:34:00Z')` + + TraceExploreQuery: `set truncationmaxrecords=10000; set truncationmaxsize=67108864; union isfuzzy=true availabilityResults,customEvents,dependencies,exceptions,pageViews,requests` + `| where (operation_Id != '' and operation_Id == '${__data.fields.traceID}') or (customDimensions.ai_legacyRootId != '' and customDimensions.ai_legacyRootId == '${__data.fields.traceID}')` + `| extend duration = iff(isnull(column_ifexists("duration", real(null))), toreal(0), column_ifexists("duration", real(null)))` + `| extend spanID = iff(itemType == "pageView" or isempty(column_ifexists("id", "")), tostring(new_guid()), column_ifexists("id", ""))` + @@ -730,8 +791,7 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) { `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + `| order by startTime asc`, - TraceLogsExploreQuery: "union *, traces, customEvents, pageViews, requests, dependencies, exceptions, customMetrics, availabilityResults \n" + - "| where ['timestamp'] >= datetime('2018-03-15T13:00:00Z') and ['timestamp'] <= datetime('2018-03-15T13:34:00Z') \n" + + TraceLogsExploreQuery: "union availabilityResults,\n" + "customEvents,\n" + "dependencies,\n" + "exceptions,\n" + "pageViews,\n" + "requests,\n" + "traces\n" + "| where operation_Id == \"${__data.fields.traceID}\"", }, }, @@ -767,7 +827,7 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) { "resultFormat": "%s" } }`, dataquery.ResultFormatTrace)), - Query: `set truncationmaxrecords=10000; set truncationmaxsize=67108864; union isfuzzy=true availabilityResults,customEvents,dependencies,exceptions,pageViews,requests | where ['timestamp'] >= datetime('2018-03-15T13:00:00Z') and ['timestamp'] <= datetime('2018-03-15T13:34:00Z')` + + Query: `set truncationmaxrecords=10000; set truncationmaxsize=67108864; union isfuzzy=true availabilityResults,customEvents,dependencies,exceptions,pageViews,requests` + `| where (operation_Id != '' and operation_Id == 'test-op-id') or (customDimensions.ai_legacyRootId != '' and customDimensions.ai_legacyRootId == 'test-op-id')` + `| extend duration = iff(isnull(column_ifexists("duration", real(null))), toreal(0), column_ifexists("duration", real(null)))` + `| extend spanID = iff(itemType == "pageView" or isempty(column_ifexists("id", "")), tostring(new_guid()), column_ifexists("id", ""))` + @@ -782,7 +842,7 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) { Resources: []string{"/subscriptions/r1"}, TimeRange: timeRange, QueryType: string(dataquery.AzureQueryTypeAzureTraces), - TraceExploreQuery: `set truncationmaxrecords=10000; set truncationmaxsize=67108864; union isfuzzy=true availabilityResults,customEvents,dependencies,exceptions,pageViews,requests | where ['timestamp'] >= datetime('2018-03-15T13:00:00Z') and ['timestamp'] <= datetime('2018-03-15T13:34:00Z')` + + TraceExploreQuery: `set truncationmaxrecords=10000; set truncationmaxsize=67108864; union isfuzzy=true availabilityResults,customEvents,dependencies,exceptions,pageViews,requests` + `| where (operation_Id != '' and operation_Id == 'test-op-id') or (customDimensions.ai_legacyRootId != '' and customDimensions.ai_legacyRootId == 'test-op-id')` + `| extend duration = iff(isnull(column_ifexists("duration", real(null))), toreal(0), column_ifexists("duration", real(null)))` + `| extend spanID = iff(itemType == "pageView" or isempty(column_ifexists("id", "")), tostring(new_guid()), column_ifexists("id", ""))` + @@ -794,8 +854,7 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) { `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + `| order by startTime asc`, - TraceLogsExploreQuery: "union *, traces, customEvents, pageViews, requests, dependencies, exceptions, customMetrics, availabilityResults \n" + - "| where ['timestamp'] >= datetime('2018-03-15T13:00:00Z') and ['timestamp'] <= datetime('2018-03-15T13:34:00Z') \n" + + TraceLogsExploreQuery: "union availabilityResults,\n" + "customEvents,\n" + "dependencies,\n" + "exceptions,\n" + "pageViews,\n" + "requests,\n" + "traces\n" + "| where operation_Id == \"test-op-id\"", }, }, @@ -838,18 +897,293 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) { TimeRange: timeRange, QueryType: string(dataquery.AzureQueryTypeAzureTraces), TraceExploreQuery: "", - TraceLogsExploreQuery: "union *, traces, customEvents, pageViews, requests, dependencies, exceptions, customMetrics, availabilityResults \n" + - "| where ['timestamp'] >= datetime('2018-03-15T13:00:00Z') and ['timestamp'] <= datetime('2018-03-15T13:34:00Z') \n" + + TraceLogsExploreQuery: "union availabilityResults,\n" + "customEvents,\n" + "dependencies,\n" + "exceptions,\n" + "pageViews,\n" + "requests,\n" + "traces\n" + "| where operation_Id == \"test-op-id\"", }, }, Err: require.NoError, }, + { + name: "trace query with operation ID and correlated workspaces", + queryModel: []backend.DataQuery{ + { + JSON: []byte(fmt.Sprintf(`{ + "queryType": "Azure Traces", + "azureTraces": { + "operationId": "op-id-multi", + "resources": ["/subscriptions/r1"], + "resultFormat": "%s" + } + }`, dataquery.ResultFormatTrace)), + RefID: "A", + TimeRange: timeRange, + QueryType: string(dataquery.AzureQueryTypeAzureTraces), + }, + }, + azureLogAnalyticsQueries: []*AzureLogAnalyticsQuery{ + { + RefID: "A", + ResultFormat: string(dataquery.ResultFormatTrace), + URL: "v1/subscriptions/r1/query", + JSON: []byte(fmt.Sprintf(`{ + "queryType": "Azure Traces", + "azureTraces": { + "operationId": "op-id-multi", + "resources": ["/subscriptions/r1"], + "resultFormat": "%s" + } + }`, dataquery.ResultFormatTrace)), + Query: `set truncationmaxrecords=10000; set truncationmaxsize=67108864; union isfuzzy=true availabilityResults,customEvents,dependencies,exceptions,pageViews,requests,app('r2').availabilityResults,app('r2').customEvents,app('r2').dependencies,app('r2').exceptions,app('r2').pageViews,app('r2').requests` + + `| where (operation_Id != '' and operation_Id == 'op-id-multi') or (customDimensions.ai_legacyRootId != '' and customDimensions.ai_legacyRootId == 'op-id-multi')` + + `| extend duration = iff(isnull(column_ifexists("duration", real(null))), toreal(0), column_ifexists("duration", real(null)))` + + `| extend spanID = iff(itemType == "pageView" or isempty(column_ifexists("id", "")), tostring(new_guid()), column_ifexists("id", ""))` + + `| extend operationName = iff(isempty(column_ifexists("name", "")), column_ifexists("problemId", ""), column_ifexists("name", ""))` + + `| extend serviceName = cloud_RoleName| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` + + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + + `| extend error = iff(itemType == "exceptions", true, false)` + + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + + `| order by startTime asc`, + Resources: []string{"/subscriptions/r1"}, + TimeRange: timeRange, + QueryType: string(dataquery.AzureQueryTypeAzureTraces), + TraceExploreQuery: `set truncationmaxrecords=10000; set truncationmaxsize=67108864; union isfuzzy=true availabilityResults,customEvents,dependencies,exceptions,pageViews,requests,app('r2').availabilityResults,app('r2').customEvents,app('r2').dependencies,app('r2').exceptions,app('r2').pageViews,app('r2').requests` + + `| where (operation_Id != '' and operation_Id == 'op-id-multi') or (customDimensions.ai_legacyRootId != '' and customDimensions.ai_legacyRootId == 'op-id-multi')` + + `| extend duration = iff(isnull(column_ifexists("duration", real(null))), toreal(0), column_ifexists("duration", real(null)))` + + `| extend spanID = iff(itemType == "pageView" or isempty(column_ifexists("id", "")), tostring(new_guid()), column_ifexists("id", ""))` + + `| extend operationName = iff(isempty(column_ifexists("name", "")), column_ifexists("problemId", ""), column_ifexists("name", ""))` + + `| extend serviceName = cloud_RoleName| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` + + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + + `| extend error = iff(itemType == "exceptions", true, false)` + + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + + `| order by startTime asc`, + TraceLogsExploreQuery: "union *,\n" + + "app('r2').availabilityResults,\n" + + "app('r2').customEvents,\n" + + "app('r2').dependencies,\n" + + "app('r2').exceptions,\n" + + "app('r2').pageViews,\n" + + "app('r2').requests,\n" + + "app('r2').traces\n" + + "| where operation_Id == \"op-id-multi\"", + }, + }, + Err: require.NoError, + }, + { + name: "trace query with multiple resources", + queryModel: []backend.DataQuery{ + { + JSON: []byte(fmt.Sprintf(`{ + "queryType": "Azure Traces", + "azureTraces": { + "resources": ["/subscriptions/r1", "/subscriptions/r2"], + "resultFormat": "%s" + } + }`, dataquery.ResultFormatTrace)), + RefID: "A", + TimeRange: timeRange, + QueryType: string(dataquery.AzureQueryTypeAzureTraces), + }, + }, + azureLogAnalyticsQueries: []*AzureLogAnalyticsQuery{ + { + RefID: "A", + ResultFormat: string(dataquery.ResultFormatTrace), + URL: "v1/subscriptions/r1/query", + JSON: []byte(fmt.Sprintf(`{ + "queryType": "Azure Traces", + "azureTraces": { + "resources": ["/subscriptions/r1", "/subscriptions/r2"], + "resultFormat": "%s" + } + }`, dataquery.ResultFormatTrace)), + Query: `set truncationmaxrecords=10000; set truncationmaxsize=67108864; union isfuzzy=true availabilityResults,customEvents,dependencies,exceptions,pageViews,requests,app('r2').availabilityResults,app('r2').customEvents,app('r2').dependencies,app('r2').exceptions,app('r2').pageViews,app('r2').requests` + + `| extend duration = iff(isnull(column_ifexists("duration", real(null))), toreal(0), column_ifexists("duration", real(null)))` + + `| extend spanID = iff(itemType == "pageView" or isempty(column_ifexists("id", "")), tostring(new_guid()), column_ifexists("id", ""))` + + `| extend operationName = iff(isempty(column_ifexists("name", "")), column_ifexists("problemId", ""), column_ifexists("name", ""))` + + `| extend serviceName = cloud_RoleName| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` + + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + + `| extend error = iff(itemType == "exceptions", true, false)` + + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + + `| order by startTime asc`, + Resources: []string{"/subscriptions/r1", "/subscriptions/r2"}, + TimeRange: timeRange, + QueryType: string(dataquery.AzureQueryTypeAzureTraces), + TraceExploreQuery: `set truncationmaxrecords=10000; set truncationmaxsize=67108864; union isfuzzy=true availabilityResults,customEvents,dependencies,exceptions,pageViews,requests,app('r2').availabilityResults,app('r2').customEvents,app('r2').dependencies,app('r2').exceptions,app('r2').pageViews,app('r2').requests` + + `| where (operation_Id != '' and operation_Id == '${__data.fields.traceID}') or (customDimensions.ai_legacyRootId != '' and customDimensions.ai_legacyRootId == '${__data.fields.traceID}')` + + `| extend duration = iff(isnull(column_ifexists("duration", real(null))), toreal(0), column_ifexists("duration", real(null)))` + + `| extend spanID = iff(itemType == "pageView" or isempty(column_ifexists("id", "")), tostring(new_guid()), column_ifexists("id", ""))` + + `| extend operationName = iff(isempty(column_ifexists("name", "")), column_ifexists("problemId", ""), column_ifexists("name", ""))` + + `| extend serviceName = cloud_RoleName| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` + + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + + `| extend error = iff(itemType == "exceptions", true, false)` + + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + + `| order by startTime asc`, + TraceLogsExploreQuery: "union *,\n" + + "app('r2').availabilityResults,\n" + + "app('r2').customEvents,\n" + + "app('r2').dependencies,\n" + + "app('r2').exceptions,\n" + + "app('r2').pageViews,\n" + + "app('r2').requests,\n" + + "app('r2').traces\n" + + "| where operation_Id == \"${__data.fields.traceID}\"", + }, + }, + Err: require.NoError, + }, + { + name: "trace query with multiple resources and overlapping correlated workspaces", + queryModel: []backend.DataQuery{ + { + JSON: []byte(fmt.Sprintf(`{ + "queryType": "Azure Traces", + "azureTraces": { + "operationId": "op-id-multi", + "resources": ["/subscriptions/r1", "/subscriptions/r2"], + "resultFormat": "%s" + } + }`, dataquery.ResultFormatTrace)), + RefID: "A", + TimeRange: timeRange, + QueryType: string(dataquery.AzureQueryTypeAzureTraces), + }, + }, + azureLogAnalyticsQueries: []*AzureLogAnalyticsQuery{ + { + RefID: "A", + ResultFormat: string(dataquery.ResultFormatTrace), + URL: "v1/subscriptions/r1/query", + JSON: []byte(fmt.Sprintf(`{ + "queryType": "Azure Traces", + "azureTraces": { + "operationId": "op-id-multi", + "resources": ["/subscriptions/r1", "/subscriptions/r2"], + "resultFormat": "%s" + } + }`, dataquery.ResultFormatTrace)), + Query: `set truncationmaxrecords=10000; set truncationmaxsize=67108864; union isfuzzy=true availabilityResults,customEvents,dependencies,exceptions,pageViews,requests,app('r2').availabilityResults,app('r2').customEvents,app('r2').dependencies,app('r2').exceptions,app('r2').pageViews,app('r2').requests` + + `| where (operation_Id != '' and operation_Id == 'op-id-multi') or (customDimensions.ai_legacyRootId != '' and customDimensions.ai_legacyRootId == 'op-id-multi')` + + `| extend duration = iff(isnull(column_ifexists("duration", real(null))), toreal(0), column_ifexists("duration", real(null)))` + + `| extend spanID = iff(itemType == "pageView" or isempty(column_ifexists("id", "")), tostring(new_guid()), column_ifexists("id", ""))` + + `| extend operationName = iff(isempty(column_ifexists("name", "")), column_ifexists("problemId", ""), column_ifexists("name", ""))` + + `| extend serviceName = cloud_RoleName| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` + + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + + `| extend error = iff(itemType == "exceptions", true, false)` + + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + + `| order by startTime asc`, + Resources: []string{"/subscriptions/r1", "/subscriptions/r2"}, + TimeRange: timeRange, + QueryType: string(dataquery.AzureQueryTypeAzureTraces), + TraceExploreQuery: `set truncationmaxrecords=10000; set truncationmaxsize=67108864; union isfuzzy=true availabilityResults,customEvents,dependencies,exceptions,pageViews,requests,app('r2').availabilityResults,app('r2').customEvents,app('r2').dependencies,app('r2').exceptions,app('r2').pageViews,app('r2').requests` + + `| where (operation_Id != '' and operation_Id == 'op-id-multi') or (customDimensions.ai_legacyRootId != '' and customDimensions.ai_legacyRootId == 'op-id-multi')` + + `| extend duration = iff(isnull(column_ifexists("duration", real(null))), toreal(0), column_ifexists("duration", real(null)))` + + `| extend spanID = iff(itemType == "pageView" or isempty(column_ifexists("id", "")), tostring(new_guid()), column_ifexists("id", ""))` + + `| extend operationName = iff(isempty(column_ifexists("name", "")), column_ifexists("problemId", ""), column_ifexists("name", ""))` + + `| extend serviceName = cloud_RoleName| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` + + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + + `| extend error = iff(itemType == "exceptions", true, false)` + + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + + `| order by startTime asc`, + TraceLogsExploreQuery: "union *,\n" + + "app('r2').availabilityResults,\n" + + "app('r2').customEvents,\n" + + "app('r2').dependencies,\n" + + "app('r2').exceptions,\n" + + "app('r2').pageViews,\n" + + "app('r2').requests,\n" + + "app('r2').traces\n" + + "| where operation_Id == \"op-id-multi\"", + }, + }, + Err: require.NoError, + }, + { + name: "trace query with multiple resources and non-overlapping correlated workspaces", + queryModel: []backend.DataQuery{ + { + JSON: []byte(fmt.Sprintf(`{ + "queryType": "Azure Traces", + "azureTraces": { + "operationId": "op-id-non-overlapping", + "resources": ["/subscriptions/r1", "/subscriptions/r2"], + "resultFormat": "%s" + } + }`, dataquery.ResultFormatTrace)), + RefID: "A", + TimeRange: timeRange, + QueryType: string(dataquery.AzureQueryTypeAzureTraces), + }, + }, + azureLogAnalyticsQueries: []*AzureLogAnalyticsQuery{ + { + RefID: "A", + ResultFormat: string(dataquery.ResultFormatTrace), + URL: "v1/subscriptions/r1/query", + JSON: []byte(fmt.Sprintf(`{ + "queryType": "Azure Traces", + "azureTraces": { + "operationId": "op-id-non-overlapping", + "resources": ["/subscriptions/r1", "/subscriptions/r2"], + "resultFormat": "%s" + } + }`, dataquery.ResultFormatTrace)), + Query: `set truncationmaxrecords=10000; set truncationmaxsize=67108864; union isfuzzy=true availabilityResults,customEvents,dependencies,exceptions,pageViews,requests,app('r2').availabilityResults,app('r2').customEvents,app('r2').dependencies,app('r2').exceptions,app('r2').pageViews,app('r2').requests,app('r3').availabilityResults,app('r3').customEvents,app('r3').dependencies,app('r3').exceptions,app('r3').pageViews,app('r3').requests` + + `| where (operation_Id != '' and operation_Id == 'op-id-non-overlapping') or (customDimensions.ai_legacyRootId != '' and customDimensions.ai_legacyRootId == 'op-id-non-overlapping')` + + `| extend duration = iff(isnull(column_ifexists("duration", real(null))), toreal(0), column_ifexists("duration", real(null)))` + + `| extend spanID = iff(itemType == "pageView" or isempty(column_ifexists("id", "")), tostring(new_guid()), column_ifexists("id", ""))` + + `| extend operationName = iff(isempty(column_ifexists("name", "")), column_ifexists("problemId", ""), column_ifexists("name", ""))` + + `| extend serviceName = cloud_RoleName| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` + + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + + `| extend error = iff(itemType == "exceptions", true, false)` + + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + + `| order by startTime asc`, + Resources: []string{"/subscriptions/r1", "/subscriptions/r2"}, + TimeRange: timeRange, + QueryType: string(dataquery.AzureQueryTypeAzureTraces), + TraceExploreQuery: `set truncationmaxrecords=10000; set truncationmaxsize=67108864; union isfuzzy=true availabilityResults,customEvents,dependencies,exceptions,pageViews,requests,app('r2').availabilityResults,app('r2').customEvents,app('r2').dependencies,app('r2').exceptions,app('r2').pageViews,app('r2').requests,app('r3').availabilityResults,app('r3').customEvents,app('r3').dependencies,app('r3').exceptions,app('r3').pageViews,app('r3').requests` + + `| where (operation_Id != '' and operation_Id == 'op-id-non-overlapping') or (customDimensions.ai_legacyRootId != '' and customDimensions.ai_legacyRootId == 'op-id-non-overlapping')` + + `| extend duration = iff(isnull(column_ifexists("duration", real(null))), toreal(0), column_ifexists("duration", real(null)))` + + `| extend spanID = iff(itemType == "pageView" or isempty(column_ifexists("id", "")), tostring(new_guid()), column_ifexists("id", ""))` + + `| extend operationName = iff(isempty(column_ifexists("name", "")), column_ifexists("problemId", ""), column_ifexists("name", ""))` + + `| extend serviceName = cloud_RoleName| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` + + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + + `| extend error = iff(itemType == "exceptions", true, false)` + + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + + `| order by startTime asc`, + TraceLogsExploreQuery: "union *,\n" + + "app('r2').availabilityResults,\n" + + "app('r2').customEvents,\n" + + "app('r2').dependencies,\n" + + "app('r2').exceptions,\n" + + "app('r2').pageViews,\n" + + "app('r2').requests,\n" + + "app('r2').traces,\n" + + "app('r3').availabilityResults,\n" + + "app('r3').customEvents,\n" + + "app('r3').dependencies,\n" + + "app('r3').exceptions,\n" + + "app('r3').pageViews,\n" + + "app('r3').requests,\n" + + "app('r3').traces\n" + + "| where operation_Id == \"op-id-non-overlapping\"", + }, + }, + Err: require.NoError, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - queries, err := datasource.buildQueries(logger, tt.queryModel, types.DatasourceInfo{}) + queries, err := datasource.buildQueries(ctx, logger, tt.queryModel, dsInfo, tracer) tt.Err(t, err) if diff := cmp.Diff(tt.azureLogAnalyticsQueries[0], queries[0]); diff != "" { t.Errorf("Result mismatch (-want +got): \n%s", diff) From 18d4fda6132ec5ec4d7de32e3e425c702a8845b5 Mon Sep 17 00:00:00 2001 From: Andriy Date: Tue, 9 May 2023 16:58:10 +0200 Subject: [PATCH 076/179] Fix Enterprise Datasources link on Grafana Enterprise page (#67962) fix-enterprise-datasources-link --- docs/sources/introduction/grafana-enterprise.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/introduction/grafana-enterprise.md b/docs/sources/introduction/grafana-enterprise.md index be9b0f590dd..b70735815ec 100644 --- a/docs/sources/introduction/grafana-enterprise.md +++ b/docs/sources/introduction/grafana-enterprise.md @@ -13,7 +13,7 @@ weight: 200 Grafana Enterprise is a commercial edition of Grafana that includes additional features not found in the open source version. -Building on everything you already know and love about Grafana open source, Grafana Enterprise includes [exclusive datasource plugins]({{< relref "#enterprise-plugins">}}) and [additional features]({{< relref "#enterprise-features">}}). You also get 24x7x365 support and training from the core Grafana team. +Building on everything you already know and love about Grafana open source, Grafana Enterprise includes [exclusive datasource plugins]({{< relref "#enterprise-data-sources">}}) and [additional features]({{< relref "#enterprise-features">}}). You also get 24x7x365 support and training from the core Grafana team. To learn more about Grafana Enterprise, refer to [our product page](https://grafana.com/enterprise). From 5d047160e9422dd4377bcd4cf63876677b0bf6c0 Mon Sep 17 00:00:00 2001 From: Gilles De Mey Date: Tue, 9 May 2023 17:58:15 +0200 Subject: [PATCH 077/179] Alerting: Update contact point state semantics (#67994) --- .../src/components/Icon/cached.json | 1 + .../src/components/Icon/iconBundle.ts | 422 +++++++++--------- .../features/alerting/unified/Receivers.tsx | 19 +- .../components/receivers/ReceiversTable.tsx | 51 ++- 4 files changed, 252 insertions(+), 241 deletions(-) diff --git a/packages/grafana-ui/src/components/Icon/cached.json b/packages/grafana-ui/src/components/Icon/cached.json index 4f62efd4552..781f0d9c845 100644 --- a/packages/grafana-ui/src/components/Icon/cached.json +++ b/packages/grafana-ui/src/components/Icon/cached.json @@ -60,6 +60,7 @@ "unicons/ellipsis-h", "unicons/envelope", "unicons/exchange-alt", + "unicons/exclamation-circle", "unicons/exclamation-triangle", "unicons/external-link-alt", "unicons/eye", diff --git a/packages/grafana-ui/src/components/Icon/iconBundle.ts b/packages/grafana-ui/src/components/Icon/iconBundle.ts index 24682bce731..675871f8201 100644 --- a/packages/grafana-ui/src/components/Icon/iconBundle.ts +++ b/packages/grafana-ui/src/components/Icon/iconBundle.ts @@ -68,111 +68,112 @@ import u1057 from '../../../../../public/img/icons/unicons/ellipsis-v.svg'; import u1058 from '../../../../../public/img/icons/unicons/ellipsis-h.svg'; import u1059 from '../../../../../public/img/icons/unicons/envelope.svg'; import u1060 from '../../../../../public/img/icons/unicons/exchange-alt.svg'; -import u1061 from '../../../../../public/img/icons/unicons/exclamation-triangle.svg'; -import u1062 from '../../../../../public/img/icons/unicons/external-link-alt.svg'; -import u1063 from '../../../../../public/img/icons/unicons/eye.svg'; -import u1064 from '../../../../../public/img/icons/unicons/eye-slash.svg'; -import u1065 from '../../../../../public/img/icons/unicons/file-alt.svg'; -import u1066 from '../../../../../public/img/icons/unicons/file-blank.svg'; -import u1067 from '../../../../../public/img/icons/unicons/filter.svg'; -import u1068 from '../../../../../public/img/icons/unicons/folder.svg'; -import u1069 from '../../../../../public/img/icons/unicons/folder-open.svg'; -import u1070 from '../../../../../public/img/icons/unicons/folder-plus.svg'; -import u1071 from '../../../../../public/img/icons/unicons/folder-upload.svg'; -import u1072 from '../../../../../public/img/icons/unicons/forward.svg'; -import u1073 from '../../../../../public/img/icons/unicons/graph-bar.svg'; -import u1074 from '../../../../../public/img/icons/unicons/history.svg'; -import u1075 from '../../../../../public/img/icons/unicons/home-alt.svg'; -import u1076 from '../../../../../public/img/icons/unicons/import.svg'; -import u1077 from '../../../../../public/img/icons/unicons/info.svg'; -import u1078 from '../../../../../public/img/icons/unicons/info-circle.svg'; -import u1079 from '../../../../../public/img/icons/unicons/k6.svg'; -import u1080 from '../../../../../public/img/icons/unicons/key-skeleton-alt.svg'; -import u1081 from '../../../../../public/img/icons/unicons/keyboard.svg'; -import u1082 from '../../../../../public/img/icons/unicons/link.svg'; -import u1083 from '../../../../../public/img/icons/unicons/list-ul.svg'; -import u1084 from '../../../../../public/img/icons/unicons/lock.svg'; -import u1085 from '../../../../../public/img/icons/unicons/minus.svg'; -import u1086 from '../../../../../public/img/icons/unicons/minus-circle.svg'; -import u1087 from '../../../../../public/img/icons/unicons/mobile-android.svg'; -import u1088 from '../../../../../public/img/icons/unicons/monitor.svg'; -import u1089 from '../../../../../public/img/icons/unicons/pause.svg'; -import u1090 from '../../../../../public/img/icons/unicons/pen.svg'; -import u1091 from '../../../../../public/img/icons/unicons/play.svg'; -import u1092 from '../../../../../public/img/icons/unicons/plug.svg'; -import u1093 from '../../../../../public/img/icons/unicons/plus.svg'; -import u1094 from '../../../../../public/img/icons/unicons/plus-circle.svg'; -import u1095 from '../../../../../public/img/icons/unicons/power.svg'; -import u1096 from '../../../../../public/img/icons/unicons/presentation-play.svg'; -import u1097 from '../../../../../public/img/icons/unicons/process.svg'; -import u1098 from '../../../../../public/img/icons/unicons/question-circle.svg'; -import u1099 from '../../../../../public/img/icons/unicons/repeat.svg'; -import u1100 from '../../../../../public/img/icons/unicons/rocket.svg'; -import u1101 from '../../../../../public/img/icons/unicons/rss.svg'; -import u1102 from '../../../../../public/img/icons/unicons/save.svg'; -import u1103 from '../../../../../public/img/icons/unicons/search.svg'; -import u1104 from '../../../../../public/img/icons/unicons/search-minus.svg'; -import u1105 from '../../../../../public/img/icons/unicons/search-plus.svg'; -import u1106 from '../../../../../public/img/icons/unicons/share-alt.svg'; -import u1107 from '../../../../../public/img/icons/unicons/shield.svg'; -import u1108 from '../../../../../public/img/icons/unicons/signal.svg'; -import u1109 from '../../../../../public/img/icons/unicons/signin.svg'; -import u1110 from '../../../../../public/img/icons/unicons/signout.svg'; -import u1111 from '../../../../../public/img/icons/unicons/sitemap.svg'; -import u1112 from '../../../../../public/img/icons/unicons/slack.svg'; -import u1113 from '../../../../../public/img/icons/unicons/sliders-v-alt.svg'; -import u1114 from '../../../../../public/img/icons/unicons/sort-amount-down.svg'; -import u1115 from '../../../../../public/img/icons/unicons/sort-amount-up.svg'; -import u1116 from '../../../../../public/img/icons/unicons/square-shape.svg'; -import u1117 from '../../../../../public/img/icons/unicons/star.svg'; -import u1118 from '../../../../../public/img/icons/unicons/step-backward.svg'; -import u1119 from '../../../../../public/img/icons/unicons/sync.svg'; -import u1120 from '../../../../../public/img/icons/unicons/table.svg'; -import u1121 from '../../../../../public/img/icons/unicons/tag-alt.svg'; -import u1122 from '../../../../../public/img/icons/unicons/times.svg'; -import u1123 from '../../../../../public/img/icons/unicons/trash-alt.svg'; -import u1124 from '../../../../../public/img/icons/unicons/unlock.svg'; -import u1125 from '../../../../../public/img/icons/unicons/upload.svg'; -import u1126 from '../../../../../public/img/icons/unicons/user.svg'; -import u1127 from '../../../../../public/img/icons/unicons/users-alt.svg'; -import u1128 from '../../../../../public/img/icons/unicons/wrap-text.svg'; -import u1129 from '../../../../../public/img/icons/unicons/cloud-upload.svg'; -import u1130 from '../../../../../public/img/icons/unicons/credit-card.svg'; -import u1131 from '../../../../../public/img/icons/unicons/file-copy-alt.svg'; -import u1132 from '../../../../../public/img/icons/unicons/fire.svg'; -import u1133 from '../../../../../public/img/icons/unicons/hourglass.svg'; -import u1134 from '../../../../../public/img/icons/unicons/layer-group.svg'; -import u1135 from '../../../../../public/img/icons/unicons/layers-alt.svg'; -import u1136 from '../../../../../public/img/icons/unicons/line-alt.svg'; -import u1137 from '../../../../../public/img/icons/unicons/list-ui-alt.svg'; -import u1138 from '../../../../../public/img/icons/unicons/message.svg'; -import u1139 from '../../../../../public/img/icons/unicons/palette.svg'; -import u1140 from '../../../../../public/img/icons/unicons/percentage.svg'; -import u1141 from '../../../../../public/img/icons/unicons/shield-exclamation.svg'; -import u1142 from '../../../../../public/img/icons/unicons/plus-square.svg'; -import u1143 from '../../../../../public/img/icons/unicons/x.svg'; -import u1144 from '../../../../../public/img/icons/unicons/capture.svg'; -import u1145 from '../../../../../public/img/icons/custom/gf-grid.svg'; -import u1146 from '../../../../../public/img/icons/custom/gf-landscape.svg'; -import u1147 from '../../../../../public/img/icons/custom/gf-layout-simple.svg'; -import u1148 from '../../../../../public/img/icons/custom/gf-portrait.svg'; -import u1149 from '../../../../../public/img/icons/custom/gf-bar-alignment-after.svg'; -import u1150 from '../../../../../public/img/icons/custom/gf-bar-alignment-before.svg'; -import u1151 from '../../../../../public/img/icons/custom/gf-bar-alignment-center.svg'; -import u1152 from '../../../../../public/img/icons/custom/gf-interpolation-linear.svg'; -import u1153 from '../../../../../public/img/icons/custom/gf-interpolation-smooth.svg'; -import u1154 from '../../../../../public/img/icons/custom/gf-interpolation-step-after.svg'; -import u1155 from '../../../../../public/img/icons/custom/gf-interpolation-step-before.svg'; -import u1156 from '../../../../../public/img/icons/custom/gf-logs.svg'; -import u1157 from '../../../../../public/img/icons/custom/gf-movepane-left.svg'; -import u1158 from '../../../../../public/img/icons/custom/gf-movepane-right.svg'; -import u1159 from '../../../../../public/img/icons/mono/favorite.svg'; -import u1160 from '../../../../../public/img/icons/mono/grafana.svg'; -import u1161 from '../../../../../public/img/icons/mono/heart.svg'; -import u1162 from '../../../../../public/img/icons/mono/heart-break.svg'; -import u1163 from '../../../../../public/img/icons/mono/panel-add.svg'; -import u1164 from '../../../../../public/img/icons/mono/library-panel.svg'; -import u1165 from '../../../../../public/img/icons/unicons/record-audio.svg'; +import u1061 from '../../../../../public/img/icons/unicons/exclamation-circle.svg'; +import u1062 from '../../../../../public/img/icons/unicons/exclamation-triangle.svg'; +import u1063 from '../../../../../public/img/icons/unicons/external-link-alt.svg'; +import u1064 from '../../../../../public/img/icons/unicons/eye.svg'; +import u1065 from '../../../../../public/img/icons/unicons/eye-slash.svg'; +import u1066 from '../../../../../public/img/icons/unicons/file-alt.svg'; +import u1067 from '../../../../../public/img/icons/unicons/file-blank.svg'; +import u1068 from '../../../../../public/img/icons/unicons/filter.svg'; +import u1069 from '../../../../../public/img/icons/unicons/folder.svg'; +import u1070 from '../../../../../public/img/icons/unicons/folder-open.svg'; +import u1071 from '../../../../../public/img/icons/unicons/folder-plus.svg'; +import u1072 from '../../../../../public/img/icons/unicons/folder-upload.svg'; +import u1073 from '../../../../../public/img/icons/unicons/forward.svg'; +import u1074 from '../../../../../public/img/icons/unicons/graph-bar.svg'; +import u1075 from '../../../../../public/img/icons/unicons/history.svg'; +import u1076 from '../../../../../public/img/icons/unicons/home-alt.svg'; +import u1077 from '../../../../../public/img/icons/unicons/import.svg'; +import u1078 from '../../../../../public/img/icons/unicons/info.svg'; +import u1079 from '../../../../../public/img/icons/unicons/info-circle.svg'; +import u1080 from '../../../../../public/img/icons/unicons/k6.svg'; +import u1081 from '../../../../../public/img/icons/unicons/key-skeleton-alt.svg'; +import u1082 from '../../../../../public/img/icons/unicons/keyboard.svg'; +import u1083 from '../../../../../public/img/icons/unicons/link.svg'; +import u1084 from '../../../../../public/img/icons/unicons/list-ul.svg'; +import u1085 from '../../../../../public/img/icons/unicons/lock.svg'; +import u1086 from '../../../../../public/img/icons/unicons/minus.svg'; +import u1087 from '../../../../../public/img/icons/unicons/minus-circle.svg'; +import u1088 from '../../../../../public/img/icons/unicons/mobile-android.svg'; +import u1089 from '../../../../../public/img/icons/unicons/monitor.svg'; +import u1090 from '../../../../../public/img/icons/unicons/pause.svg'; +import u1091 from '../../../../../public/img/icons/unicons/pen.svg'; +import u1092 from '../../../../../public/img/icons/unicons/play.svg'; +import u1093 from '../../../../../public/img/icons/unicons/plug.svg'; +import u1094 from '../../../../../public/img/icons/unicons/plus.svg'; +import u1095 from '../../../../../public/img/icons/unicons/plus-circle.svg'; +import u1096 from '../../../../../public/img/icons/unicons/power.svg'; +import u1097 from '../../../../../public/img/icons/unicons/presentation-play.svg'; +import u1098 from '../../../../../public/img/icons/unicons/process.svg'; +import u1099 from '../../../../../public/img/icons/unicons/question-circle.svg'; +import u1100 from '../../../../../public/img/icons/unicons/repeat.svg'; +import u1101 from '../../../../../public/img/icons/unicons/rocket.svg'; +import u1102 from '../../../../../public/img/icons/unicons/rss.svg'; +import u1103 from '../../../../../public/img/icons/unicons/save.svg'; +import u1104 from '../../../../../public/img/icons/unicons/search.svg'; +import u1105 from '../../../../../public/img/icons/unicons/search-minus.svg'; +import u1106 from '../../../../../public/img/icons/unicons/search-plus.svg'; +import u1107 from '../../../../../public/img/icons/unicons/share-alt.svg'; +import u1108 from '../../../../../public/img/icons/unicons/shield.svg'; +import u1109 from '../../../../../public/img/icons/unicons/signal.svg'; +import u1110 from '../../../../../public/img/icons/unicons/signin.svg'; +import u1111 from '../../../../../public/img/icons/unicons/signout.svg'; +import u1112 from '../../../../../public/img/icons/unicons/sitemap.svg'; +import u1113 from '../../../../../public/img/icons/unicons/slack.svg'; +import u1114 from '../../../../../public/img/icons/unicons/sliders-v-alt.svg'; +import u1115 from '../../../../../public/img/icons/unicons/sort-amount-down.svg'; +import u1116 from '../../../../../public/img/icons/unicons/sort-amount-up.svg'; +import u1117 from '../../../../../public/img/icons/unicons/square-shape.svg'; +import u1118 from '../../../../../public/img/icons/unicons/star.svg'; +import u1119 from '../../../../../public/img/icons/unicons/step-backward.svg'; +import u1120 from '../../../../../public/img/icons/unicons/sync.svg'; +import u1121 from '../../../../../public/img/icons/unicons/table.svg'; +import u1122 from '../../../../../public/img/icons/unicons/tag-alt.svg'; +import u1123 from '../../../../../public/img/icons/unicons/times.svg'; +import u1124 from '../../../../../public/img/icons/unicons/trash-alt.svg'; +import u1125 from '../../../../../public/img/icons/unicons/unlock.svg'; +import u1126 from '../../../../../public/img/icons/unicons/upload.svg'; +import u1127 from '../../../../../public/img/icons/unicons/user.svg'; +import u1128 from '../../../../../public/img/icons/unicons/users-alt.svg'; +import u1129 from '../../../../../public/img/icons/unicons/wrap-text.svg'; +import u1130 from '../../../../../public/img/icons/unicons/cloud-upload.svg'; +import u1131 from '../../../../../public/img/icons/unicons/credit-card.svg'; +import u1132 from '../../../../../public/img/icons/unicons/file-copy-alt.svg'; +import u1133 from '../../../../../public/img/icons/unicons/fire.svg'; +import u1134 from '../../../../../public/img/icons/unicons/hourglass.svg'; +import u1135 from '../../../../../public/img/icons/unicons/layer-group.svg'; +import u1136 from '../../../../../public/img/icons/unicons/layers-alt.svg'; +import u1137 from '../../../../../public/img/icons/unicons/line-alt.svg'; +import u1138 from '../../../../../public/img/icons/unicons/list-ui-alt.svg'; +import u1139 from '../../../../../public/img/icons/unicons/message.svg'; +import u1140 from '../../../../../public/img/icons/unicons/palette.svg'; +import u1141 from '../../../../../public/img/icons/unicons/percentage.svg'; +import u1142 from '../../../../../public/img/icons/unicons/shield-exclamation.svg'; +import u1143 from '../../../../../public/img/icons/unicons/plus-square.svg'; +import u1144 from '../../../../../public/img/icons/unicons/x.svg'; +import u1145 from '../../../../../public/img/icons/unicons/capture.svg'; +import u1146 from '../../../../../public/img/icons/custom/gf-grid.svg'; +import u1147 from '../../../../../public/img/icons/custom/gf-landscape.svg'; +import u1148 from '../../../../../public/img/icons/custom/gf-layout-simple.svg'; +import u1149 from '../../../../../public/img/icons/custom/gf-portrait.svg'; +import u1150 from '../../../../../public/img/icons/custom/gf-bar-alignment-after.svg'; +import u1151 from '../../../../../public/img/icons/custom/gf-bar-alignment-before.svg'; +import u1152 from '../../../../../public/img/icons/custom/gf-bar-alignment-center.svg'; +import u1153 from '../../../../../public/img/icons/custom/gf-interpolation-linear.svg'; +import u1154 from '../../../../../public/img/icons/custom/gf-interpolation-smooth.svg'; +import u1155 from '../../../../../public/img/icons/custom/gf-interpolation-step-after.svg'; +import u1156 from '../../../../../public/img/icons/custom/gf-interpolation-step-before.svg'; +import u1157 from '../../../../../public/img/icons/custom/gf-logs.svg'; +import u1158 from '../../../../../public/img/icons/custom/gf-movepane-left.svg'; +import u1159 from '../../../../../public/img/icons/custom/gf-movepane-right.svg'; +import u1160 from '../../../../../public/img/icons/mono/favorite.svg'; +import u1161 from '../../../../../public/img/icons/mono/grafana.svg'; +import u1162 from '../../../../../public/img/icons/mono/heart.svg'; +import u1163 from '../../../../../public/img/icons/mono/heart-break.svg'; +import u1164 from '../../../../../public/img/icons/mono/panel-add.svg'; +import u1165 from '../../../../../public/img/icons/mono/library-panel.svg'; +import u1166 from '../../../../../public/img/icons/unicons/record-audio.svg'; // do not edit this list directly // the list of icons live here: @grafana/ui/components/Icon/cached.json @@ -256,111 +257,112 @@ export function initIconCache() { cacheItem(u1058, 'unicons/ellipsis-h.svg'); cacheItem(u1059, 'unicons/envelope.svg'); cacheItem(u1060, 'unicons/exchange-alt.svg'); - cacheItem(u1061, 'unicons/exclamation-triangle.svg'); - cacheItem(u1062, 'unicons/external-link-alt.svg'); - cacheItem(u1063, 'unicons/eye.svg'); - cacheItem(u1064, 'unicons/eye-slash.svg'); - cacheItem(u1065, 'unicons/file-alt.svg'); - cacheItem(u1066, 'unicons/file-blank.svg'); - cacheItem(u1067, 'unicons/filter.svg'); - cacheItem(u1068, 'unicons/folder.svg'); - cacheItem(u1069, 'unicons/folder-open.svg'); - cacheItem(u1070, 'unicons/folder-plus.svg'); - cacheItem(u1071, 'unicons/folder-upload.svg'); - cacheItem(u1072, 'unicons/forward.svg'); - cacheItem(u1073, 'unicons/graph-bar.svg'); - cacheItem(u1074, 'unicons/history.svg'); - cacheItem(u1075, 'unicons/home-alt.svg'); - cacheItem(u1076, 'unicons/import.svg'); - cacheItem(u1077, 'unicons/info.svg'); - cacheItem(u1078, 'unicons/info-circle.svg'); - cacheItem(u1079, 'unicons/k6.svg'); - cacheItem(u1080, 'unicons/key-skeleton-alt.svg'); - cacheItem(u1081, 'unicons/keyboard.svg'); - cacheItem(u1082, 'unicons/link.svg'); - cacheItem(u1083, 'unicons/list-ul.svg'); - cacheItem(u1084, 'unicons/lock.svg'); - cacheItem(u1085, 'unicons/minus.svg'); - cacheItem(u1086, 'unicons/minus-circle.svg'); - cacheItem(u1087, 'unicons/mobile-android.svg'); - cacheItem(u1088, 'unicons/monitor.svg'); - cacheItem(u1089, 'unicons/pause.svg'); - cacheItem(u1090, 'unicons/pen.svg'); - cacheItem(u1091, 'unicons/play.svg'); - cacheItem(u1092, 'unicons/plug.svg'); - cacheItem(u1093, 'unicons/plus.svg'); - cacheItem(u1094, 'unicons/plus-circle.svg'); - cacheItem(u1095, 'unicons/power.svg'); - cacheItem(u1096, 'unicons/presentation-play.svg'); - cacheItem(u1097, 'unicons/process.svg'); - cacheItem(u1098, 'unicons/question-circle.svg'); - cacheItem(u1099, 'unicons/repeat.svg'); - cacheItem(u1100, 'unicons/rocket.svg'); - cacheItem(u1101, 'unicons/rss.svg'); - cacheItem(u1102, 'unicons/save.svg'); - cacheItem(u1103, 'unicons/search.svg'); - cacheItem(u1104, 'unicons/search-minus.svg'); - cacheItem(u1105, 'unicons/search-plus.svg'); - cacheItem(u1106, 'unicons/share-alt.svg'); - cacheItem(u1107, 'unicons/shield.svg'); - cacheItem(u1108, 'unicons/signal.svg'); - cacheItem(u1109, 'unicons/signin.svg'); - cacheItem(u1110, 'unicons/signout.svg'); - cacheItem(u1111, 'unicons/sitemap.svg'); - cacheItem(u1112, 'unicons/slack.svg'); - cacheItem(u1113, 'unicons/sliders-v-alt.svg'); - cacheItem(u1114, 'unicons/sort-amount-down.svg'); - cacheItem(u1115, 'unicons/sort-amount-up.svg'); - cacheItem(u1116, 'unicons/square-shape.svg'); - cacheItem(u1117, 'unicons/star.svg'); - cacheItem(u1118, 'unicons/step-backward.svg'); - cacheItem(u1119, 'unicons/sync.svg'); - cacheItem(u1120, 'unicons/table.svg'); - cacheItem(u1121, 'unicons/tag-alt.svg'); - cacheItem(u1122, 'unicons/times.svg'); - cacheItem(u1123, 'unicons/trash-alt.svg'); - cacheItem(u1124, 'unicons/unlock.svg'); - cacheItem(u1125, 'unicons/upload.svg'); - cacheItem(u1126, 'unicons/user.svg'); - cacheItem(u1127, 'unicons/users-alt.svg'); - cacheItem(u1128, 'unicons/wrap-text.svg'); - cacheItem(u1129, 'unicons/cloud-upload.svg'); - cacheItem(u1130, 'unicons/credit-card.svg'); - cacheItem(u1131, 'unicons/file-copy-alt.svg'); - cacheItem(u1132, 'unicons/fire.svg'); - cacheItem(u1133, 'unicons/hourglass.svg'); - cacheItem(u1134, 'unicons/layer-group.svg'); - cacheItem(u1135, 'unicons/layers-alt.svg'); - cacheItem(u1136, 'unicons/line-alt.svg'); - cacheItem(u1137, 'unicons/list-ui-alt.svg'); - cacheItem(u1138, 'unicons/message.svg'); - cacheItem(u1139, 'unicons/palette.svg'); - cacheItem(u1140, 'unicons/percentage.svg'); - cacheItem(u1141, 'unicons/shield-exclamation.svg'); - cacheItem(u1142, 'unicons/plus-square.svg'); - cacheItem(u1143, 'unicons/x.svg'); - cacheItem(u1144, 'unicons/capture.svg'); - cacheItem(u1145, 'custom/gf-grid.svg'); - cacheItem(u1146, 'custom/gf-landscape.svg'); - cacheItem(u1147, 'custom/gf-layout-simple.svg'); - cacheItem(u1148, 'custom/gf-portrait.svg'); - cacheItem(u1149, 'custom/gf-bar-alignment-after.svg'); - cacheItem(u1150, 'custom/gf-bar-alignment-before.svg'); - cacheItem(u1151, 'custom/gf-bar-alignment-center.svg'); - cacheItem(u1152, 'custom/gf-interpolation-linear.svg'); - cacheItem(u1153, 'custom/gf-interpolation-smooth.svg'); - cacheItem(u1154, 'custom/gf-interpolation-step-after.svg'); - cacheItem(u1155, 'custom/gf-interpolation-step-before.svg'); - cacheItem(u1156, 'custom/gf-logs.svg'); - cacheItem(u1157, 'custom/gf-movepane-left.svg'); - cacheItem(u1158, 'custom/gf-movepane-right.svg'); - cacheItem(u1159, 'mono/favorite.svg'); - cacheItem(u1160, 'mono/grafana.svg'); - cacheItem(u1161, 'mono/heart.svg'); - cacheItem(u1162, 'mono/heart-break.svg'); - cacheItem(u1163, 'mono/panel-add.svg'); - cacheItem(u1164, 'mono/library-panel.svg'); - cacheItem(u1165, 'unicons/record-audio.svg'); + cacheItem(u1061, 'unicons/exclamation-circle.svg'); + cacheItem(u1062, 'unicons/exclamation-triangle.svg'); + cacheItem(u1063, 'unicons/external-link-alt.svg'); + cacheItem(u1064, 'unicons/eye.svg'); + cacheItem(u1065, 'unicons/eye-slash.svg'); + cacheItem(u1066, 'unicons/file-alt.svg'); + cacheItem(u1067, 'unicons/file-blank.svg'); + cacheItem(u1068, 'unicons/filter.svg'); + cacheItem(u1069, 'unicons/folder.svg'); + cacheItem(u1070, 'unicons/folder-open.svg'); + cacheItem(u1071, 'unicons/folder-plus.svg'); + cacheItem(u1072, 'unicons/folder-upload.svg'); + cacheItem(u1073, 'unicons/forward.svg'); + cacheItem(u1074, 'unicons/graph-bar.svg'); + cacheItem(u1075, 'unicons/history.svg'); + cacheItem(u1076, 'unicons/home-alt.svg'); + cacheItem(u1077, 'unicons/import.svg'); + cacheItem(u1078, 'unicons/info.svg'); + cacheItem(u1079, 'unicons/info-circle.svg'); + cacheItem(u1080, 'unicons/k6.svg'); + cacheItem(u1081, 'unicons/key-skeleton-alt.svg'); + cacheItem(u1082, 'unicons/keyboard.svg'); + cacheItem(u1083, 'unicons/link.svg'); + cacheItem(u1084, 'unicons/list-ul.svg'); + cacheItem(u1085, 'unicons/lock.svg'); + cacheItem(u1086, 'unicons/minus.svg'); + cacheItem(u1087, 'unicons/minus-circle.svg'); + cacheItem(u1088, 'unicons/mobile-android.svg'); + cacheItem(u1089, 'unicons/monitor.svg'); + cacheItem(u1090, 'unicons/pause.svg'); + cacheItem(u1091, 'unicons/pen.svg'); + cacheItem(u1092, 'unicons/play.svg'); + cacheItem(u1093, 'unicons/plug.svg'); + cacheItem(u1094, 'unicons/plus.svg'); + cacheItem(u1095, 'unicons/plus-circle.svg'); + cacheItem(u1096, 'unicons/power.svg'); + cacheItem(u1097, 'unicons/presentation-play.svg'); + cacheItem(u1098, 'unicons/process.svg'); + cacheItem(u1099, 'unicons/question-circle.svg'); + cacheItem(u1100, 'unicons/repeat.svg'); + cacheItem(u1101, 'unicons/rocket.svg'); + cacheItem(u1102, 'unicons/rss.svg'); + cacheItem(u1103, 'unicons/save.svg'); + cacheItem(u1104, 'unicons/search.svg'); + cacheItem(u1105, 'unicons/search-minus.svg'); + cacheItem(u1106, 'unicons/search-plus.svg'); + cacheItem(u1107, 'unicons/share-alt.svg'); + cacheItem(u1108, 'unicons/shield.svg'); + cacheItem(u1109, 'unicons/signal.svg'); + cacheItem(u1110, 'unicons/signin.svg'); + cacheItem(u1111, 'unicons/signout.svg'); + cacheItem(u1112, 'unicons/sitemap.svg'); + cacheItem(u1113, 'unicons/slack.svg'); + cacheItem(u1114, 'unicons/sliders-v-alt.svg'); + cacheItem(u1115, 'unicons/sort-amount-down.svg'); + cacheItem(u1116, 'unicons/sort-amount-up.svg'); + cacheItem(u1117, 'unicons/square-shape.svg'); + cacheItem(u1118, 'unicons/star.svg'); + cacheItem(u1119, 'unicons/step-backward.svg'); + cacheItem(u1120, 'unicons/sync.svg'); + cacheItem(u1121, 'unicons/table.svg'); + cacheItem(u1122, 'unicons/tag-alt.svg'); + cacheItem(u1123, 'unicons/times.svg'); + cacheItem(u1124, 'unicons/trash-alt.svg'); + cacheItem(u1125, 'unicons/unlock.svg'); + cacheItem(u1126, 'unicons/upload.svg'); + cacheItem(u1127, 'unicons/user.svg'); + cacheItem(u1128, 'unicons/users-alt.svg'); + cacheItem(u1129, 'unicons/wrap-text.svg'); + cacheItem(u1130, 'unicons/cloud-upload.svg'); + cacheItem(u1131, 'unicons/credit-card.svg'); + cacheItem(u1132, 'unicons/file-copy-alt.svg'); + cacheItem(u1133, 'unicons/fire.svg'); + cacheItem(u1134, 'unicons/hourglass.svg'); + cacheItem(u1135, 'unicons/layer-group.svg'); + cacheItem(u1136, 'unicons/layers-alt.svg'); + cacheItem(u1137, 'unicons/line-alt.svg'); + cacheItem(u1138, 'unicons/list-ui-alt.svg'); + cacheItem(u1139, 'unicons/message.svg'); + cacheItem(u1140, 'unicons/palette.svg'); + cacheItem(u1141, 'unicons/percentage.svg'); + cacheItem(u1142, 'unicons/shield-exclamation.svg'); + cacheItem(u1143, 'unicons/plus-square.svg'); + cacheItem(u1144, 'unicons/x.svg'); + cacheItem(u1145, 'unicons/capture.svg'); + cacheItem(u1146, 'custom/gf-grid.svg'); + cacheItem(u1147, 'custom/gf-landscape.svg'); + cacheItem(u1148, 'custom/gf-layout-simple.svg'); + cacheItem(u1149, 'custom/gf-portrait.svg'); + cacheItem(u1150, 'custom/gf-bar-alignment-after.svg'); + cacheItem(u1151, 'custom/gf-bar-alignment-before.svg'); + cacheItem(u1152, 'custom/gf-bar-alignment-center.svg'); + cacheItem(u1153, 'custom/gf-interpolation-linear.svg'); + cacheItem(u1154, 'custom/gf-interpolation-smooth.svg'); + cacheItem(u1155, 'custom/gf-interpolation-step-after.svg'); + cacheItem(u1156, 'custom/gf-interpolation-step-before.svg'); + cacheItem(u1157, 'custom/gf-logs.svg'); + cacheItem(u1158, 'custom/gf-movepane-left.svg'); + cacheItem(u1159, 'custom/gf-movepane-right.svg'); + cacheItem(u1160, 'mono/favorite.svg'); + cacheItem(u1161, 'mono/grafana.svg'); + cacheItem(u1162, 'mono/heart.svg'); + cacheItem(u1163, 'mono/heart-break.svg'); + cacheItem(u1164, 'mono/panel-add.svg'); + cacheItem(u1165, 'mono/library-panel.svg'); + cacheItem(u1166, 'unicons/record-audio.svg'); // do not edit this list directly // the list of icons live here: @grafana/ui/components/Icon/cached.json } diff --git a/public/app/features/alerting/unified/Receivers.tsx b/public/app/features/alerting/unified/Receivers.tsx index 6eff867f0f1..68cebae6705 100644 --- a/public/app/features/alerting/unified/Receivers.tsx +++ b/public/app/features/alerting/unified/Receivers.tsx @@ -38,13 +38,11 @@ function NotificationError({ errorCount }: NotificationErrorProps) { const styles = useStyles2(getStyles); return ( -
    - - - -
    - {`${errorCount} ${pluralize('error', errorCount)} with contact points`} -
    +
    + + + +
    {`${errorCount} ${pluralize('error', errorCount)} with contact points`}
    {'Some alert notifications might not be delivered'}
    @@ -214,11 +212,8 @@ function getPageNavigationModel(type: PageType | undefined, id: string | undefin export default withErrorBoundary(Receivers, { style: 'page' }); const getStyles = (theme: GrafanaTheme2) => ({ - warning: css` - color: ${theme.colors.warning.text}; - `, - countMessage: css` - padding-left: 10px; + error: css` + color: ${theme.colors.error.text}; `, headingContainer: css` display: flex; diff --git a/public/app/features/alerting/unified/components/receivers/ReceiversTable.tsx b/public/app/features/alerting/unified/components/receivers/ReceiversTable.tsx index 9a2937f9286..44120922d81 100644 --- a/public/app/features/alerting/unified/components/receivers/ReceiversTable.tsx +++ b/public/app/features/alerting/unified/components/receivers/ReceiversTable.tsx @@ -90,8 +90,10 @@ interface ReceiverErrorProps { function ReceiverError({ errorCount, errorDetail, showErrorCount, tooltip }: ReceiverErrorProps) { const text = showErrorCount ? `${errorCount} ${pluralize('error', errorCount)}` : 'Error'; const tooltipToRender = tooltip ?? errorDetail ?? 'Error'; - return ; + + return ; } + interface NotifierHealthProps { errorsByNotifier: number; errorDetail?: string; @@ -99,13 +101,18 @@ interface NotifierHealthProps { } function NotifierHealth({ errorsByNotifier, errorDetail, lastNotify }: NotifierHealthProps) { - const noErrorsColor = isLastNotifyNullDate(lastNotify) ? 'orange' : 'green'; - const noErrorsText = isLastNotifyNullDate(lastNotify) ? 'No attempts' : 'OK'; - return errorsByNotifier > 0 ? ( - - ) : ( - - ); + const hasErrors = errorsByNotifier > 0; + const noAttempts = isLastNotifyNullDate(lastNotify); + + if (hasErrors) { + return ; + } + + if (noAttempts) { + return <>No attempts; + } + + return ; } interface ReceiverHealthProps { @@ -114,17 +121,23 @@ interface ReceiverHealthProps { } function ReceiverHealth({ errorsByReceiver, someWithNoAttempt }: ReceiverHealthProps) { - const noErrorsColor = someWithNoAttempt ? 'orange' : 'green'; - const noErrorsText = someWithNoAttempt ? 'No attempts' : 'OK'; - return errorsByReceiver > 0 ? ( - - ) : ( - - ); + const hasErrors = errorsByReceiver > 0; + + if (hasErrors) { + return ( + + ); + } + + if (someWithNoAttempt) { + return <>No attempts; + } + + return ; } const useContactPointsState = (alertManagerName: string) => { From 10109eaf00573b79d5e151a403291aa7bf68af73 Mon Sep 17 00:00:00 2001 From: Andreas Christou Date: Tue, 9 May 2023 17:39:27 +0100 Subject: [PATCH 078/179] AzureMonitor: Fix tests (#68127) Fix tests --- .../azure-log-analytics-datasource_test.go | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/pkg/tsdb/azuremonitor/loganalytics/azure-log-analytics-datasource_test.go b/pkg/tsdb/azuremonitor/loganalytics/azure-log-analytics-datasource_test.go index 02298dcb7ef..28f4127ee93 100644 --- a/pkg/tsdb/azuremonitor/loganalytics/azure-log-analytics-datasource_test.go +++ b/pkg/tsdb/azuremonitor/loganalytics/azure-log-analytics-datasource_test.go @@ -939,8 +939,8 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) { `| extend spanID = iff(itemType == "pageView" or isempty(column_ifexists("id", "")), tostring(new_guid()), column_ifexists("id", ""))` + `| extend operationName = iff(isempty(column_ifexists("name", "")), column_ifexists("problemId", ""), column_ifexists("name", ""))` + `| extend serviceName = cloud_RoleName| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` + - `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + - `| extend error = iff(itemType == "exceptions", true, false)` + + `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,error,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + `| order by startTime asc`, @@ -953,8 +953,8 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) { `| extend spanID = iff(itemType == "pageView" or isempty(column_ifexists("id", "")), tostring(new_guid()), column_ifexists("id", ""))` + `| extend operationName = iff(isempty(column_ifexists("name", "")), column_ifexists("problemId", ""), column_ifexists("name", ""))` + `| extend serviceName = cloud_RoleName| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` + - `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + - `| extend error = iff(itemType == "exceptions", true, false)` + + `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,error,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + `| order by startTime asc`, @@ -1004,8 +1004,8 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) { `| extend spanID = iff(itemType == "pageView" or isempty(column_ifexists("id", "")), tostring(new_guid()), column_ifexists("id", ""))` + `| extend operationName = iff(isempty(column_ifexists("name", "")), column_ifexists("problemId", ""), column_ifexists("name", ""))` + `| extend serviceName = cloud_RoleName| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` + - `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + - `| extend error = iff(itemType == "exceptions", true, false)` + + `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,error,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + `| order by startTime asc`, @@ -1018,8 +1018,8 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) { `| extend spanID = iff(itemType == "pageView" or isempty(column_ifexists("id", "")), tostring(new_guid()), column_ifexists("id", ""))` + `| extend operationName = iff(isempty(column_ifexists("name", "")), column_ifexists("problemId", ""), column_ifexists("name", ""))` + `| extend serviceName = cloud_RoleName| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` + - `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + - `| extend error = iff(itemType == "exceptions", true, false)` + + `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,error,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + `| order by startTime asc`, @@ -1072,8 +1072,8 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) { `| extend spanID = iff(itemType == "pageView" or isempty(column_ifexists("id", "")), tostring(new_guid()), column_ifexists("id", ""))` + `| extend operationName = iff(isempty(column_ifexists("name", "")), column_ifexists("problemId", ""), column_ifexists("name", ""))` + `| extend serviceName = cloud_RoleName| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` + - `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + - `| extend error = iff(itemType == "exceptions", true, false)` + + `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,error,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + `| order by startTime asc`, @@ -1086,8 +1086,8 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) { `| extend spanID = iff(itemType == "pageView" or isempty(column_ifexists("id", "")), tostring(new_guid()), column_ifexists("id", ""))` + `| extend operationName = iff(isempty(column_ifexists("name", "")), column_ifexists("problemId", ""), column_ifexists("name", ""))` + `| extend serviceName = cloud_RoleName| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` + - `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + - `| extend error = iff(itemType == "exceptions", true, false)` + + `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,error,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + `| order by startTime asc`, @@ -1140,8 +1140,8 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) { `| extend spanID = iff(itemType == "pageView" or isempty(column_ifexists("id", "")), tostring(new_guid()), column_ifexists("id", ""))` + `| extend operationName = iff(isempty(column_ifexists("name", "")), column_ifexists("problemId", ""), column_ifexists("name", ""))` + `| extend serviceName = cloud_RoleName| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` + - `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + - `| extend error = iff(itemType == "exceptions", true, false)` + + `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,error,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + `| order by startTime asc`, @@ -1154,8 +1154,8 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) { `| extend spanID = iff(itemType == "pageView" or isempty(column_ifexists("id", "")), tostring(new_guid()), column_ifexists("id", ""))` + `| extend operationName = iff(isempty(column_ifexists("name", "")), column_ifexists("problemId", ""), column_ifexists("name", ""))` + `| extend serviceName = cloud_RoleName| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` + - `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + - `| extend error = iff(itemType == "exceptions", true, false)` + + `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,error,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + `| order by startTime asc`, From 75729883051f00d550af9e23aba17389714e3c4e Mon Sep 17 00:00:00 2001 From: Dan Cech Date: Wed, 10 May 2023 03:03:28 +1000 Subject: [PATCH 079/179] add uid to team table (#66920) * add uid to team table * concat * add prefix to transition uids --------- Co-authored-by: Ryan McKinley --- pkg/services/sqlstore/migrations/team_mig.go | 13 +++++++ pkg/services/team/model.go | 4 ++ pkg/services/team/teamimpl/store.go | 40 ++++++++++++++------ 3 files changed, 46 insertions(+), 11 deletions(-) diff --git a/pkg/services/sqlstore/migrations/team_mig.go b/pkg/services/sqlstore/migrations/team_mig.go index cf15d775865..c292d7088f9 100644 --- a/pkg/services/sqlstore/migrations/team_mig.go +++ b/pkg/services/sqlstore/migrations/team_mig.go @@ -24,6 +24,19 @@ func addTeamMigrations(mg *Migrator) { mg.AddMigration("add index team.org_id", NewAddIndexMigration(teamV1, teamV1.Indices[0])) mg.AddMigration("add unique index team_org_id_name", NewAddIndexMigration(teamV1, teamV1.Indices[1])) + mg.AddMigration("Add column uid in team", NewAddColumnMigration(teamV1, &Column{ + Name: "uid", Type: DB_NVarchar, Length: 40, Nullable: true, + })) + + mg.AddMigration("Update uid column values in team", NewRawSQLMigration(""). + SQLite("UPDATE team SET uid=printf('t%09d',id) WHERE uid IS NULL;"). + Postgres("UPDATE team SET uid='t' || lpad('' || id::text,9,'0') WHERE uid IS NULL;"). + Mysql("UPDATE team SET uid=concat('t',lpad(id,9,'0')) WHERE uid IS NULL;")) + + mg.AddMigration("Add unique index team_org_id_uid", NewAddIndexMigration(teamV1, &Index{ + Cols: []string{"org_id", "uid"}, Type: UniqueIndex, + })) + teamMemberV1 := Table{ Name: "team_member", Columns: []*Column{ diff --git a/pkg/services/team/model.go b/pkg/services/team/model.go index c19333c8015..bff43aed86b 100644 --- a/pkg/services/team/model.go +++ b/pkg/services/team/model.go @@ -23,6 +23,7 @@ var ( // Team model type Team struct { ID int64 `json:"id" xorm:"pk autoincr 'id'"` + UID string `json:"uid" xorm:"uid"` OrgID int64 `json:"orgId" xorm:"org_id"` Name string `json:"name"` Email string `json:"email"` @@ -82,6 +83,7 @@ type SearchTeamsQuery struct { type TeamDTO struct { ID int64 `json:"id" xorm:"id"` + UID string `json:"uid" xorm:"uid"` OrgID int64 `json:"orgId" xorm:"org_id"` Name string `json:"name"` Email string `json:"email"` @@ -145,6 +147,7 @@ type RemoveTeamMemberCommand struct { type GetTeamMembersQuery struct { OrgID int64 TeamID int64 + TeamUID string UserID int64 External bool SignedInUser *user.SignedInUser @@ -156,6 +159,7 @@ type GetTeamMembersQuery struct { type TeamMemberDTO struct { OrgID int64 `json:"orgId" xorm:"org_id"` TeamID int64 `json:"teamId" xorm:"team_id"` + TeamUID string `json:"teamUID" xorm:"uid"` UserID int64 `json:"userId" xorm:"user_id"` External bool `json:"-"` AuthModule string `json:"auth_module"` diff --git a/pkg/services/team/teamimpl/store.go b/pkg/services/team/teamimpl/store.go index 585b9c1b4b3..8f2bc5178a4 100644 --- a/pkg/services/team/teamimpl/store.go +++ b/pkg/services/team/teamimpl/store.go @@ -13,6 +13,7 @@ import ( "github.com/grafana/grafana/pkg/services/team" "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/setting" + "github.com/grafana/grafana/pkg/util" ) type store interface { @@ -67,6 +68,7 @@ func getTeamMemberCount(db db.DB, filteredUsers []string) string { func getTeamSelectSQLBase(db db.DB, filteredUsers []string) string { return `SELECT team.id as id, + team.uid, team.org_id, team.name as name, team.email as email, ` + @@ -77,6 +79,7 @@ func getTeamSelectSQLBase(db db.DB, filteredUsers []string) string { func getTeamSelectWithPermissionsSQLBase(db db.DB, filteredUsers []string) string { return `SELECT team.id AS id, + team.uid, team.org_id, team.name AS name, team.email AS email, @@ -88,6 +91,7 @@ func getTeamSelectWithPermissionsSQLBase(db db.DB, filteredUsers []string) strin func (ss *xormStore) Create(name, email string, orgID int64) (team.Team, error) { t := team.Team{ + UID: util.GenerateShortUID(), Name: name, Email: email, OrgID: orgID, @@ -536,6 +540,7 @@ func (ss *xormStore) getTeamMembers(ctx context.Context, query *team.GetTeamMemb sess.Join("INNER", ss.db.GetDialect().Quote("user"), fmt.Sprintf("team_member.user_id=%s.%s", ss.db.GetDialect().Quote("user"), ss.db.GetDialect().Quote("id")), ) + sess.Join("INNER", "team", "team.id=team_member.team_id") // explicitly check for serviceaccounts sess.Where(fmt.Sprintf("%s.is_service_account=?", ss.db.GetDialect().Quote("user")), ss.db.GetDialect().BooleanStr(false)) @@ -545,11 +550,12 @@ func (ss *xormStore) getTeamMembers(ctx context.Context, query *team.GetTeamMemb } // Join with only most recent auth module - authJoinCondition := `( - SELECT id from user_auth + authJoinCondition := `user_auth.id=( + SELECT id + FROM user_auth WHERE user_auth.user_id = team_member.user_id - ORDER BY user_auth.created DESC ` - authJoinCondition = "user_auth.id=" + authJoinCondition + ss.db.GetDialect().Limit(1) + ")" + ORDER BY user_auth.created DESC ` + + ss.db.GetDialect().Limit(1) + ")" sess.Join("LEFT", "user_auth", authJoinCondition) if query.OrgID != 0 { @@ -558,6 +564,9 @@ func (ss *xormStore) getTeamMembers(ctx context.Context, query *team.GetTeamMemb if query.TeamID != 0 { sess.Where("team_member.team_id=?", query.TeamID) } + if query.TeamUID != "" { + sess.Where("team.uid=?", query.TeamUID) + } if query.UserID != 0 { sess.Where("team_member.user_id=?", query.UserID) } @@ -574,6 +583,7 @@ func (ss *xormStore) getTeamMembers(ctx context.Context, query *team.GetTeamMemb "team_member.external", "team_member.permission", "user_auth.auth_module", + "team.uid", ) sess.Asc("user.login", "user.email") @@ -587,10 +597,20 @@ func (ss *xormStore) getTeamMembers(ctx context.Context, query *team.GetTeamMemb } func (ss *xormStore) IsAdmin(ctx context.Context, query *team.IsAdminOfTeamsQuery) (bool, error) { - var queryResult bool + queryResult := false + err := ss.db.WithDbSession(ctx, func(sess *db.Session) error { - sql := "SELECT COUNT(team.id) AS count FROM team INNER JOIN team_member ON team_member.team_id = team.id WHERE team.org_id = ? AND team_member.user_id = ? AND team_member.permission = ?" - params := []interface{}{query.SignedInUser.OrgID, query.SignedInUser.UserID, dashboards.PERMISSION_ADMIN} + sql := `SELECT COUNT(team.id) AS count + FROM team + INNER JOIN team_member ON team_member.team_id = team.id + WHERE team.org_id = ? + AND team_member.user_id = ? + AND team_member.permission = ?` + params := []interface{}{ + query.SignedInUser.OrgID, + query.SignedInUser.UserID, + dashboards.PERMISSION_ADMIN, + } type teamCount struct { Count int64 @@ -605,8 +625,6 @@ func (ss *xormStore) IsAdmin(ctx context.Context, query *team.IsAdminOfTeamsQuer return nil }) - if err != nil { - return false, err - } - return queryResult, nil + + return queryResult, err } From 720b5b3b6515c72f98c957b2f2c9bbc4aedec7e5 Mon Sep 17 00:00:00 2001 From: Gilles De Mey Date: Tue, 9 May 2023 19:50:24 +0200 Subject: [PATCH 080/179] Alerting: Omit "description" and "summary" from default email template annotations (#68122) --- .github/CODEOWNERS | 2 +- .../partials/alerting/instance_details.mjml | 111 +++++------------- public/emails/ng_alert_notification.html | 88 ++++---------- 3 files changed, 58 insertions(+), 143 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 5ece762b8ff..d0da194624f 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -433,7 +433,6 @@ lerna.json @grafana/frontend-ops /public/app/types/ @grafana/grafana-frontend-platform /public/dashboards/ @grafana/dashboards-squad /public/fonts/ @grafana/alerting-squad-frontend -/public/emails/ @grafana/grafana-frontend-platform /public/gazetteer/ @ryantxu /public/img/ @grafana/grafana-frontend-platform /public/lib/ @grafana/grafana-frontend-platform @@ -618,6 +617,7 @@ embed.go @grafana/grafana-as-code /docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md @grafanabot /pkg/services/featuremgmt/toggles_gen.csv @grafanabot /pkg/services/featuremgmt/toggles_gen.go @grafanabot +/public/emails/ @grafanabot # Conf /conf/defaults.ini @torkelo diff --git a/emails/templates/partials/alerting/instance_details.mjml b/emails/templates/partials/alerting/instance_details.mjml index 26a4bbd7856..458124dd974 100644 --- a/emails/templates/partials/alerting/instance_details.mjml +++ b/emails/templates/partials/alerting/instance_details.mjml @@ -1,71 +1,47 @@ - - {{ if .ImageURL }} - +{{ if .ImageURL }} - - {{ end }} - +{{ end }} - - {{ if .EmbeddedImage }} - +{{ if .EmbeddedImage }} - - {{ end }} - +{{ end }} - - {{ if .Annotations.summary }} - + {{ if .Annotations.summary }} Summary - - {{- .Annotations.summary -}} - - - {{ end }} - + {{- .Annotations.summary -}} + {{ end }} - - {{ if .Annotations.description }} - + {{ if .Annotations.description }} Description - - {{ range $line := (splitList "\n" .Annotations.description) }} - + {{ range $line := (splitList "\n" .Annotations.description) }} {{ $line }}
    - - {{ end }} - + {{ end }}
    - - {{ end }} - + {{ end }}
    - - {{ if .Values }} - +{{ if .Values }} @@ -76,80 +52,56 @@ - - {{ range $refID, $value := .Values }} - + {{ range $refID, $value := .Values }} {{ $refID }}={{ $value }}  - - {{ end }} - + {{ end }} - - {{ end }} - +{{ end }} - - {{ if .Labels.SortedPairs }} - + {{ if .Labels.SortedPairs }} Labels - - {{ range .Labels.SortedPairs }} - + {{ range .Labels.SortedPairs }}
    - + - - {{ end }} - + {{ end }} - - {{ end }} - + {{ end }} - - - {{ if .Annotations.SortedPairs }} - + + {{ if (without .Annotations.SortedPairs.Names "description" "summary") }} Annotations - - {{ range .Annotations.SortedPairs }} - + {{ range .Annotations.SortedPairs }} + {{ if and (ne .Name "description") (ne .Name "summary") }} - + - - {{ end }} - + {{ end }} + {{ end }} - - {{ end }} - + {{ end }} @@ -178,9 +130,7 @@ {{ end }}{{ if .PanelURL }} - - View panel - + View panel {{ end }} @@ -189,7 +139,8 @@ - Observed {{ ago .StartsAt }} before this notification was delivered, at {{ .StartsAt }} + Observed {{ ago .StartsAt }} before this notification was delivered, at + {{ .StartsAt }} diff --git a/public/emails/ng_alert_notification.html b/public/emails/ng_alert_notification.html index 92e98369e9b..3dcf73630d8 100644 --- a/public/emails/ng_alert_notification.html +++ b/public/emails/ng_alert_notification.html @@ -470,13 +470,9 @@ @@ -533,12 +529,8 @@ @@ -576,24 +568,18 @@ - {{ end }}{{ if .Annotations.SortedPairs }} + {{ end }}{{ if (without .Annotations.SortedPairs.Names "description" "summary") }} @@ -979,13 +961,9 @@ @@ -1042,12 +1020,8 @@ @@ -1085,24 +1059,18 @@ - {{ end }}{{ if .Annotations.SortedPairs }} + {{ end }}{{ if (without .Annotations.SortedPairs.Names "description" "summary") }} From afbc376b2ce909cc1572428d78138e5514eb2a61 Mon Sep 17 00:00:00 2001 From: Tobias Skarhed <1438972+tskarhed@users.noreply.github.com> Date: Tue, 9 May 2023 21:04:58 +0200 Subject: [PATCH 081/179] Nested Folders: Do not keep search state when navigating to a result item (#67749) * Reset search state when navigating to a new search item * Reset query on search select * Spread initialState * Remove unused import * Only reset query * Add test * Fix spelling * Change functionality * Only applied saved filters when searching --- .../search/state/SearchStateManager.test.ts | 25 +++++++++++++++++++ .../search/state/SearchStateManager.ts | 8 +++--- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/public/app/features/search/state/SearchStateManager.test.ts b/public/app/features/search/state/SearchStateManager.test.ts index 11352388e1d..78267fadde4 100644 --- a/public/app/features/search/state/SearchStateManager.test.ts +++ b/public/app/features/search/state/SearchStateManager.test.ts @@ -3,6 +3,7 @@ import { locationService } from '@grafana/runtime'; import { DashboardQueryResult, getGrafanaSearcher } from '../service'; import { SearchLayout } from '../types'; +import * as utils from '../utils'; import { getSearchStateManager } from './SearchStateManager'; @@ -44,5 +45,29 @@ describe('SearchStateManager', () => { stm.initStateFromUrl(); expect(stm.state.folderUid).toBe(undefined); }); + + it('should reset filters if state is updated and no URL params are present', () => { + const parseRouteParamsSpy = jest.spyOn(utils, 'parseRouteParams'); + // Set initial values + parseRouteParamsSpy.mockImplementation(() => ({ + query: 'hello', + sort: 'alpha-asc', + })); + const stm = getSearchStateManager(); + stm.initStateFromUrl(); + + // Verify that they have been set + expect(stm.state.query).toBe('hello'); + expect(stm.state.sort).toBe('alpha-asc'); + expect(stm.state.folderUid).toBe(undefined); + + // Changed to a view with no URL state. + parseRouteParamsSpy.mockImplementation(() => ({})); + stm.initStateFromUrl('abc'); + + expect(stm.state.query).toBe(''); + expect(stm.state.sort).toBe(undefined); + expect(stm.state.folderUid).toBe('abc'); + }); }); }); diff --git a/public/app/features/search/state/SearchStateManager.ts b/public/app/features/search/state/SearchStateManager.ts index 22b45f93cb4..180aa23920f 100644 --- a/public/app/features/search/state/SearchStateManager.ts +++ b/public/app/features/search/state/SearchStateManager.ts @@ -49,6 +49,7 @@ export class SearchStateManager extends StateManagerBase { } stateManager.setState({ + ...initialState, ...stateFromUrl, folderUid: folderUid, eventTrackingNamespace: folderUid ? 'manage_dashboards' : 'dashboard_search', @@ -63,8 +64,10 @@ export class SearchStateManager extends StateManagerBase { * Updates internal and url state, then triggers a new search */ setStateAndDoSearch(state: Partial) { + const sort = state.sort || this.state.sort || localStorage.getItem(SEARCH_SELECTED_SORT) || undefined; + // Set internal state - this.setState(state); + this.setState({ sort, ...state }); // Update url state this.updateLocation({ @@ -290,14 +293,13 @@ export function getSearchStateManager() { if (!stateManager) { const selectedLayout = localStorage.getItem(SEARCH_SELECTED_LAYOUT) as SearchLayout; const layout = selectedLayout ?? initialState.layout; - const sort = localStorage.getItem(SEARCH_SELECTED_SORT) ?? undefined; let includePanels = store.getBool(SEARCH_PANELS_LOCAL_STORAGE_KEY, true); if (includePanels) { includePanels = false; } - stateManager = new SearchStateManager({ ...initialState, layout, sort, includePanels }); + stateManager = new SearchStateManager({ ...initialState, layout, includePanels }); } return stateManager; From 5422609fb10bf136db17a64ab38d0964423a235f Mon Sep 17 00:00:00 2001 From: Matthew Jacobson Date: Tue, 9 May 2023 15:27:40 -0400 Subject: [PATCH 082/179] Alerting: Fix broken integration test (#68140) From https://github.com/grafana/grafana/pull/68122 --- pkg/services/ngalert/notifier/email_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/services/ngalert/notifier/email_test.go b/pkg/services/ngalert/notifier/email_test.go index 1fe36a4a3bc..01da729b9bd 100644 --- a/pkg/services/ngalert/notifier/email_test.go +++ b/pkg/services/ngalert/notifier/email_test.go @@ -111,11 +111,11 @@ func TestEmailNotifierIntegration(t *testing.T) { expSnippets: []string{ "2 firing instances", "severity", - "warning\n", - "critical\n", + "warning", + "critical", "alertname", - "FiringTwo\n", - "FiringOne\n", + "FiringTwo", + "FiringOne", " Date: Tue, 9 May 2023 15:07:03 -0500 Subject: [PATCH 083/179] updated screenshots, links and some wording (#67864) --- .../prometheus/query-editor/index.md | 41 ++++++++----------- 1 file changed, 18 insertions(+), 23 deletions(-) diff --git a/docs/sources/datasources/prometheus/query-editor/index.md b/docs/sources/datasources/prometheus/query-editor/index.md index 6a9e6dc2d27..5b99aa0f2aa 100644 --- a/docs/sources/datasources/prometheus/query-editor/index.md +++ b/docs/sources/datasources/prometheus/query-editor/index.md @@ -24,13 +24,11 @@ You can switch the Prometheus query editor between two modes: - [Code mode](#code-mode), which provides a feature-rich editor for writing queries - [Builder mode](#builder-mode), which provides a visual query designer -{{< figure src="/static/img/docs/prometheus/header-9-1.png" max-width="500px" class="docs-image--no-shadow" caption="Editor toolbar" >}} +{{< figure src="/static/img/docs/prometheus/editing-mode.png" max-width="500px" class="docs-image--no-shadow" caption="Query editor mode" >}} To switch between the editor modes, select the corresponding **Builder** and **Code** tabs. -To run a query, select **Run queries** located at the top of the editor. - -> **Note:** To run Prometheus queries in [Explore]({{< relref "../../../explore" >}}), select **Run query**. +To run a query, select **Run query** in the upper right of the editor. Each mode is synchronized with the other modes, so you can switch between them without losing your work, although there are some limitations. @@ -46,7 +44,7 @@ For options and functions common to all query editors, refer to [Query and trans You can configure Prometheus-specific options in the query editor by setting several options regardless of its mode. -{{< figure src="/static/img/docs/prometheus/options-9-1.png" max-width="500px" class="docs-image--no-shadow" caption="Options" >}} +{{< figure src="/static/img/docs/prometheus/options.png" max-width="500px" class="docs-image--no-shadow" caption="Options" >}} ### Legend @@ -55,7 +53,7 @@ The **Legend** setting defines the time series's name. You can use a predefined | Option | Description | | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Auto** | Shows the value of a single label for each series with only one label, or displays all labels if a series has multiple labels. | -| **Verbose** | Displays all labels. | +| **Verbose** | Displays all label names. | | **Custom** | Uses templating to select which labels will be included.
    For example, `{{hostname}}` is replaced by the label value for the label `hostname`.
    Clear the input and click outside of it to select another mode. | ### Min step @@ -68,11 +66,11 @@ This setting supports the `$__interval` and `$__rate_interval` macros. You can switch between **Table**, **Time series**, and **Heatmap** options by configuring the query's **Format**. -| Option | Description | -| --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **Table** | This works only in a [Table panel]{{< relref "../../../panels-visualizations/visualizations/table" >}}. | -| **Time series** | Uses the default time series format. | -| **Heatmap** | Displays metrics of the Histogram type on a [Heatmap panel]{{< relref "../../../panels-visualizations/visualizations/heatmap" >}} by converting cumulative histograms to regular ones and sorting the series by the bucket bound. | +| Option | Description | +| --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Table** | This works only in a [Table panel]({{< relref "../../../panels-visualizations/visualizations/table" >}}). | +| **Time series** | Uses the default time series format. | +| **Heatmap** | Displays metrics of the Histogram type on a [Heatmap panel]({{< relref "../../../panels-visualizations/visualizations/heatmap" >}}) by converting cumulative histograms to regular ones and sorting the series by the bucket bound. | ### Type @@ -92,21 +90,18 @@ The **Type** setting selects the query type. ## Code mode -{{< figure src="/static/img/docs/prometheus/code-mode-9-1.png" max-width="500px" class="docs-image--no-shadow" caption="Code mode" >}} +{{< figure src="/static/img/docs/prometheus/code-mode.png" max-width="500px" class="docs-image--no-shadow" caption="Code mode" >}} In **Code mode**, you can write complex queries using a text editor with autocompletion features and syntax highlighting. It also contains a [Metrics browser]({{< relref "#metrics-browser" >}}) to further help you write queries. -For more information about Prometheus's query language, refer to the [Prometheus documentation](http://prometheus.io/docs/querying/basics/). +For more information about Prometheus's query language (PromQL), see [Querying Prometheus](http://prometheus.io/docs/querying/basics/). ### Use autocompletion -{{< figure src="/static/img/docs/prometheus/autocomplete-9-1.png" max-width="500px" class="docs-image--no-shadow" caption="Autocomplete" >}} - Code mode's autocompletion feature works automatically while typing. -To manually trigger autocompletion, use the keyboard shortcut Ctrl/Cmd + Space. -The query editor can autocomplete static functions, aggregations, and keywords, and also dynamic items like metrics and labels. +The query editor can autocomplete static functions, aggregations, keywords, and also dynamic items like metrics and labels. The autocompletion dropdown includes documentation for the suggested items where available. To run a query in [Explore]({{< relref "../../../explore/" >}}), use the keyboard shortcut Shift + Enter. @@ -117,7 +112,7 @@ The metrics browser locates metrics and selects relevant labels to help you buil When you open the browser, it displays all available metrics and labels. If supported by your Prometheus instance, each metric also displays its HELP and TYPE as a tooltip. -{{< figure src="/static/img/docs/prometheus/metric-browser-9-1.png" max-width="500px" class="docs-image--no-shadow" caption="Metrics browser" >}} +{{< figure src="/static/img/docs/prometheus/metric-browser.png" max-width="500px" class="docs-image--no-shadow" caption="Metrics browser" >}} When you select a metric, the browser narrows down the available labels to show only the ones applicable to the metric. You can then select one or more labels for which the available label values are shown in lists in the bottom section. @@ -154,7 +149,7 @@ In addition to the **Run query** button and mode switcher, Builder mode includes ### Metric and labels -{{< figure src="/static/img/docs/prometheus/metric-select-9-1.png" max-width="500px" class="docs-image--no-shadow" caption="Metric and labels" >}} +{{< figure src="/static/img/docs/prometheus/metrics-and-labels.png" max-width="500px" class="docs-image--no-shadow" caption="Metric and label filters" >}} Select a specific metric name from the dropdown list. The data source requests the list of available metrics from the Prometheus server based on the selected time rage. @@ -166,7 +161,7 @@ Use the `+` button to add a label, and the `x` button to remove a label. ### Operations -{{< figure src="/static/img/docs/prometheus/operations-9-1.png" max-width="500px" class="docs-image--no-shadow" caption="Operations" >}} +{{< figure src="/static/img/docs/prometheus/operations.png" max-width="500px" class="docs-image--no-shadow" caption="Operations" >}} Select the `+ Operations` button to add operations to your query. The query editor groups operations into related sections, and you can type while the operations dropdown is open to search and filter the list. @@ -189,16 +184,16 @@ To re-order operations manually, drag the operation box by its name and drop it #### Hints -{{< figure src="/static/img/docs/prometheus/hint-8-5.png" max-width="500px" class="docs-image--no-shadow" caption="Hint" >}} +{{< figure src="/static/img/docs/prometheus/hint-example.png" max-width="500px" class="docs-image--no-shadow" caption="Hint" >}} The query editor can detect which operations are most appropriate for some selected metrics. If it does, it displays a hint next to the `+ Operations` button. -To add the operations to your query, click the hint. +To add the operation to your query, click the `hint`. ## Use Explain mode to understand queries -{{< figure src="/static/img/docs/prometheus/explain-8-5.png" max-width="500px" class="docs-image--no-shadow" caption="Explain mode" >}} +{{< figure src="/static/img/docs/prometheus/explain-results.png" max-width="500px" class="docs-image--no-shadow" caption="Explain results" >}} Explain mode helps you understand a query by displaying a step-by-step explanation of all query components and operations. From 13603c7d7157b4fa2fd00f1534718f588cc233e5 Mon Sep 17 00:00:00 2001 From: Adela Almasan <88068998+adela-almasan@users.noreply.github.com> Date: Tue, 9 May 2023 15:12:02 -0500 Subject: [PATCH 084/179] Annotations: Fix rendering (#68038) --- .../plugins/panel/timeseries/plugins/AnnotationsPlugin.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/public/app/plugins/panel/timeseries/plugins/AnnotationsPlugin.tsx b/public/app/plugins/panel/timeseries/plugins/AnnotationsPlugin.tsx index c190a814b6b..8d7f9772918 100644 --- a/public/app/plugins/panel/timeseries/plugins/AnnotationsPlugin.tsx +++ b/public/app/plugins/panel/timeseries/plugins/AnnotationsPlugin.tsx @@ -28,6 +28,11 @@ export const AnnotationsPlugin = ({ annotations, timeZone, config }: Annotations } annotationsRef.current = views; + + return () => { + // clear on unmount + annotationsRef.current = []; + }; }, [annotations]); useLayoutEffect(() => { From d6ba522c3cdf7947068511e8667b223df4093371 Mon Sep 17 00:00:00 2001 From: Joseph Perez <45749060+josmperez@users.noreply.github.com> Date: Tue, 9 May 2023 13:53:11 -0700 Subject: [PATCH 085/179] Docs: Plugin doc review changes from chunk 1-B with corrected set of files (#67164) * Re-pushing doc review changes from 1-B with fewer files Signed-off-by: Joe Perez * Fix for URL examples Signed-off-by: Joe Perez * Data frames fixes Signed-off-by: Joe Perez * Fixes from doc review Signed-off-by: Joe Perez * More doc review changes Signed-off-by: Joe Perez * Doc fixes Signed-off-by: Joe Perez * Doc fix Signed-off-by: Joe Perez * Prettier Signed-off-by: Joe Perez * Fix migration index Signed-off-by: Joe Perez * Fix screenshot Signed-off-by: Joe Perez * Doc fixes Signed-off-by: Joe Perez * Quick fix Signed-off-by: Joe Perez --------- Signed-off-by: Joe Perez --- .../sources/developers/plugins/data-frames.md | 98 ++++++++++++------- .../developers/plugins/error-handling.md | 12 ++- .../migration-guide/angular-react/_index.md | 26 ++--- .../developers/plugins/package-a-plugin.md | 67 +++++++++++++ .../developers/plugins/publish-a-plugin.md | 17 ++-- .../plugins/working-with-data-frames.md | 24 ++--- 6 files changed, 159 insertions(+), 85 deletions(-) create mode 100644 docs/sources/developers/plugins/package-a-plugin.md diff --git a/docs/sources/developers/plugins/data-frames.md b/docs/sources/developers/plugins/data-frames.md index 2e5a8ce9e81..de02509e221 100644 --- a/docs/sources/developers/plugins/data-frames.md +++ b/docs/sources/developers/plugins/data-frames.md @@ -12,39 +12,59 @@ The data frame structure is a concept that's borrowed from data analysis tools l This document gives an overview of the data frame structure, and of how data is handled within Grafana. -## The data frame +### Data frame fields -A data frame is a columnar-oriented table structure, which means it stores data by column and not by row. To understand what this means, let’s look at the TypeScript definition used by Grafana: +A data frame is a collection of _fields_, where each field corresponds to a column. Each field, in turn, consists of a collection of values and metadata, such as the data type of those values. ```ts -interface DataFrame { - name?: string; - // reference to query that create the frame - refId?: string; - - fields: []Field; -} -``` - -In essence, a data frame is a collection of _fields_, where each field corresponds to a column. Each field, in turn, consists of a collection of values, along with meta information, such as the data type of those values. - -```ts -interface Field { +export interface Field> { + /** + * Name of the field (column) + */ name: string; - // Prometheus like Labels / Tags - labels?: Record; - - // For example string, number, time (or more specific primitives in the backend) + /** + * Field value type (string, number, and so on) + */ type: FieldType; - // Array of values all of the same type - values: Vector; - - // Optional display data for the field (e.g. unit, name over-ride, etc) + /** + * Meta info about how field and how to display it + */ config: FieldConfig; + + /** + * The raw field values + * In Grafana 10, this accepts both simple arrays and the Vector interface + * In Grafana 11, the Vector interface will be removed + */ + values: V | T[]; + + /** + * When type === FieldType.Time, this can optionally store + * the nanosecond-precison fractions as integers between + * 0 and 999999. + */ + nanos?: number[]; + + labels?: Labels; + + /** + * Cached values with appropriate display and id values + */ + state?: FieldState | null; + + /** + * Convert a value for display + */ + display?: DisplayProcessor; + + /** + * Get value data links with variables interpolated + */ + getLinks?: (config: ValueLinkConfig) => Array>; } ``` -Let's look at an example. The table below demonstrates a data frame with two fields, _time_ and _temperature_. +Let's look at an example. The following table demonstrates a data frame with two fields, _time_ and _temperature_: | time | temperature | | ------------------- | ----------- | @@ -52,22 +72,24 @@ Let's look at an example. The table below demonstrates a data frame with two fie | 2020-01-02 03:05:00 | 47.0 | | 2020-01-02 03:06:00 | 48.0 | -Each field has three values, and each value in a field must share the same type. In this case, all values in the time field are timestamps, and all values in the temperature field are numbers. +Each field has three values, and each value in a field must share the same type. In this case, all values in the `time` field are timestamps, and all values in the `temperature` field are numbers. One restriction on data frames is that all fields in the frame must be of the same length to be a valid data frame. -### Field configuration +### Field configurations Each field in a data frame contains optional information about the values in the field, such as units, scaling, and so on. By adding field configurations to a data frame, Grafana can configure visualizations automatically. For example, you could configure Grafana to automatically set the unit provided by the data source. -## Transformations +## Data transformations -Along with the type information, field configs enable _data transformations_ within Grafana. +We have seen how field configs contain type information, and they also have another role. Data frame fields enable _data transformations_ within Grafana. A data transformation is any function that accepts a data frame as input, and returns another data frame as output. By using data frames in your plugin, you get a range of transformations for free. +To learn more about data transformations in Grafana, refer to [Transform data](https://grafana.com/docs/grafana/latest/panels-visualizations/query-transform-data/transform-data/). + ## Data frames as time series A data frame with at least one time field is considered a _time series_. @@ -76,9 +98,9 @@ For more information on time series, refer to our [Introduction to time series]( ### Wide format -When a collection of time series shares the same _time index_—the time fields in each time series are identical—they can be stored together, in a _wide_ format. By reusing the time field, we can reduce the amount of data being sent to the browser. +When a collection of time series shares the same _time index_—the time fields in each time series are identical—they can be stored together, in a _wide_ format. By reusing the time field, less data is sent to the browser. -In this example, the `cpu` usage from each host share the time index, so we can store them in the same data frame. +In this example, the `cpu` usage from each host shares the time index, so we can store them in the same data frame: ```text Name: Wide @@ -93,7 +115,7 @@ Dimensions: 3 fields by 2 rows +---------------------+-----------------+-----------------+ ``` -However, if the two time series don't share the same time values, they are represented as two distinct data frames. +However, if the two time series don't share the same time values, they are represented as two distinct data frames: ```text Name: cpu @@ -119,17 +141,17 @@ Dimensions: 2 fields by 2 rows +---------------------+-----------------+ ``` -The wide format can typically be used when multiple time series are collected by the same process. In this case, every measurement is made at the same interval and will therefore share the same time values. +A typical use for the wide format is when multiple time series are collected by the same process. In this case, every measurement is made at the same interval and therefore shares the same time values. ### Long format -Some data sources return data in a _long_ format (also called _narrow_ format). This is common format returned by, for example, SQL databases. +Some data sources return data in a _long_ format (also called _narrow_ format). This is a common format returned by, for example, SQL databases. -In long format, string values are represented as separate fields rather than as labels. As a result, a data form in long form may have duplicated time values. +In the long format, string values are represented as separate fields rather than as labels. As a result, a data form in long form may have duplicated time values. Grafana can detect and convert data frames in long format into wide format. -For example, the following data frame in long format: +For example, the following data frame appears in long format: ```text Name: Long @@ -146,7 +168,7 @@ Dimensions: 4 fields by 4 rows +---------------------+-----------------+-----------------+----------------+ ``` -can be converted into a data frame in wide format: +The above table can be converted into a data frame in wide format like this: ```text Name: Wide @@ -161,11 +183,11 @@ Dimensions: 5 fields by 2 rows +---------------------+------------------+------------------+------------------+------------------+ ``` -> **Note:** Not all panels support the wide time series data frame format. To keep full backward compatibility we've introduced a transformation that can be used to convert from the wide to the long format. For usage information, refer to the [Prepare time series-transformation]({{< relref "../../panels-visualizations/query-transform-data/transform-data/#prepare-time-series" >}}). +> **Note:** Not all panels support the wide time series data frame format. To keep full backward compatibility Grafana has introduced a transformation that you can use to convert from the wide to the long format. For usage information, refer to the [Prepare time series-transformation]({{< relref "../../panels-visualizations/query-transform-data/transform-data/#prepare-time-series" >}}). ## Technical references -This section contains links to technical reference and implementations of data frames. +The concept of a data frame in Grafana is borrowed from data analysis tools like the [R programming language](https://www.r-project.org), and [Pandas](https://pandas.pydata.org/). Other technical references are provided below. ### Apache Arrow diff --git a/docs/sources/developers/plugins/error-handling.md b/docs/sources/developers/plugins/error-handling.md index a7535cdc74d..778952f0d3d 100644 --- a/docs/sources/developers/plugins/error-handling.md +++ b/docs/sources/developers/plugins/error-handling.md @@ -1,10 +1,10 @@ --- -title: Error handling +title: Work with error handling --- -# Error handling +# Work with error handling -This guide explains how to handle errors in plugins. +This guide explains how to handle errors in plugins and provides suggestions for common scenarios. ## Provide usable defaults @@ -33,7 +33,7 @@ Grafana displays the error message in the top-left corner of the panel. {{< figure src="/static/img/docs/panel_error.png" class="docs-image--no-shadow" max-width="850px" >}} -Avoid displaying overly-technical error messages to the user. If you want to let technical users report an error, consider logging it instead. +We recommend that you avoid displaying overly technical error messages to the user. If you want to let technical users report an error, consider logging it to the console instead. ```ts try { @@ -44,7 +44,9 @@ try { } ``` -> **Note:** Grafana displays the exception message in the UI as written, so we recommend using grammatically correct sentences. For more information, refer to the [Documentation style guide](https://github.com/grafana/grafana/blob/main/contribute/style-guides/documentation-style-guide.md). +> **Note:** Grafana displays the exception message in the UI as written, so use grammatically correct sentences. For more information, refer to the [Documentation style guide](https://github.com/grafana/grafana/blob/main/contribute/style-guides/documentation-style-guide.md). + +## Common error scenarios Here are some examples of situations where you might want to display an error to the user. diff --git a/docs/sources/developers/plugins/migration-guide/angular-react/_index.md b/docs/sources/developers/plugins/migration-guide/angular-react/_index.md index 4260bbbcaac..b4b0e7e20f6 100644 --- a/docs/sources/developers/plugins/migration-guide/angular-react/_index.md +++ b/docs/sources/developers/plugins/migration-guide/angular-react/_index.md @@ -13,31 +13,17 @@ weight: 1000 # Migrate a plugin from AngularJS to React -If you’re looking to migrate a plugin to the new plugin platform, then we recommend that you release it under a new major version. Consider keeping a release branch for the previous version to be able to roll out patch releases for versions prior to Grafana 7. +If you want to migrate a plugin to Grafana's React-based plugin platform, then we recommend that you release it under a new major version. Consider keeping a release branch for the previous version to be able to roll out patch releases for versions prior to Grafana 7. -While there's no 1-to-1 migration path from an Angular plugin to the new React platform, from early adopters, we’ve learned that one of the easiest ways to migrate is to: +While there's no standard migration path from an Angular plugin to the new React platform, we’ve learned that one of the easiest ways to migrate is to: 1. Create a new branch called `migrate-to-react`. -1. Start from scratch with one of the templates provided by Grafana Toolkit. +1. Start from scratch with one of the templates provided by the [Create-plugin](https://www.npmjs.com/package/@grafana/create-plugin) tool. 1. Move the existing code into the new plugin incrementally, one component at a time. ## Migrate a panel plugin -Prior to Grafana 7.0, you would export a MetricsPanelCtrl from module.ts. - -**src/module.ts** - -```ts -import { MetricsPanelCtrl } from 'grafana/app/plugins/sdk'; - -class MyPanelCtrl extends MetricsPanelCtrl { - // ... -} - -export { MyPanelCtrl as PanelCtrl }; -``` - -Starting with 7.0, plugins now export a PanelPlugin from module.ts where MyPanel is a React component containing the props from PanelProps. +Starting with Grafana 7.0, plugins export a `PanelPlugin` from `module.ts` where `MyPanel`is a React component containing the props from `PanelProps`. **src/module.ts** @@ -64,8 +50,8 @@ export function MyPanel({ options, data, width, height }: Props) { While all plugins are different, we'd like to share a migration process that has worked for some of our users. 1. Define your configuration model and `ConfigEditor`. For many plugins, the configuration editor is the simplest component so it's a good candidate to start with. -1. Implement the `testDatasource()` method on the class that extends `DataSourceApi` using the settings in your configuration model to make sure you can successfully configure and access the external API. -1. Implement the `query()` method. At this point, you can hard-code your query, because we haven’t yet implemented the query editor. The `query()` method supports both the new data frame response and the old TimeSeries response, so don’t worry about converting to the new format just yet. +1. Implement the `testDatasource()` method on the class that extends `DataSourceApi`. Use the settings in your configuration model to make sure that you can successfully configure and access the external API. +1. Implement the `query()` method. At this point, you can hard-code your query, because we haven’t yet implemented the query editor. The `query()` method supports both the new data frame response and the old `TimeSeries` response, so don’t worry about converting to the new format just yet. 1. Implement the `QueryEditor`. How much work this requires depends on how complex your query model is. By now, you should be able to release your new version. diff --git a/docs/sources/developers/plugins/package-a-plugin.md b/docs/sources/developers/plugins/package-a-plugin.md new file mode 100644 index 00000000000..6fff59f4c20 --- /dev/null +++ b/docs/sources/developers/plugins/package-a-plugin.md @@ -0,0 +1,67 @@ +--- +description: How to package a plugin +title: Package a plugin +--- + +By packaging a plugin, you can organize the plugin code and make it ready for use in your organization. + +## Package a plugin for distribution + +1. Build the plugin + + ``` + yarn install --pure-lockfile + yarn build + ``` + +1. Optional: If your data source plugin has a backend plugin, build it as well. + + ``` + mage + ``` + + Make sure that all the binaries are executable and have a `0755` (`-rwxr-xr-x`) permission. + +1. Sign the plugin. To learn more, refer to [Sign the plugin]({{< relref "./sign-a-plugin" >}}). + +1. Rename the `dist` directory to match your plugin ID, and then create a ZIP archive. + + ``` + mv dist/ myorg-simple-panel + zip myorg-simple-panel-1.0.0.zip myorg-simple-panel -r + ``` + +1. Optional: Verify that your plugin is packaged correctly using [zipinfo](https://linux.die.net/man/1/zipinfo). + It should look like this: + +``` +zipinfo grafana-clickhouse-datasource-1.1.2.zip + +Archive: grafana-clickhouse-datasource-1.1.2.zip +Zip file size: 34324077 bytes, number of entries: 22 +drwxr-xr-x 0 bx stor 22-Mar-24 23:23 grafana-clickhouse-datasource/ +-rw-r--r-- 1654 bX defN 22-Mar-24 23:23 grafana-clickhouse-datasource/CHANGELOG.md +-rw-r--r-- 11357 bX defN 22-Mar-24 23:23 grafana-clickhouse-datasource/LICENSE +-rw-r--r-- 2468 bX defN 22-Mar-24 23:23 grafana-clickhouse-datasource/MANIFEST.txt +-rw-r--r-- 8678 bX defN 22-Mar-24 23:23 grafana-clickhouse-datasource/README.md +drwxr-xr-x 0 bx stor 22-Mar-24 23:23 grafana-clickhouse-datasource/dashboards/ +-rw-r--r-- 42973 bX defN 22-Mar-24 23:23 grafana-clickhouse-datasource/dashboards/cluster-analysis.json +-rw-r--r-- 56759 bX defN 22-Mar-24 23:23 grafana-clickhouse-datasource/dashboards/data-analysis.json +-rw-r--r-- 39406 bX defN 22-Mar-24 23:23 grafana-clickhouse-datasource/dashboards/query-analysis.json +-rwxr-xr-x 16469136 bX defN 22-Mar-24 23:23 grafana-clickhouse-datasource/gpx_clickhouse_darwin_amd64 +-rwxr-xr-x 16397666 bX defN 22-Mar-24 23:23 grafana-clickhouse-datasource/gpx_clickhouse_darwin_arm64 +-rwxr-xr-x 14942208 bX defN 22-Mar-24 23:23 grafana-clickhouse-datasource/gpx_clickhouse_linux_amd64 +-rwxr-xr-x 14155776 bX defN 22-Mar-24 23:23 grafana-clickhouse-datasource/gpx_clickhouse_linux_arm +-rwxr-xr-x 14548992 bX defN 22-Mar-24 23:23 grafana-clickhouse-datasource/gpx_clickhouse_linux_arm64 +-rwxr-xr-x 15209472 bX defN 22-Mar-24 23:23 grafana-clickhouse-datasource/gpx_clickhouse_windows_amd64.exe +drwxr-xr-x 0 bx stor 22-Mar-24 23:23 grafana-clickhouse-datasource/img/ +-rw-r--r-- 304 bX defN 22-Mar-24 23:23 grafana-clickhouse-datasource/img/logo.png +-rw-r--r-- 1587 bX defN 22-Mar-24 23:23 grafana-clickhouse-datasource/img/logo.svg +-rw-r--r-- 138400 bX defN 22-Mar-24 23:23 grafana-clickhouse-datasource/module.js +-rw-r--r-- 808 bX defN 22-Mar-24 23:23 grafana-clickhouse-datasource/module.js.LICENSE.txt +-rw-r--r-- 487395 bX defN 22-Mar-24 23:23 grafana-clickhouse-datasource/module.js.map +-rw-r--r-- 1616 bX defN 22-Mar-24 23:23 grafana-clickhouse-datasource/plugin.json +22 files, 92516655 bytes uncompressed, 34319591 bytes compressed: 62.9% +``` + +When you've packaged your plugin, you can proceed to [publishing a plugin]({{< relref "publish-a-plugin.md" >}}) or [installing a packaged plugin](https://grafana.com/docs/grafana/latest/administration/plugin-management/#install-a-packaged-plugin). diff --git a/docs/sources/developers/plugins/publish-a-plugin.md b/docs/sources/developers/plugins/publish-a-plugin.md index 9f12dc73408..35e3c432f2b 100644 --- a/docs/sources/developers/plugins/publish-a-plugin.md +++ b/docs/sources/developers/plugins/publish-a-plugin.md @@ -39,7 +39,7 @@ Follow these steps to publish your plugin for the first time. 1. [Sign in](/auth/sign-in) to your Grafana Cloud account. 1. In the left menu, under **Org settings**, click **My Plugins**. -1. Click **Submit Plugin**. The Create Plugin Submission form appears. +1. Click **Submit Plugin**. The Create Plugin Submission dialog appears. {{< figure src="/static/img/docs/plugins/plugins-submission-create2.png" class="docs-image--no-shadow" max-width="650px" >}} @@ -54,10 +54,9 @@ Follow these steps to publish your plugin for the first time. - The remaining questions help us determine the [signature level]({{< relref "./sign-a-plugin#plugin-signature-levels" >}}) for your plugin. 1. Click **Submit**. After you submit your plugin, we run an automated validation to make sure it adheres to our guidelines. - Your submission is placed in the review queue after the submission is validated. - -All submissions are manually inspected by a plugin reviewer. -For every new plugin, we perform a manual review that includes the following checks: + Once your submission passes the validation, it's placed in a review queue. + All submissions are manually inspected by a plugin reviewer. + For every new plugin, we perform a manual review that includes the following checks: - **Code review:** For quality and security purposes, we review the source code for the plugin. If you're unable to make the source code publicly available, let us know in a comment on your plugin submission. @@ -71,10 +70,8 @@ To submit an **update** for an already published plugin: 1. [Sign in](/auth/sign-in) to your Grafana Cloud account. 1. In the left menu, under **Org settings**, click **My Plugins**. -1. Click **Submit Update** for the plugin you want to update. The Create Plugin Submission form appears. - +1. Click **Submit Update** for the plugin you want to update. The Create Plugin Submission dialog appears. {{< figure src="/static/img/docs/plugins/plugins-submission-create2.png" class="docs-image--no-shadow" max-width="650px" >}} - 1. Enter the information requested by the form. - **OS & Architecture:** - Select **Single** if your plugin archive contains binaries for multiple architectures. @@ -110,7 +107,7 @@ To submit an **update** for an already published plugin: ### Can I unlist my plugin from the Grafana plugin catalog in case of a bug? -- In the event of a bug, unlisting the plugin from our catalog may be possible in exceptional cases, such as security concerns. However, we don't have control over the specific instances where the plugin is installed. +- In the event of a bug, unlisting the plugin from our catalog may be possible in exceptional cases, such as security concerns. However, we don't have control over the instances where the plugin is installed. ### Can I distribute my plugin somewhere else other than the Grafana plugin catalog? @@ -127,5 +124,5 @@ To submit an **update** for an already published plugin: ### What source code URL formats are supported? - Using a tag or branch: `https://github.com/grafana/clock-panel/tree/v2.1.3` -- A tag or branch and the code is in a subdirectory (important for mono repos): `https://github.com/grafana/clock-panel/tree/v2.1.3/plugin/` (here, the plugin contains the plugin code) +- Using a tag or branch and the code is in a subdirectory (important for mono repos): `https://github.com/grafana/clock-panel/tree/v2.1.3/plugin/` (here, the plugin contains the plugin code) - Using the latest main or master branch commit: `https://github.com/grafana/clock-panel/` (not recommended, it's better to pass a tag or branch) diff --git a/docs/sources/developers/plugins/working-with-data-frames.md b/docs/sources/developers/plugins/working-with-data-frames.md index ac56e08f77f..8a5df83c1ab 100644 --- a/docs/sources/developers/plugins/working-with-data-frames.md +++ b/docs/sources/developers/plugins/working-with-data-frames.md @@ -1,20 +1,20 @@ --- -title: Working with data frames +title: Work with data frames --- -# Working with data frames +# Work with data frames -The data frame is a columnar data structure which allows efficient querying of large amounts of data. Since data frames are a central concept when developing plugins for Grafana, in this guide we'll look at some ways you can use them. +The [data frame]({{< relref "data-frames" >}}) is a columnar data structure that allows for efficient querying of large amounts of data. Since data frames are a central concept when developing plugins for Grafana, in this guide we'll look at some ways you can use them. -The DataFrame interface contains a `name` and an array of `fields` where each field contains the name, type, and the values for the field. +The `DataFrame` interface contains a `name` and an array of `fields` where each field contains the name, type, and the values for the field. -> **Note:** If you're looking to migrate an existing plugin to use the data frame format, refer to [Migrate to data frames]({{< relref "migration-guide/#migrate-to-data-frames" >}}). +> **Note:** If you want to migrate an existing plugin to use the data frame format, refer to [Migrate to data frames]({{< relref "migration-guide/#migrate-to-data-frames" >}}). ## Create a data frame -If you build a data source plugin, then you'll most likely want to convert a response from an external API to a data frame. Let's look at how to create a data frame. +If you build a data source plugin, then you'll most likely want to convert a response from an external API to a data frame. Let's look at how to do this. -Let's start with creating a simple data frame that represents a time series. The easiest way to create a data frame is to use the toDataFrame function. +Let's start with creating a simple data frame that represents a time series. The easiest way to create a data frame is to use the `toDataFrame` function. ```ts // Need to be of the same length. @@ -31,9 +31,9 @@ const frame = toDataFrame({ }); ``` -> **Note:** Data frames representing time series contain at least a `time` field, and a `number` field. By convention, built-in plugins use `Time` and `Value` as field names for data frames containing time series data. +> **Note:** Data frames representing time series contain at least a `time` field and a `number` field. By convention, built-in plugins use `Time` and `Value` as field names for data frames containing time series data. -As you can see from the example, creating data frames like this requires that your data is already stored as columnar data. If you already have the records in the form of an array of objects, then you can pass it to `toDataFrame` which tries to guess the schema based on the types and names of the objects in the array. If you're creating complex data frames this way, then be sure to verify that you get the schema you expect. +As you can see from the example, to create data frames like this, your data must already be stored as columnar data. If you already have the records in the form of an array of objects, then you can pass it to `toDataFrame`. In this case, `toDataFrame` tries to guess the schema based on the types and names of the objects in the array. To create complex data frames this way, be sure to verify that you get the schema you expect. ```ts const series = [ @@ -57,7 +57,7 @@ function SimplePanel({ data: Props }) { } ``` -Before you start reading the data, think about what data you expect. For example, to visualize a time series we'd need at least one time field, and one number field. +Before you start reading the data, think about what data you expect. For example, to visualize a time series you need at least one time field and one number field. ```ts const timeField = frame.fields.find((field) => field.type === FieldType.time); @@ -78,7 +78,7 @@ for (let i = 0; i < frame.length; i++) { } ``` -Alternatively, you can use the DataFrameView, which gives you an array of objects that contain a property for each field in the frame. +Alternatively, you can use the `DataFrameView`, which gives you an array of objects that contain a property for each field in the frame. ```ts const view = new DataFrameView(frame); @@ -113,7 +113,7 @@ return ( ); ``` -To apply field options to the name of a field, use getFieldDisplayName. +To apply field options to the name of a field, use `getFieldDisplayName`. ```ts const valueField = frame.fields.find((field) => field.type === FieldType.number); From f100a4c68003300e99d7b6fa86c6f5196c41596f Mon Sep 17 00:00:00 2001 From: Leon Sorokin Date: Tue, 9 May 2023 16:24:13 -0500 Subject: [PATCH 086/179] Heatmap: Handle unsorted timestamps in calculate mode (#68111) --- public/app/features/transformers/calculateHeatmap/heatmap.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/public/app/features/transformers/calculateHeatmap/heatmap.ts b/public/app/features/transformers/calculateHeatmap/heatmap.ts index 15677698baf..71864a375f1 100644 --- a/public/app/features/transformers/calculateHeatmap/heatmap.ts +++ b/public/app/features/transformers/calculateHeatmap/heatmap.ts @@ -15,6 +15,7 @@ import { durationToMilliseconds, parseDuration, } from '@grafana/data'; +import { isLikelyAscendingVector } from '@grafana/data/src/transformations/transformers/joinDataFrames'; import { ScaleDistribution, HeatmapCellLayout, @@ -325,7 +326,7 @@ export function calculateHeatmapFromData(frames: DataFrame[], options: HeatmapCa }; const heat2d = heatmap(xs, ys, { - xSorted: true, + xSorted: isLikelyAscendingVector(xs), xTime: xField.type === FieldType.time, xMode: xBucketsCfg.mode, xSize: From 4ee894b0bea4306bc171a16c0cc5f15fd799b414 Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Tue, 9 May 2023 14:29:52 -0700 Subject: [PATCH 087/179] Heatmap: Fix log scale editor (#68132) --- packages/grafana-ui/src/options/builder/axis.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/grafana-ui/src/options/builder/axis.tsx b/packages/grafana-ui/src/options/builder/axis.tsx index 6b940e0736c..2a728eff2e7 100644 --- a/packages/grafana-ui/src/options/builder/axis.tsx +++ b/packages/grafana-ui/src/options/builder/axis.tsx @@ -150,6 +150,7 @@ const LOG_DISTRIBUTION_OPTIONS: Array> = [ */ export const ScaleDistributionEditor = ({ value, onChange }: StandardEditorProps) => { const type = value?.type ?? ScaleDistribution.Linear; + const log = value?.log ?? 2; return ( <>
    @@ -160,7 +161,7 @@ export const ScaleDistributionEditor = ({ value, onChange }: StandardEditorProps onChange({ ...value, type: v!, - log: v === ScaleDistribution.Linear ? undefined : value.log ?? 2, + log: v === ScaleDistribution.Linear ? undefined : log, }); }} /> @@ -169,7 +170,7 @@ export const ScaleDistributionEditor = ({ value, onChange }: StandardEditorProps { onChange({ ...value, From d1229b532d22beb1c4853fe02f2616a3286bb71f Mon Sep 17 00:00:00 2001 From: Selene Date: Wed, 10 May 2023 01:13:45 +0200 Subject: [PATCH 088/179] Chore: Delete codegen dead code (#68072) * Delete codegen dead code * Use codejen * Fix lint * Use fs verify --- devenv/jsonnet/dev-dashboards.go | 21 ++- go.mod | 5 +- go.sum | 65 ++----- pkg/codegen/coremodel.go | 288 ------------------------------- pkg/codegen/diffwrite.go | 134 -------------- pkg/codegen/tmpl.go | 21 --- 6 files changed, 35 insertions(+), 499 deletions(-) delete mode 100644 pkg/codegen/coremodel.go delete mode 100644 pkg/codegen/diffwrite.go diff --git a/devenv/jsonnet/dev-dashboards.go b/devenv/jsonnet/dev-dashboards.go index b1d926a25ff..92079311756 100644 --- a/devenv/jsonnet/dev-dashboards.go +++ b/devenv/jsonnet/dev-dashboards.go @@ -5,6 +5,7 @@ package main import ( "bytes" + "context" "embed" "fmt" "os" @@ -13,8 +14,8 @@ import ( "strings" "text/template" + "github.com/grafana/codejen" dev_dashboards "github.com/grafana/grafana/devenv/dev-dashboards" - "github.com/grafana/grafana/pkg/codegen" ) var ( @@ -35,17 +36,21 @@ func main() { if err != nil { panic(err) } - wd := codegen.NewWriteDiffer() - wd[OUTPUT_PATH] = []byte(out) + + f := codejen.NewFile(OUTPUT_PATH, []byte(out), dummyJenny{}) + fs := codejen.NewFS() + if err = fs.Add(*f); err != nil { + panic(err) + } if _, set := os.LookupEnv("CODEGEN_VERIFY"); set { - err = wd.Verify() + err = fs.Verify(context.Background(), "") if err != nil { fmt.Fprintf(os.Stderr, "generated code is not up to date:\n%s\nrun `make gen-jsonnet` to regenerate\n\n", err) os.Exit(1) } } else { - err = wd.Write() + err = fs.Verify(context.Background(), "") if err != nil { fmt.Fprintf(os.Stderr, "error while writing generated code to disk:\n%s\n", err) os.Exit(1) @@ -121,3 +126,9 @@ func (g *libjsonnetGen) readDir(dir string) error { } return nil } + +type dummyJenny struct{} + +func (dummyJenny) JennyName() string { + return "dummyJenny" +} diff --git a/go.mod b/go.mod index 782a4fdb6c8..beb2c46400f 100644 --- a/go.mod +++ b/go.mod @@ -15,9 +15,6 @@ replace github.com/docker/docker => github.com/moby/moby v23.0.4+incompatible // contains openapi encoder fixes. remove ASAP replace cuelang.org/go => github.com/sdboyer/cue v0.5.0-beta.2.0.20230419165817-251c3ae823d8 -// contains go generation fixes -replace github.com/deepmap/oapi-codegen => github.com/spinillos/oapi-codegen v1.12.5-0.20230417081915-2945b61c0b1c - // For some insane reason, client-go seems to have a broken v12.0.0 tag on it that forces us to // hoist a replace statement. replace k8s.io/client-go => k8s.io/client-go v0.25.3 @@ -143,7 +140,7 @@ require ( github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/cheekybits/genny v1.0.0 // indirect github.com/cockroachdb/apd/v2 v2.0.2 // indirect - github.com/deepmap/oapi-codegen v1.12.4 + github.com/deepmap/oapi-codegen v1.12.4 // indirect github.com/dennwc/varint v1.0.0 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/docker/go-units v0.5.0 // indirect diff --git a/go.sum b/go.sum index e607e3eedff..a3d557fd4c0 100644 --- a/go.sum +++ b/go.sum @@ -458,8 +458,6 @@ github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0Bsq github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= github.com/buildkite/yaml v2.1.0+incompatible h1:xirI+ql5GzfikVNDmt+yeiXpf/v1Gt03qXTtT5WXdr8= github.com/buildkite/yaml v2.1.0+incompatible/go.mod h1:UoU8vbcwu1+vjZq01+KrpSeLBgQQIjL/H7Y6KwikUrI= -github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= -github.com/bytedance/sonic v1.8.0/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= github.com/c-bata/go-prompt v0.2.2/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34= github.com/cactus/go-statsd-client/statsd v0.0.0-20191106001114-12b4e2b38748/go.mod h1:l/bIBLeOl9eX+wxJAzxS4TveKRtAqlyDpHjhkfO0MEI= github.com/caio/go-tdigest v3.1.0+incompatible h1:uoVMJ3Q5lXmVLCCqaMGHLBWnbGoN6Lpu7OAUPR60cds= @@ -485,8 +483,6 @@ github.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOo github.com/checkpoint-restore/go-criu/v5 v5.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d89JlCLQIfgVcNsNN0t6T2M= github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE= github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ= -github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= -github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= github.com/chromedp/cdproto v0.0.0-20220208224320-6efb837e6bc2 h1:XCdvHbz3LhewBHN7+mQPx0sg/Hxil/1USnBmxkjHcmY= github.com/chromedp/cdproto v0.0.0-20220208224320-6efb837e6bc2/go.mod h1:At5TxYYdxkbQL0TSefRjhLE3Q0lgvqKKMSFUglJ7i1U= github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww= @@ -631,6 +627,7 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/crewjam/httperr v0.2.0/go.mod h1:Jlz+Sg/XqBQhyMjdDiC+GNNRzZTD7x39Gu3pglZ5oH4= github.com/crossdock/crossdock-go v0.0.0-20160816171116-049aabb0122b/go.mod h1:v9FBN7gdVTpiD/+LZ7Po0UKvROyT87uLVxTHVky/dlQ= +github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4= github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4= github.com/cznic/b v0.0.0-20180115125044-35e9bbe41f07/go.mod h1:URriBxXwVq5ijiJ12C7iIZqlA69nTlI+LgI6/pwftG8= github.com/cznic/fileutil v0.0.0-20180108211300-6a051e75936f/go.mod h1:8S58EK26zhXSxzv7NQFpnliaOQsmDUxvoQO3rt154Vg= @@ -657,9 +654,10 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dchest/uniuri v0.0.0-20160212164326-8902c56451e9/go.mod h1:GgB8SF9nRG+GqaDtLcwJZsQFhcogVCJ79j4EdT0c2V4= github.com/dchest/uniuri v1.2.0/go.mod h1:fSzm4SLHzNZvWLvWJew423PhAzkpNQYq+uNLq4kxhkY= -github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.0-20210816181553-5444fa50b93d/go.mod h1:tmAIfUFEirG/Y8jhZ9M+h36obRZAk/1fcSpXwAVlfqE= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= +github.com/deepmap/oapi-codegen v1.6.0/go.mod h1:ryDa9AgbELGeB+YEXE1dR53yAjHwFvE9iAUlWl9Al3M= +github.com/deepmap/oapi-codegen v1.8.2/go.mod h1:YLgSKSDv/bZQB7N4ws6luhozi3cEdRktEqrX88CvjIw= +github.com/deepmap/oapi-codegen v1.12.4 h1:pPmn6qI9MuOtCz82WY2Xaw46EQjgvxednXXrP7g5Q2s= +github.com/deepmap/oapi-codegen v1.12.4/go.mod h1:3lgHGMu6myQ2vqbbTXH2H1o4eXFTGnFiDaOaKKl5yas= github.com/dennwc/varint v1.0.0 h1:kGNFFSSw8ToIy3obO/kKr8U9GZYUAxQEVuix4zfDWzE= github.com/dennwc/varint v1.0.0/go.mod h1:hnItb35rvZvJrbTALZtY/iQfDs48JKRG1RPpgziApxA= github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0= @@ -798,6 +796,8 @@ github.com/gchaincl/sqlhooks v1.3.0 h1:yKPXxW9a5CjXaVf2HkQn6wn7TZARvbAOAelr3H8vK github.com/gchaincl/sqlhooks v1.3.0/go.mod h1:9BypXnereMT0+Ys8WGWHqzgkkOfHIhyeUCqXC24ra34= github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= github.com/gdamore/tcell v1.3.0/go.mod h1:Hjvr+Ofd+gLglo7RYKxxnzCBmev3BzsS67MebKS4zMM= +github.com/getkin/kin-openapi v0.53.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4= +github.com/getkin/kin-openapi v0.61.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4= github.com/getkin/kin-openapi v0.76.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSyhcnluiMv+Xg= github.com/getkin/kin-openapi v0.115.0 h1:c8WHRLVY3G8m9jQTy0/DnIuljgRwTCB5twZytQS4JyU= github.com/getkin/kin-openapi v0.115.0/go.mod h1:l5e9PaFUo9fyLJCPGQeXI2ML8c3P8BHOEV2VaAVf/pc= @@ -809,7 +809,6 @@ github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm github.com/gin-gonic/gin v1.5.0/go.mod h1:Nd6IXA8m5kNZdNEHMBd93KT+mdY3+bewLgRvmCsR2Do= github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= github.com/gin-gonic/gin v1.7.3/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY= -github.com/gin-gonic/gin v1.9.0/go.mod h1:W1Me9+hsUSyj3CePGrd1/QrKJMSJ1Tu/0hFEH89961k= github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0= github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= @@ -819,7 +818,7 @@ github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1T github.com/go-asn1-ber/asn1-ber v1.5.4 h1:vXT6d/FNDiELJnLb6hGNa309LMsrCoYFvpwHDF0+Y1A= github.com/go-asn1-ber/asn1-ber v1.5.4/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= github.com/go-chi/chi v4.1.0+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= -github.com/go-chi/chi/v5 v5.0.8/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +github.com/go-chi/chi/v5 v5.0.0/go.mod h1:BBug9lr0cqtdAhsu6R4AAdvufI0/XBzAQSsUqJpoZOs= github.com/go-fonts/dejavu v0.1.0/go.mod h1:4Wt4I4OU2Nq9asgDCteaAaWZOV24E+0/Pwo0gppep4g= github.com/go-fonts/latin-modern v0.2.0/go.mod h1:rQVLdDMK+mK1xscDwsqM5J8U2jrRa3T0ecnM9pNujks= github.com/go-fonts/liberation v0.1.1/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY= @@ -993,16 +992,12 @@ github.com/go-openapi/validate v0.21.0/go.mod h1:rjnrwK57VJ7A8xqfpAOEKRH8yQSGUri github.com/go-openapi/validate v0.22.1 h1:G+c2ub6q47kfX1sOBLwIQwzBVt8qmOAARyo/9Fqs9NU= github.com/go-openapi/validate v0.22.1/go.mod h1:rjnrwK57VJ7A8xqfpAOEKRH8yQSGUriMu5/zuPSQ1hg= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= -github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= -github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY= github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= -github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= -github.com/go-playground/validator/v10 v10.11.2/go.mod h1:NieE624vt4SCTJtD87arVLvdmjPAeV8BQlHtMnw9D7s= github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= github.com/go-resty/resty/v2 v2.1.1-0.20191201195748-d7b97669fe48 h1:JVrqSeQfdhYRFk24TvhTZWU0q8lfCojxZQFi3Ou7+uY= @@ -1054,8 +1049,6 @@ github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJA github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= -github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= -github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-yaml v1.9.5/go.mod h1:U/jl18uSupI5rdI2jmuCswEA2htH9eXfferR3KfscvA= github.com/gocql/gocql v0.0.0-20190301043612-f6df8288f9b4/go.mod h1:4Fw1eo5iaEhDUs8XyuhSVCVy52Jq3L+/3GJgYkwc+/0= github.com/gocql/gocql v0.0.0-20200228163523-cd4b606dd2fb/go.mod h1:DL0ekTmBSTdlNF25Orwt/JMzqIq3EJ4MVa/J/uK64OY= @@ -1084,7 +1077,6 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/gogo/status v1.0.3/go.mod h1:SavQ51ycCLnc7dGyJxp8YAmudx8xqiVrRf+6IXRsugc= github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= -github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-jwt/jwt/v4 v4.4.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= @@ -1288,8 +1280,6 @@ github.com/grafana/go-mssqldb v0.9.1 h1:3CqteWF0CadwXV9f3FxoI+i3uSW3azjTlQipyOJt github.com/grafana/go-mssqldb v0.9.1/go.mod h1:HTCsUqZdb7oIO7jc37YauiSB5C3P/13AnpctVWBhlus= github.com/grafana/go-mssqldb v0.9.2 h1:FkyRJR4ywsT07iMtpFMHStrl8uuNkGIwp253Fee06z8= github.com/grafana/go-mssqldb v0.9.2/go.mod h1:HTCsUqZdb7oIO7jc37YauiSB5C3P/13AnpctVWBhlus= -github.com/grafana/grafana-aws-sdk v0.12.0 h1:eUjFdFZeZE+nyu/RMRz+qFxTBew69ToLBrbRhTbjkfM= -github.com/grafana/grafana-aws-sdk v0.12.0/go.mod h1:rCXLYoMpPqF90U7XqgVJ1HIAopFVF0bB3SXBVEJIm3I= github.com/grafana/grafana-aws-sdk v0.15.0 h1:ZOPHQcC5NUFi1bLTwnju91G0KmGh1z+qXOKj9nDfxNs= github.com/grafana/grafana-aws-sdk v0.15.0/go.mod h1:rCXLYoMpPqF90U7XqgVJ1HIAopFVF0bB3SXBVEJIm3I= github.com/grafana/grafana-azure-sdk-go v1.6.0 h1:lxvH/mVY7gKBtJKhZ4B/6tIZFY7Jth97HxBA38olaxs= @@ -1603,7 +1593,6 @@ github.com/klauspost/compress v1.15.1/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47e github.com/klauspost/compress v1.15.13 h1:NFn1Wr8cfnenSJSA46lLq4wHCcBzKTSjnBIexDMMOV0= github.com/klauspost/compress v1.15.13/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= github.com/klauspost/cpuid v0.0.0-20170728055534-ae7887de9fa5/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= -github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/crc32 v0.0.0-20161016154125-cb6bfca970f6/go.mod h1:+ZoRqAPRLkC4NPOvfYeR5KNOrY6TD+/sAC3HXPZgDYg= github.com/klauspost/pgzip v1.0.2-0.20170402124221-0bf5dcad4ada/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/knadh/koanf v1.2.0/go.mod h1:xpPTwMhsA/aaQLAilyCCqfpEiY1gpa160AiCuWHJUjY= @@ -1630,22 +1619,17 @@ github.com/kshvakov/clickhouse v1.3.5/go.mod h1:DMzX7FxRymoNkVgizH0DWAL8Cur7wHLg github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/labstack/echo/v4 v4.2.1/go.mod h1:AA49e0DZ8kk5jTOOCKNuPR6oTnBS0dYiM4FW1e6jwpg= github.com/labstack/echo/v4 v4.10.2 h1:n1jAhnq/elIFTHr1EYpiYtyKgx4RW9ccVgkqByZaN2M= github.com/labstack/echo/v4 v4.10.2/go.mod h1:OEyqf2//K1DFdE57vw2DRgWY0M7s65IVQO2FzvI4J5k= +github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8= github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= github.com/leesper/go_rng v0.0.0-20190531154944-a612b043e353 h1:X/79QL0b4YJVO5+OsPH9rF2u428CIrGL/jLmPsoOQQ4= github.com/leesper/go_rng v0.0.0-20190531154944-a612b043e353/go.mod h1:N0SVk0uhy+E1PZ3C9ctsPRlvOPAFPkCNlcPBDkt0N3U= github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= -github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= github.com/leoluk/perflib_exporter v0.1.0/go.mod h1:rpV0lYj7lemdTm31t7zpCqYqPnw7xs86f+BaaNBVYFM= -github.com/lestrrat-go/backoff/v2 v2.0.8/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y= -github.com/lestrrat-go/blackmagic v1.0.0/go.mod h1:TNgH//0vYSs8VXDCfkZLgIrVTTXQELZffUV0tz3MtdQ= -github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E= -github.com/lestrrat-go/iter v1.0.1/go.mod h1:zIdgO1mRKhn8l9vrZJZz9TUMMFbQbLeTsbqPDrJ/OJc= -github.com/lestrrat-go/jwx v1.2.25/go.mod h1:zoNuZymNl5lgdcu6P7K6ie2QRll5HVfF4xwxBBK1NxY= -github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= github.com/lib/pq v1.10.6 h1:jbk+ZieJ0D7EVGJYpL9QTz7/YW6UHbmdnZWYyK5cdBs= github.com/lib/pq v1.10.6/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= @@ -1685,15 +1669,17 @@ github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHef github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= -github.com/matryer/moq v0.3.1/go.mod h1:RJ75ZZZD71hejp39j4crZLsEDszGk6iH4v4YsWFKH4s= +github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ= github.com/mattermost/xml-roundtrip-validator v0.1.0 h1:RXbVD2UAl7A7nOTR4u7E3ILa4IbtvKBHw64LDsmu9hU= github.com/mattermost/xml-roundtrip-validator v0.1.0/go.mod h1:qccnGMcpgwcNaBnxqpJpWWUiPNr5H3O8eDgGV9gT5To= github.com/mattetti/filebuffer v1.0.1 h1:gG7pyfnSIZCxdoKq+cPa8T0hhYtD9NxCdI4D7PTjRLM= github.com/mattetti/filebuffer v1.0.1/go.mod h1:YdMURNDOttIiruleeVr6f56OrMc+MydEnTcXwtkxNVs= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= +github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= @@ -1714,7 +1700,6 @@ github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOA github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98= github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= @@ -1941,7 +1926,6 @@ github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrap github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas= -github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= github.com/perimeterx/marshmallow v1.1.4 h1:pZLDH9RjlLGGorbXhcaQLhfuV0pFMNfPO55FuFkxqLw= github.com/perimeterx/marshmallow v1.1.4/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= @@ -2197,8 +2181,6 @@ github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DM github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= github.com/spf13/viper v1.13.0/go.mod h1:Icm2xNL3/8uyh/wFuB1jI7TiTNKp8632Nwegu+zgdYw= -github.com/spinillos/oapi-codegen v1.12.5-0.20230417081915-2945b61c0b1c h1:u9EyGmVLczUaCNBvY4j9NItCJNaQRbEWGUR8fcu20Kk= -github.com/spinillos/oapi-codegen v1.12.5-0.20230417081915-2945b61c0b1c/go.mod h1:WMhniMLAXaHHNYz+Nk+h35qYieaeUaASStrkdkHnBr8= github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0= github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h1:AO3tvPzVZ/ayst6UlUKUv6rcPQInYe3IknH3jYhAKu8= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= @@ -2254,7 +2236,6 @@ github.com/tklauser/numcpus v0.2.2/go.mod h1:x3qojaO3uyYt0i56EW/VUYs7uBvdl2fkfZF github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= -github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ua-parser/uap-go v0.0.0-20211112212520-00c877edfe0f h1:A+MmlgpvrHLeUP8dkBVn4Pnf5Bp5Yk2OALm7SEJLLE8= github.com/ua-parser/uap-go v0.0.0-20211112212520-00c877edfe0f/go.mod h1:OBcG9bn7sHtXgarhUEb3OfCnNsgtGnkVf41ilSZ3K3E= github.com/uber-go/tally v3.3.15+incompatible/go.mod h1:YDTIBxdXyOU/sCWilKB4bgyufu1cEi0jdVnRdxvjnmU= @@ -2272,9 +2253,8 @@ github.com/ugorji/go v1.2.7 h1:qYhyWUUd6WbiM+C6JZAUkIJt/1WrjzNHY9+KCIjVqTo= github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= +github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= -github.com/ugorji/go/codec v1.2.9 h1:rmenucSohSTiyL09Y+l2OCk+FrMxGMzho2+tjr5ticU= -github.com/ugorji/go/codec v1.2.9/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/unknwon/bra v0.0.0-20200517080246-1e3013ecaff8 h1:aVGB3YnaS/JNfOW3tiHIlmNmTDg618va+eT0mVomgyI= github.com/unknwon/bra v0.0.0-20200517080246-1e3013ecaff8/go.mod h1:fVle4kNr08ydeohzYafr20oZzbAkhQT39gKK/pFQ5M4= github.com/unknwon/com v1.0.1 h1:3d1LTxD+Lnf3soQiD4Cp/0BRB+Rsa/+RTvz8GMMzIXs= @@ -2291,6 +2271,7 @@ github.com/urfave/cli/v2 v2.24.4 h1:0gyJJEBYtCV87zI/x2nZCPyDxD51K6xM8SkwjHFCNEU= github.com/urfave/cli/v2 v2.24.4/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= @@ -2500,7 +2481,6 @@ go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= gocloud.dev v0.25.0 h1:Y7vDq8xj7SyM848KXf32Krda2e6jQ4CLh/mTeCSqXtk= gocloud.dev v0.25.0/go.mod h1:7HegHVCYZrMiU3IE1qtnzf/vRrDwLYnRNR3EhWX8x9Y= -golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180505025534-4ec37c66abab/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -2535,6 +2515,7 @@ golang.org/x/crypto v0.0.0-20200422194213-44a606286825/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -2556,12 +2537,9 @@ golang.org/x/crypto v0.0.0-20220128200615-198e4374d7ed/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20221012134737-56aed061732a/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= -golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -2614,7 +2592,6 @@ golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.6.0-dev.0.20220818022119-ed83ed61efb9/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs= golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= @@ -2714,8 +2691,6 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.0.0-20220907135653-1e95f45603a7/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= -golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= -golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= @@ -2854,6 +2829,7 @@ golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200817155316-9781c653f443/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200828194041-157a740278f4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -2937,9 +2913,7 @@ golang.org/x/sys v0.0.0-20220908150016-7ac13a9a928d/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= @@ -2949,8 +2923,6 @@ golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9sn golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= -golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ= @@ -2967,7 +2939,6 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= @@ -2977,6 +2948,7 @@ golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210611083556-38a9dc6acbc6/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -3076,7 +3048,6 @@ golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyj golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4= golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= diff --git a/pkg/codegen/coremodel.go b/pkg/codegen/coremodel.go deleted file mode 100644 index d6c5dc5aee2..00000000000 --- a/pkg/codegen/coremodel.go +++ /dev/null @@ -1,288 +0,0 @@ -package codegen - -import ( - "bytes" - "errors" - "fmt" - "io" - "os" - "path/filepath" - "strings" - "testing/fstest" - - cerrors "cuelang.org/go/cue/errors" - "cuelang.org/go/pkg/encoding/yaml" - "github.com/deepmap/oapi-codegen/pkg/codegen" - "github.com/getkin/kin-openapi/openapi3" - "github.com/grafana/cuetsy" - tsast "github.com/grafana/cuetsy/ts/ast" - "github.com/grafana/grafana/pkg/cuectx" - "github.com/grafana/thema" - "github.com/grafana/thema/encoding/openapi" -) - -// CoremodelDeclaration contains the results of statically analyzing a Grafana -// directory for a Thema lineage. -type CoremodelDeclaration struct { - Lineage thema.Lineage - // Absolute path to the coremodel's coremodel.cue file. - LineagePath string - // Path to the coremodel's coremodel.cue file relative to repo root. - RelativePath string - // Indicates whether the coremodel is considered canonical or not. Generated - // code from not-yet-canonical coremodels should include appropriate caveats in - // documentation and possibly be hidden from external public API surface areas. - IsCanonical bool - - // Indicates whether the coremodel represents an API type, and should therefore - // be included in API client code generation. - IsAPIType bool -} - -// ExtractLineage loads a Grafana Thema lineage from the filesystem. -// -// The provided path must be the absolute path to the file containing the -// lineage to be loaded. -// -// This loading approach is intended primarily for use with code generators, or -// other use cases external to grafana-server backend. For code within -// grafana-server, prefer lineage loaders provided in e.g. pkg/coremodel/*. -func ExtractLineage(path string, rt *thema.Runtime) (*CoremodelDeclaration, error) { - if !filepath.IsAbs(path) { - return nil, fmt.Errorf("must provide an absolute path, got %q", path) - } - - ec := &CoremodelDeclaration{ - LineagePath: path, - } - - var find func(path string) (string, error) - find = func(path string) (string, error) { - parent := filepath.Dir(path) - if parent == path { - return "", errors.New("grafana root directory could not be found") - } - fp := filepath.Join(path, "go.mod") - if _, err := os.Stat(fp); err == nil { - return path, nil - } - return find(parent) - } - groot, err := find(path) - if err != nil { - return ec, err - } - - f, err := os.Open(ec.LineagePath) - if err != nil { - return nil, fmt.Errorf("could not open lineage file at %s: %w", path, err) - } - - byt, err := io.ReadAll(f) - if err != nil { - return nil, err - } - - fs := fstest.MapFS{ - "coremodel.cue": &fstest.MapFile{ - Data: byt, - }, - } - - // ec.RelativePath, err = filepath.Rel(groot, filepath.Dir(path)) - ec.RelativePath, err = filepath.Rel(groot, path) - if err != nil { - // should be unreachable, since we rootclimbed to find groot above - panic(err) - } - ec.RelativePath = filepath.ToSlash(ec.RelativePath) - ec.Lineage, err = cuectx.LoadGrafanaInstancesWithThema(filepath.Dir(ec.RelativePath), fs, rt) - if err != nil { - return ec, err - } - ec.IsCanonical = isCanonical(ec.Lineage.Name()) - ec.IsAPIType = isAPIType(ec.Lineage.Name()) - return ec, nil -} - -// toTemplateObj extracts creates a struct with all the useful strings for template generation. -func (cd *CoremodelDeclaration) toTemplateObj() tplVars { - lin := cd.Lineage - sch := thema.SchemaP(lin, thema.LatestVersion(lin)) - - return tplVars{ - Name: lin.Name(), - LineagePath: cd.RelativePath, - PkgPath: filepath.ToSlash(filepath.Join("github.com/grafana/grafana", filepath.Dir(cd.RelativePath))), - TitleName: strings.Title(lin.Name()), // nolint - LatestSeqv: sch.Version()[0], - LatestSchv: sch.Version()[1], - } -} - -func isCanonical(name string) bool { - return canonicalCoremodels[name] -} - -func isAPIType(name string) bool { - return !nonAPITypes[name] -} - -// FIXME specifying coremodel canonicality DOES NOT belong here - it should be part of the coremodel declaration. -var canonicalCoremodels = map[string]bool{ - "dashboard": false, -} - -// FIXME this also needs to be moved into coremodel metadata -var nonAPITypes = map[string]bool{ - "pluginmeta": true, -} - -// PathVersion returns the string path element to use for the latest schema. -// "x" if not yet canonical, otherwise, "v" -func (cd *CoremodelDeclaration) PathVersion() string { - if !cd.IsCanonical { - return "x" - } - return fmt.Sprintf("v%v", thema.LatestVersion(cd.Lineage)[0]) -} - -// GenerateGoCoremodel generates a standard Go model struct and coremodel -// implementation from a coremodel CUE declaration. -// -// The provided path must be a directory. Generated code files will be written -// to that path. The final element of the path must match the Lineage.Name(). -func (cd *CoremodelDeclaration) GenerateGoCoremodel(path string) (WriteDiffer, error) { - lin, rt := cd.Lineage, cd.Lineage.Runtime() - _, name := filepath.Split(path) - if name != lin.Name() { - return nil, fmt.Errorf("lineage name %q must match final element of path, got %q", lin.Name(), path) - } - - sch := thema.SchemaP(lin, thema.LatestVersion(lin)) - f, err := openapi.GenerateSchema(sch, nil) - if err != nil { - return nil, fmt.Errorf("thema openapi generation failed: %w", err) - } - - str, err := yaml.Marshal(rt.Context().BuildFile(f)) - if err != nil { - return nil, fmt.Errorf("cue-yaml marshaling failed: %w", err) - } - - loader := openapi3.NewLoader() - oT, err := loader.LoadFromData([]byte(str)) - if err != nil { - return nil, fmt.Errorf("loading generated openapi failed; %w", err) - } - - var importbuf bytes.Buffer - if err = tmpls.Lookup("coremodel_imports.tmpl").Execute(&importbuf, tvars_coremodel_imports{ - PackageName: lin.Name(), - }); err != nil { - return nil, fmt.Errorf("error executing imports template: %w", err) - } - - gostr, err := codegen.Generate(oT, codegen.Configuration{ - PackageName: lin.Name(), - Generate: codegen.GenerateOptions{ - Models: true, - }, - Compatibility: codegen.CompatibilityOptions{ - AlwaysPrefixEnumValues: true, - }, - OutputOptions: codegen.OutputOptions{ - SkipFmt: true, - SkipPrune: true, - UserTemplates: map[string]string{ - "imports.tmpl": importbuf.String(), - "typedef.tmpl": tmplTypedef, - }, - }, - }) - if err != nil { - return nil, fmt.Errorf("openapi generation failed: %w", err) - } - - buf := new(bytes.Buffer) - if err = tmpls.Lookup("autogen_header.tmpl").Execute(buf, tvars_autogen_header{ - LineagePath: cd.RelativePath, - GeneratorPath: "pkg/framework/coremodel/gen.go", // FIXME hardcoding is not OK - }); err != nil { - return nil, fmt.Errorf("error executing header template: %w", err) - } - - fmt.Fprint(buf, "\n", gostr) - - vars := cd.toTemplateObj() - err = tmpls.Lookup("addenda.tmpl").Execute(buf, vars) - if err != nil { - panic(err) - } - - fullp := filepath.Join(path, fmt.Sprintf("%s_gen.go", lin.Name())) - byt, err := postprocessGoFile(genGoFile{ - path: fullp, - walker: PrefixDropper(strings.Title(lin.Name())), - in: buf.Bytes(), - }) - if err != nil { - return nil, err - } - - wd := NewWriteDiffer() - wd[fullp] = byt - - return wd, nil -} - -type tplVars struct { - Name string - LineagePath, PkgPath string - TitleName string - LatestSeqv, LatestSchv uint - IsComposed bool -} - -func (cd *CoremodelDeclaration) GenerateTypescriptCoremodel() (*tsast.File, error) { - schv := cd.Lineage.Latest().Underlying() - - tf, err := cuetsy.GenerateAST(schv, cuetsy.Config{ - Export: true, - }) - if err != nil { - return nil, fmt.Errorf("cuetsy tf gen failed: %w", err) - } - - top, err := cuetsy.GenerateSingleAST(strings.Title(cd.Lineage.Name()), schv, cuetsy.TypeInterface) - if err != nil { - return nil, fmt.Errorf("cuetsy top gen failed: %s", cerrors.Details(err, nil)) - } - - buf := new(bytes.Buffer) - if err := tmpls.Lookup("autogen_header.tmpl").Execute(buf, tvars_autogen_header{ - LineagePath: cd.RelativePath, - GeneratorPath: "pkg/framework/coremodel/gen.go", // FIXME hardcoding is not OK - }); err != nil { - return nil, fmt.Errorf("error executing header template: %w", err) - } - tf.Doc = &tsast.Comment{ - Text: buf.String(), - } - - // TODO until cuetsy can toposort its outputs, put the top/parent type at the bottom of the file. - tf.Nodes = append(tf.Nodes, top.T) - if top.D != nil { - tf.Nodes = append(tf.Nodes, top.D) - } - return tf, nil -} - -var tmplTypedef = `{{range .Types}} -{{ with .Schema.Description }}{{ . }}{{ else }}// {{.TypeName}} is the Go representation of a {{.JsonName}}.{{ end }} -// -// THIS TYPE IS INTENDED FOR INTERNAL USE BY THE GRAFANA BACKEND, AND IS SUBJECT TO BREAKING CHANGES. -// Equivalent Go types at stable import paths are provided in https://github.com/grafana/grok. -type {{.TypeName}} {{if and (opts.AliasTypes) (.CanAlias)}}={{end}} {{.Schema.TypeDecl}} -{{end}} -` diff --git a/pkg/codegen/diffwrite.go b/pkg/codegen/diffwrite.go deleted file mode 100644 index fb1cece0fcd..00000000000 --- a/pkg/codegen/diffwrite.go +++ /dev/null @@ -1,134 +0,0 @@ -package codegen - -import ( - "context" - "errors" - "fmt" - "io" - "os" - "path/filepath" - "sort" - - "github.com/google/go-cmp/cmp" - "github.com/hashicorp/go-multierror" - "golang.org/x/sync/errgroup" -) - -// WriteDiffer is a pseudo-filesystem that supports batch-writing its contents -// to the real filesystem, or batch-comparing its contents to the real -// filesystem. Its intended use is for idiomatic `go generate`-style code -// generators, where it is expected that the results of codegen are committed to -// version control. -// -// In such cases, the normal behavior of a generator is to write files to disk, -// but in CI, that behavior should change to verify that what is already on disk -// is identical to the results of code generation. This allows CI to ensure that -// the results of code generation are always up to date. WriteDiffer supports -// these related behaviors through its Write() and Verify() methods, respectively. -// -// Note that the statelessness of WriteDiffer means that, if a particular input -// to the code generator goes away, it will not notice generated files left -// behind if their inputs are removed. -// TODO introduce a search/match system -type WriteDiffer map[string][]byte - -func NewWriteDiffer() WriteDiffer { - return WriteDiffer(make(map[string][]byte)) -} - -type writeSlice []struct { - path string - contents []byte -} - -// Verify checks the contents of each file against the filesystem. It emits an error -// if any of its contained files differ. -func (wd WriteDiffer) Verify() error { - var result error - - for _, item := range wd.toSlice() { - if _, err := os.Stat(item.path); err != nil { - if errors.Is(err, os.ErrNotExist) { - result = multierror.Append(result, fmt.Errorf("%s: generated file should exist, but does not", item.path)) - } else { - result = multierror.Append(result, fmt.Errorf("%s: could not stat generated file: %w", item.path, err)) - } - continue - } - - f, err := os.Open(filepath.Clean(item.path)) - if err != nil { - result = multierror.Append(result, fmt.Errorf("%s: %w", item.path, err)) - continue - } - - ob, err := io.ReadAll(f) - if err != nil { - result = multierror.Append(result, fmt.Errorf("%s: %w", item.path, err)) - continue - } - dstr := cmp.Diff(string(ob), string(item.contents)) - if dstr != "" { - result = multierror.Append(result, fmt.Errorf("%s would have changed:\n\n%s", item.path, dstr)) - } - } - - return result -} - -// Write writes all of the files to their indicated paths. -func (wd WriteDiffer) Write() error { - g, _ := errgroup.WithContext(context.TODO()) - g.SetLimit(12) - - for _, item := range wd.toSlice() { - it := item - g.Go(func() error { - err := os.MkdirAll(filepath.Dir(it.path), os.ModePerm) - if err != nil { - return fmt.Errorf("%s: failed to ensure parent directory exists: %w", it.path, err) - } - - if err := os.WriteFile(it.path, it.contents, 0644); err != nil { - return fmt.Errorf("%s: error while writing file: %w", it.path, err) - } - return nil - }) - } - - return g.Wait() -} - -func (wd WriteDiffer) toSlice() writeSlice { - sl := make(writeSlice, 0, len(wd)) - type ws struct { - path string - contents []byte - } - - for k, v := range wd { - sl = append(sl, ws{ - path: k, - contents: v, - }) - } - - sort.Slice(sl, func(i, j int) bool { - return sl[i].path < sl[j].path - }) - - return sl -} - -// Merge combines all the entries from the provided WriteDiffer into the callee -// WriteDiffer. Duplicate paths result in an error. -func (wd WriteDiffer) Merge(wd2 WriteDiffer) error { - for k, v := range wd2 { - if _, has := wd[k]; has { - return fmt.Errorf("path %s already exists in write differ", k) - } - wd[k] = v - } - - return nil -} diff --git a/pkg/codegen/tmpl.go b/pkg/codegen/tmpl.go index e5071707c44..beaca5c2318 100644 --- a/pkg/codegen/tmpl.go +++ b/pkg/codegen/tmpl.go @@ -1,7 +1,6 @@ package codegen import ( - "bytes" "embed" "strings" "text/template" @@ -28,12 +27,6 @@ var tmplFS embed.FS // The following group of types, beginning with tvars_*, all contain the set // of variables expected by the corresponding named template file under tmpl/ type ( - tvars_autogen_header struct { - GeneratorPath string - LineagePath string - LineageCUEPath string - GenLicense bool - } tvars_gen_header struct { MainGenerator string Using []codejen.NamedJenny @@ -45,23 +38,9 @@ type ( KindPackagePrefix string Kinds []kindsys.Core } - tvars_coremodel_imports struct { - PackageName string - } tvars_resource struct { PackageName string KindName string SubresourceNames []string } ) - -type HeaderVars = tvars_autogen_header - -// GenGrafanaHeader creates standard header elements for generated Grafana files. -func GenGrafanaHeader(vars HeaderVars) string { - buf := new(bytes.Buffer) - if err := tmpls.Lookup("autogen_header.tmpl").Execute(buf, vars); err != nil { - panic(err) - } - return buf.String() -} From 0d3a78d86dd84ed95024da0a4ada6bc2b0f7af6a Mon Sep 17 00:00:00 2001 From: Andreas Christou Date: Wed, 10 May 2023 02:35:56 +0100 Subject: [PATCH 089/179] AzureMonitor: Bump credential wait time (#68120) Bump credential wait time as 15s seems too short --- e2e/cloud-plugins-suite/azure-monitor.spec.ts | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/e2e/cloud-plugins-suite/azure-monitor.spec.ts b/e2e/cloud-plugins-suite/azure-monitor.spec.ts index 5028371a299..98a2e0de423 100644 --- a/e2e/cloud-plugins-suite/azure-monitor.spec.ts +++ b/e2e/cloud-plugins-suite/azure-monitor.spec.ts @@ -1,3 +1,4 @@ +import { Interception } from 'cypress/types/net-stubbing'; import { load } from 'js-yaml'; import { v4 as uuidv4 } from 'uuid'; @@ -22,6 +23,20 @@ type AzureMonitorProvision = { datasources: AzureMonitorConfig[] }; const dataSourceName = `Azure Monitor E2E Tests - ${uuidv4()}`; +const maxRetryCount = 3; + +Cypress.Commands.add('checkHealthRetryable', function (fn: Function, retryCount: number) { + cy.then(() => { + const result = fn(++retryCount); + result.then((res: Interception) => { + if (retryCount < maxRetryCount && res.response.statusCode !== 200) { + cy.wait(20000); + cy.checkHealthRetryable(fn, retryCount); + } + }); + }); +}); + function provisionAzureMonitorDatasources(datasources: AzureMonitorProvision[]) { const datasource = datasources[0].datasources[0]; @@ -43,8 +58,11 @@ function provisionAzureMonitorDatasources(datasources: AzureMonitorProvision[]) .type(datasource.secureJsonData.clientSecret, { log: false }); e2eSelectors.configEditor.loadSubscriptions.button().click().wait('@subscriptions').wait(500); e2eSelectors.configEditor.defaultSubscription.input().find('input').type('datasources{enter}'); - // Wait for 15s so that credentials are ready. 5s has been tested locally before and seemed insufficient. - e2e().wait(15000); + + // We can do this because awaitHealth is set to true so @health is defined + cy.checkHealthRetryable(() => { + return e2e.pages.DataSource.saveAndTest().click().wait('@health'); + }, 0); }, expectedAlertMessage: 'Successfully connected to all Azure Monitor endpoints', // Reduce the timeout from 30s to error faster when an invalid alert message is presented From 1400a0a3f7a5b5b3c5b7258a3b5bb037b654f599 Mon Sep 17 00:00:00 2001 From: Leon Sorokin Date: Tue, 9 May 2023 23:16:35 -0500 Subject: [PATCH 090/179] Heatmap: Fix color rendering for value ranges < 1 (#68156) --- public/app/plugins/panel/heatmap/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app/plugins/panel/heatmap/utils.ts b/public/app/plugins/panel/heatmap/utils.ts index da83e954125..5cf14c381ca 100644 --- a/public/app/plugins/panel/heatmap/utils.ts +++ b/public/app/plugins/panel/heatmap/utils.ts @@ -967,7 +967,7 @@ export const boundedMinMax = ( }; export const valuesToFills = (values: number[], palette: string[], minValue: number, maxValue: number) => { - let range = Math.max(maxValue - minValue, 1); + let range = maxValue - minValue || 1; let paletteSize = palette.length; From 903cf3e30d3ce8daf6c624900a3c7c787ffcfebd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Farkas?= Date: Wed, 10 May 2023 08:50:59 +0200 Subject: [PATCH 091/179] elastic: refactor: simplify query-type handling (#67921) * elastic: refactor: simplify query-type handling * improved comment Co-authored-by: Ivana Huckova <30407135+ivanahuckova@users.noreply.github.com> --------- Co-authored-by: Ivana Huckova <30407135+ivanahuckova@users.noreply.github.com> --- .../state/reducer.test.ts | 4 ++-- .../BucketAggregationsEditor/state/reducer.ts | 2 +- .../MetricAggregationsEditor/MetricEditor.tsx | 2 +- .../MetricAggregationsEditor/index.tsx | 2 +- .../state/reducer.test.ts | 2 +- .../MetricAggregationsEditor/state/reducer.ts | 6 +++-- .../MetricAggregationsEditor/utils.ts | 22 ++++++++++++++++--- .../QueryEditor/QueryTypeSelector.tsx | 18 +++------------ .../components/QueryEditor/index.tsx | 2 +- .../plugins/datasource/elasticsearch/types.ts | 4 +++- 10 files changed, 36 insertions(+), 28 deletions(-) diff --git a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/state/reducer.test.ts b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/state/reducer.test.ts index 95052d9d229..b39166c1093 100644 --- a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/state/reducer.test.ts +++ b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/state/reducer.test.ts @@ -114,10 +114,10 @@ describe('Bucket Aggregations Reducer', () => { reducerTester() .givenReducer(createReducer('@timestamp'), initialState) - // If the new metric aggregation is `isSingleMetric` we should remove all bucket aggregations. + // If the new metric aggregation is non-metric, we should remove all bucket aggregations. .whenActionIsDispatched(changeMetricType({ id: 'Some id', type: 'raw_data' })) .thenStatePredicateShouldEqual((newState) => newState?.length === 0) - // Switching back to another aggregation that is NOT `isSingleMetric` should bring back a bucket aggregation + // Switching back to another aggregation that is metric should bring back a bucket aggregation .whenActionIsDispatched(changeMetricType({ id: 'Some id', type: 'max' })) .thenStatePredicateShouldEqual((newState) => newState?.length === 1) // When none of the above is true state shouldn't change. diff --git a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/state/reducer.ts b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/state/reducer.ts index 6bfc9a6ee39..d289f7dc32f 100644 --- a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/state/reducer.ts +++ b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/state/reducer.ts @@ -75,7 +75,7 @@ export const createReducer = if (changeMetricType.match(action)) { // If we are switching to a metric which requires the absence of bucket aggregations // we remove all of them. - if (metricAggregationConfig[action.payload.type].isSingleMetric) { + if (metricAggregationConfig[action.payload.type].impliedQueryType !== 'metrics') { return []; } else if (state!.length === 0) { // Else, if there are no bucket aggregations we restore a default one. diff --git a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/MetricEditor.tsx b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/MetricEditor.tsx index a476ff13069..a520ae0fbbe 100644 --- a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/MetricEditor.tsx +++ b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/MetricEditor.tsx @@ -48,7 +48,7 @@ const getTypeOptions = ( return ( Object.entries(metricAggregationConfig) - .filter(([_, config]) => !config.isSingleMetric) + .filter(([_, config]) => config.impliedQueryType === 'metrics') // Only showing metrics type supported by the version of ES. // if we cannot determine the version, we assume it is suitable. .filter(([_, { versionRange = '*' }]) => (esVersion != null ? satisfies(esVersion, versionRange) : true)) diff --git a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/index.tsx b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/index.tsx index e104465fb31..7051d3a3f05 100644 --- a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/index.tsx +++ b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/index.tsx @@ -48,7 +48,7 @@ export const MetricAggregationsEditor = ({ nextId }: Props) => { > - {!metricAggregationConfig[metric.type].isSingleMetric && index === 0 && ( + {metricAggregationConfig[metric.type].impliedQueryType === 'metrics' && index === 0 && ( dispatch(addMetric(nextId))} label="add" /> )} diff --git a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/state/reducer.test.ts b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/state/reducer.test.ts index d91c28ca65c..246642b7b5e 100644 --- a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/state/reducer.test.ts +++ b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/state/reducer.test.ts @@ -84,7 +84,7 @@ describe('Metric Aggregations Reducer', () => { .thenStateShouldEqual([firstAggregation, { ...secondAggregation, type: expectedSecondAggregation.type }]); }); - it('Should remove all other aggregations when the newly selected one is `isSingleMetric`', () => { + it('Should remove all other aggregations when the newly selected one is not metric', () => { const firstAggregation: MetricAggregation = { id: '1', type: 'count', diff --git a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/state/reducer.ts b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/state/reducer.ts index f42baf0998c..e3da5ccd2e6 100644 --- a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/state/reducer.ts +++ b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/state/reducer.ts @@ -36,9 +36,11 @@ export const reducer = (state: ElasticsearchQuery['metrics'], action: Action): E if (changeMetricType.match(action)) { return state! .filter((metric) => - // When the new metric type is `isSingleMetric` we remove all other metrics from the query + // When the new query type is not `metrics` we remove all other metrics from the query // leaving only the current one. - !!metricAggregationConfig[action.payload.type].isSingleMetric ? metric.id === action.payload.id : true + metricAggregationConfig[action.payload.type].impliedQueryType === 'metrics' + ? true + : metric.id === action.payload.id ) .map((metric) => { if (metric.id !== action.payload.id) { diff --git a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/utils.ts b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/utils.ts index b940e0cfb6e..ad27d6aebea 100644 --- a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/utils.ts +++ b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/utils.ts @@ -9,6 +9,7 @@ import { isMetricAggregationWithField, isPipelineAggregationWithMultipleBucketPa export const metricAggregationConfig: MetricsConfiguration = { count: { label: 'Count', + impliedQueryType: 'metrics', requiresField: false, isPipelineAgg: false, supportsMissing: false, @@ -20,6 +21,7 @@ export const metricAggregationConfig: MetricsConfiguration = { }, avg: { label: 'Average', + impliedQueryType: 'metrics', requiresField: true, supportsInlineScript: true, supportsMissing: true, @@ -31,6 +33,7 @@ export const metricAggregationConfig: MetricsConfiguration = { }, sum: { label: 'Sum', + impliedQueryType: 'metrics', requiresField: true, supportsInlineScript: true, supportsMissing: true, @@ -42,6 +45,7 @@ export const metricAggregationConfig: MetricsConfiguration = { }, max: { label: 'Max', + impliedQueryType: 'metrics', requiresField: true, supportsInlineScript: true, supportsMissing: true, @@ -53,6 +57,7 @@ export const metricAggregationConfig: MetricsConfiguration = { }, min: { label: 'Min', + impliedQueryType: 'metrics', requiresField: true, supportsInlineScript: true, supportsMissing: true, @@ -64,6 +69,7 @@ export const metricAggregationConfig: MetricsConfiguration = { }, extended_stats: { label: 'Extended Stats', + impliedQueryType: 'metrics', requiresField: true, supportsMissing: true, supportsInlineScript: true, @@ -80,6 +86,7 @@ export const metricAggregationConfig: MetricsConfiguration = { }, percentiles: { label: 'Percentiles', + impliedQueryType: 'metrics', requiresField: true, supportsMissing: true, supportsInlineScript: true, @@ -95,6 +102,7 @@ export const metricAggregationConfig: MetricsConfiguration = { }, cardinality: { label: 'Unique Count', + impliedQueryType: 'metrics', requiresField: true, supportsMissing: true, isPipelineAgg: false, @@ -108,6 +116,7 @@ export const metricAggregationConfig: MetricsConfiguration = { // deprecated in 6.4.0, removed in 8.0.0, // recommended replacement is moving_fn label: 'Moving Average', + impliedQueryType: 'metrics', requiresField: true, isPipelineAgg: true, versionRange: '<8.0.0', @@ -126,6 +135,7 @@ export const metricAggregationConfig: MetricsConfiguration = { moving_fn: { // TODO: Check this label: 'Moving Function', + impliedQueryType: 'metrics', requiresField: true, isPipelineAgg: true, supportsMultipleBucketPaths: false, @@ -137,6 +147,7 @@ export const metricAggregationConfig: MetricsConfiguration = { }, derivative: { label: 'Derivative', + impliedQueryType: 'metrics', requiresField: true, isPipelineAgg: true, supportsMissing: false, @@ -148,6 +159,7 @@ export const metricAggregationConfig: MetricsConfiguration = { }, serial_diff: { label: 'Serial Difference', + impliedQueryType: 'metrics', requiresField: true, isPipelineAgg: true, supportsMissing: false, @@ -163,6 +175,7 @@ export const metricAggregationConfig: MetricsConfiguration = { }, cumulative_sum: { label: 'Cumulative Sum', + impliedQueryType: 'metrics', requiresField: true, isPipelineAgg: true, supportsMissing: false, @@ -174,6 +187,7 @@ export const metricAggregationConfig: MetricsConfiguration = { }, bucket_script: { label: 'Bucket Script', + impliedQueryType: 'metrics', requiresField: false, isPipelineAgg: true, supportsMissing: false, @@ -188,7 +202,7 @@ export const metricAggregationConfig: MetricsConfiguration = { raw_document: { label: 'Raw Document (deprecated)', requiresField: false, - isSingleMetric: true, + impliedQueryType: 'raw_document', isPipelineAgg: false, supportsMissing: false, supportsMultipleBucketPaths: false, @@ -204,7 +218,7 @@ export const metricAggregationConfig: MetricsConfiguration = { raw_data: { label: 'Raw Data', requiresField: false, - isSingleMetric: true, + impliedQueryType: 'raw_data', isPipelineAgg: false, supportsMissing: false, supportsMultipleBucketPaths: false, @@ -224,7 +238,7 @@ export const metricAggregationConfig: MetricsConfiguration = { supportsMissing: false, supportsMultipleBucketPaths: false, hasSettings: true, - isSingleMetric: true, + impliedQueryType: 'logs', supportsInlineScript: false, hasMeta: false, defaults: { @@ -235,6 +249,7 @@ export const metricAggregationConfig: MetricsConfiguration = { }, top_metrics: { label: 'Top Metrics', + impliedQueryType: 'metrics', requiresField: false, isPipelineAgg: false, supportsMissing: false, @@ -250,6 +265,7 @@ export const metricAggregationConfig: MetricsConfiguration = { }, rate: { label: 'Rate', + impliedQueryType: 'metrics', requiresField: true, isPipelineAgg: false, supportsMissing: false, diff --git a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/QueryTypeSelector.tsx b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/QueryTypeSelector.tsx index 9b8ade09bfa..7dfa3fd2ec6 100644 --- a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/QueryTypeSelector.tsx +++ b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/QueryTypeSelector.tsx @@ -4,12 +4,11 @@ import { SelectableValue } from '@grafana/data'; import { RadioButtonGroup } from '@grafana/ui'; import { useDispatch } from '../../hooks/useStatelessReducer'; -import { MetricAggregation } from '../../types'; +import { MetricAggregation, QueryType } from '../../types'; import { useQuery } from './ElasticsearchQueryContext'; import { changeMetricType } from './MetricAggregationsEditor/state/actions'; - -type QueryType = 'metrics' | 'logs' | 'raw_data' | 'raw_document'; +import { metricAggregationConfig } from './MetricAggregationsEditor/utils'; const OPTIONS: Array> = [ { value: 'metrics', label: 'Metrics' }, @@ -18,17 +17,6 @@ const OPTIONS: Array> = [ { value: 'raw_document', label: 'Raw Document' }, ]; -function metricTypeToQueryType(type: MetricAggregation['type']): QueryType { - switch (type) { - case 'logs': - case 'raw_data': - case 'raw_document': - return type; - default: - return 'metrics'; - } -} - function queryTypeToMetricType(type: QueryType): MetricAggregation['type'] { switch (type) { case 'logs': @@ -54,7 +42,7 @@ export const QueryTypeSelector = () => { return null; } - const queryType = metricTypeToQueryType(firstMetric.type); + const queryType = metricAggregationConfig[firstMetric.type].impliedQueryType; const onChange = (newQueryType: QueryType) => { dispatch(changeMetricType({ id: firstMetric.id, type: queryTypeToMetricType(newQueryType) })); diff --git a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/index.tsx b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/index.tsx index 2e60a71865e..d9030d47b29 100644 --- a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/index.tsx +++ b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/index.tsx @@ -104,7 +104,7 @@ const QueryEditorForm = ({ value }: Props) => { const isTimeSeriesQuery = value?.bucketAggs?.slice(-1)[0]?.type === 'date_histogram'; const showBucketAggregationsEditor = value.metrics?.every( - (metric) => !metricAggregationConfig[metric.type].isSingleMetric + (metric) => metricAggregationConfig[metric.type].impliedQueryType === 'metrics' ); return ( diff --git a/public/app/plugins/datasource/elasticsearch/types.ts b/public/app/plugins/datasource/elasticsearch/types.ts index ecb44373041..c7f7e68df62 100644 --- a/public/app/plugins/datasource/elasticsearch/types.ts +++ b/public/app/plugins/datasource/elasticsearch/types.ts @@ -65,6 +65,8 @@ export interface ElasticsearchOptions extends DataSourceJsonData { index?: string; } +export type QueryType = 'metrics' | 'logs' | 'raw_data' | 'raw_document'; + interface MetricConfiguration { label: string; requiresField: boolean; @@ -77,7 +79,7 @@ interface MetricConfiguration { */ versionRange?: string; supportsMultipleBucketPaths: boolean; - isSingleMetric?: boolean; + impliedQueryType: QueryType; hasSettings: boolean; hasMeta: boolean; defaults: Omit, 'id' | 'type'>; From d88da205f64816506a23ccdea877ff7d8c4922f5 Mon Sep 17 00:00:00 2001 From: Ivana Huckova <30407135+ivanahuckova@users.noreply.github.com> Date: Wed, 10 May 2023 09:30:57 +0200 Subject: [PATCH 092/179] Elasticsearch: Migrate annotation calls to be run trough resources (#68075) * Update * Remove comment * Add annotation to test dashboard * Update devenv dashboard to correctly use textField --- .betterer.results | 22 +- .../elasticsearch_complex.json | 17 ++ .../elasticsearch/LegacyQueryRunner.ts | 159 +------------- .../elasticsearch/datasource.test.ts | 129 ++++++++++++ .../datasource/elasticsearch/datasource.ts | 194 +++++++++++++++++- 5 files changed, 348 insertions(+), 173 deletions(-) diff --git a/.betterer.results b/.betterer.results index 4f0262e4ecb..ef1bca72e3e 100644 --- a/.betterer.results +++ b/.betterer.results @@ -3994,17 +3994,8 @@ exports[`better eslint`] = { "public/app/plugins/datasource/elasticsearch/LegacyQueryRunner.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "1"], - [0, 0, 0, "Unexpected any. Specify a different type.", "2"], - [0, 0, 0, "Unexpected any. Specify a different type.", "3"], - [0, 0, 0, "Unexpected any. Specify a different type.", "4"], - [0, 0, 0, "Unexpected any. Specify a different type.", "5"], - [0, 0, 0, "Unexpected any. Specify a different type.", "6"], - [0, 0, 0, "Unexpected any. Specify a different type.", "7"], - [0, 0, 0, "Unexpected any. Specify a different type.", "8"], - [0, 0, 0, "Unexpected any. Specify a different type.", "9"], - [0, 0, 0, "Unexpected any. Specify a different type.", "10"], - [0, 0, 0, "Do not use any type assertions.", "11"], - [0, 0, 0, "Unexpected any. Specify a different type.", "12"] + [0, 0, 0, "Do not use any type assertions.", "2"], + [0, 0, 0, "Unexpected any. Specify a different type.", "3"] ], "public/app/plugins/datasource/elasticsearch/QueryBuilder.test.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] @@ -4083,7 +4074,14 @@ exports[`better eslint`] = { [0, 0, 0, "Unexpected any. Specify a different type.", "12"], [0, 0, 0, "Unexpected any. Specify a different type.", "13"], [0, 0, 0, "Unexpected any. Specify a different type.", "14"], - [0, 0, 0, "Unexpected any. Specify a different type.", "15"] + [0, 0, 0, "Unexpected any. Specify a different type.", "15"], + [0, 0, 0, "Unexpected any. Specify a different type.", "16"], + [0, 0, 0, "Unexpected any. Specify a different type.", "17"], + [0, 0, 0, "Unexpected any. Specify a different type.", "18"], + [0, 0, 0, "Unexpected any. Specify a different type.", "19"], + [0, 0, 0, "Unexpected any. Specify a different type.", "20"], + [0, 0, 0, "Unexpected any. Specify a different type.", "21"], + [0, 0, 0, "Unexpected any. Specify a different type.", "22"] ], "public/app/plugins/datasource/elasticsearch/hooks/useStatelessReducer.ts:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] diff --git a/devenv/dev-dashboards/datasource-elasticsearch/elasticsearch_complex.json b/devenv/dev-dashboards/datasource-elasticsearch/elasticsearch_complex.json index dc4e4b7810c..34a3db9116b 100644 --- a/devenv/dev-dashboards/datasource-elasticsearch/elasticsearch_complex.json +++ b/devenv/dev-dashboards/datasource-elasticsearch/elasticsearch_complex.json @@ -12,6 +12,23 @@ "iconColor": "rgba(0, 211, 255, 1)", "name": "Annotations & Alerts", "type": "dashboard" + }, + { + "datasource": { + "type": "elasticsearch", + "uid": "gdev-elasticsearch" + }, + "enable": true, + "iconColor": "red", + "name": "errors", + "tagsField": "metric", + "target": { + "lines": 10, + "query": "level:error", + "refId": "Anno", + "scenarioId": "annotations" + }, + "textField": "line" } ] }, diff --git a/public/app/plugins/datasource/elasticsearch/LegacyQueryRunner.ts b/public/app/plugins/datasource/elasticsearch/LegacyQueryRunner.ts index 6665b6028fd..dfc24921b43 100644 --- a/public/app/plugins/datasource/elasticsearch/LegacyQueryRunner.ts +++ b/public/app/plugins/datasource/elasticsearch/LegacyQueryRunner.ts @@ -1,4 +1,4 @@ -import { isNumber, isString, first as _first, cloneDeep } from 'lodash'; +import { first as _first, cloneDeep } from 'lodash'; import { lastValueFrom, Observable, of, throwError } from 'rxjs'; import { catchError, map, tap } from 'rxjs/operators'; @@ -11,14 +11,13 @@ import { LogRowContextOptions, LogRowContextQueryDirection, LogRowModel, - toUtc, } from '@grafana/data'; import { BackendSrvRequest, getBackendSrv, TemplateSrv } from '@grafana/runtime'; import { ElasticResponse } from './ElasticResponse'; import { ElasticDatasource, enhanceDataFrame } from './datasource'; import { defaultBucketAgg, hasMetricOfType } from './queryDef'; -import { trackAnnotationQuery, trackQuery } from './tracking'; +import { trackQuery } from './tracking'; import { ElasticsearchQuery, Logs } from './types'; export class LegacyQueryRunner { @@ -86,160 +85,6 @@ export class LegacyQueryRunner { ); } - annotationQuery(options: any) { - const annotation = options.annotation; - const timeField = annotation.timeField || '@timestamp'; - const timeEndField = annotation.timeEndField || null; - - // the `target.query` is the "new" location for the query. - // normally we would write this code as - // try-the-new-place-then-try-the-old-place, - // but we had the bug at - // https://github.com/grafana/grafana/issues/61107 - // that may have stored annotations where - // both the old and the new place are set, - // and in that scenario the old place needs - // to have priority. - const queryString = annotation.query ?? annotation.target?.query; - const tagsField = annotation.tagsField || 'tags'; - const textField = annotation.textField || null; - - const dateRanges = []; - const rangeStart: any = {}; - rangeStart[timeField] = { - from: options.range.from.valueOf(), - to: options.range.to.valueOf(), - format: 'epoch_millis', - }; - dateRanges.push({ range: rangeStart }); - - if (timeEndField) { - const rangeEnd: any = {}; - rangeEnd[timeEndField] = { - from: options.range.from.valueOf(), - to: options.range.to.valueOf(), - format: 'epoch_millis', - }; - dateRanges.push({ range: rangeEnd }); - } - - const queryInterpolated = this.datasource.interpolateLuceneQuery(queryString); - const query: any = { - bool: { - filter: [ - { - bool: { - should: dateRanges, - minimum_should_match: 1, - }, - }, - ], - }, - }; - - if (queryInterpolated) { - query.bool.filter.push({ - query_string: { - query: queryInterpolated, - }, - }); - } - const data: any = { - query, - size: 10000, - }; - - const header: any = { - search_type: 'query_then_fetch', - ignore_unavailable: true, - }; - - // @deprecated - // Field annotation.index is deprecated and will be removed in the future - if (annotation.index) { - header.index = annotation.index; - } else { - header.index = this.datasource.indexPattern.getIndexList(options.range.from, options.range.to); - } - - const payload = JSON.stringify(header) + '\n' + JSON.stringify(data) + '\n'; - - trackAnnotationQuery(annotation); - return lastValueFrom( - this.request('POST', '_msearch', payload).pipe( - map((res) => { - const list = []; - const hits = res.responses[0].hits.hits; - - const getFieldFromSource = (source: any, fieldName: any) => { - if (!fieldName) { - return; - } - - const fieldNames = fieldName.split('.'); - let fieldValue = source; - - for (let i = 0; i < fieldNames.length; i++) { - fieldValue = fieldValue[fieldNames[i]]; - if (!fieldValue) { - console.log('could not find field in annotation: ', fieldName); - return ''; - } - } - - return fieldValue; - }; - - for (let i = 0; i < hits.length; i++) { - const source = hits[i]._source; - let time = getFieldFromSource(source, timeField); - if (typeof hits[i].fields !== 'undefined') { - const fields = hits[i].fields; - if (isString(fields[timeField]) || isNumber(fields[timeField])) { - time = fields[timeField]; - } - } - - const event: { - annotation: any; - time: number; - timeEnd?: number; - text: string; - tags: string | string[]; - } = { - annotation: annotation, - time: toUtc(time).valueOf(), - text: getFieldFromSource(source, textField), - tags: getFieldFromSource(source, tagsField), - }; - - if (timeEndField) { - const timeEnd = getFieldFromSource(source, timeEndField); - if (timeEnd) { - event.timeEnd = toUtc(timeEnd).valueOf(); - } - } - - // legacy support for title field - if (annotation.titleField) { - const title = getFieldFromSource(source, annotation.titleField); - if (title) { - event.text = title + '\n' + event.text; - } - } - - if (typeof event.tags === 'string') { - event.tags = event.tags.split(','); - } - - list.push(event); - } - return list; - }) - ) - ); - } - async logContextQuery(row: LogRowModel, options?: LogRowContextOptions): Promise<{ data: DataFrame[] }> { const sortField = row.dataFrame.fields.find((f) => f.name === 'sort'); const searchAfter = sortField?.values[row.rowIndex] || [row.timeEpochMs]; diff --git a/public/app/plugins/datasource/elasticsearch/datasource.test.ts b/public/app/plugins/datasource/elasticsearch/datasource.test.ts index 479e9e20852..8df01e4fa8a 100644 --- a/public/app/plugins/datasource/elasticsearch/datasource.test.ts +++ b/public/app/plugins/datasource/elasticsearch/datasource.test.ts @@ -1304,6 +1304,135 @@ describe('ElasticDatasource using backend', () => { console.error = originalConsoleError; config.featureToggles.enableElasticsearchBackendQuerying = false; }); + describe('annotationQuery', () => { + describe('results processing', () => { + it('should return simple annotations using defaults', async () => { + const { ds, timeSrv } = getTestContext(); + ds.postResourceRequest = jest.fn().mockResolvedValue({ + responses: [ + { + hits: { + hits: [ + { _source: { '@timestamp': 1, '@test_tags': 'foo', text: 'abc' } }, + { _source: { '@timestamp': 3, '@test_tags': 'bar', text: 'def' } }, + ], + }, + }, + ], + }); + + const annotations = await ds.annotationQuery({ + annotation: {}, + range: timeSrv.timeRange(), + }); + + expect(annotations).toHaveLength(2); + expect(annotations[0].time).toBe(1); + expect(annotations[1].time).toBe(3); + }); + + it('should return annotation events using options', async () => { + const { ds, timeSrv } = getTestContext(); + ds.postResourceRequest = jest.fn().mockResolvedValue({ + responses: [ + { + hits: { + hits: [ + { _source: { '@test_time': 1, '@test_tags': 'foo', text: 'abc' } }, + { _source: { '@test_time': 3, '@test_tags': 'bar', text: 'def' } }, + ], + }, + }, + ], + }); + + const annotations = await ds.annotationQuery({ + annotation: { + timeField: '@test_time', + name: 'foo', + query: 'abc', + tagsField: '@test_tags', + textField: 'text', + }, + range: timeSrv.timeRange(), + }); + expect(annotations).toHaveLength(2); + expect(annotations[0].time).toBe(1); + expect(annotations[0].tags?.[0]).toBe('foo'); + expect(annotations[0].text).toBe('abc'); + + expect(annotations[1].time).toBe(3); + expect(annotations[1].tags?.[0]).toBe('bar'); + expect(annotations[1].text).toBe('def'); + }); + }); + + describe('request processing', () => { + it('should process annotation request using options', async () => { + const { ds } = getTestContext(); + const postResourceRequestMock = jest.spyOn(ds, 'postResourceRequest').mockResolvedValue({ + responses: [ + { + hits: { + hits: [ + { _source: { '@test_time': 1, '@test_tags': 'foo', text: 'abc' } }, + { _source: { '@test_time': 3, '@test_tags': 'bar', text: 'def' } }, + ], + }, + }, + ], + }); + + await ds.annotationQuery({ + annotation: { + timeField: '@test_time', + timeEndField: '@time_end_field', + name: 'foo', + query: 'abc', + tagsField: '@test_tags', + textField: 'text', + }, + range: { + from: dateTime(1683291160012), + to: dateTime(1683291460012), + }, + }); + expect(postResourceRequestMock).toHaveBeenCalledWith( + '_msearch', + '{"search_type":"query_then_fetch","ignore_unavailable":true,"index":"[test-]YYYY.MM.DD"}\n{"query":{"bool":{"filter":[{"bool":{"should":[{"range":{"@test_time":{"from":1683291160012,"to":1683291460012,"format":"epoch_millis"}}},{"range":{"@time_end_field":{"from":1683291160012,"to":1683291460012,"format":"epoch_millis"}}}],"minimum_should_match":1}},{"query_string":{"query":"abc"}}]}},"size":10000}\n' + ); + }); + + it('should process annotation request using defaults', async () => { + const { ds } = getTestContext(); + const postResourceRequestMock = jest.spyOn(ds, 'postResourceRequest').mockResolvedValue({ + responses: [ + { + hits: { + hits: [ + { _source: { '@test_time': 1, '@test_tags': 'foo', text: 'abc' } }, + { _source: { '@test_time': 3, '@test_tags': 'bar', text: 'def' } }, + ], + }, + }, + ], + }); + + await ds.annotationQuery({ + annotation: {}, + range: { + from: dateTime(1683291160012), + to: dateTime(1683291460012), + }, + }); + expect(postResourceRequestMock).toHaveBeenCalledWith( + '_msearch', + '{"search_type":"query_then_fetch","ignore_unavailable":true,"index":"[test-]YYYY.MM.DD"}\n{"query":{"bool":{"filter":[{"bool":{"should":[{"range":{"@timestamp":{"from":1683291160012,"to":1683291460012,"format":"epoch_millis"}}}],"minimum_should_match":1}}]}},"size":10000}\n' + ); + }); + }); + }); + describe('getDatabaseVersion', () => { it('should correctly get db version', async () => { const { ds } = getTestContext(); diff --git a/public/app/plugins/datasource/elasticsearch/datasource.ts b/public/app/plugins/datasource/elasticsearch/datasource.ts index 7e2cad0728e..62ac495d825 100644 --- a/public/app/plugins/datasource/elasticsearch/datasource.ts +++ b/public/app/plugins/datasource/elasticsearch/datasource.ts @@ -1,4 +1,4 @@ -import { cloneDeep, find, first as _first, isObject, isString, map as _map } from 'lodash'; +import { cloneDeep, find, first as _first, isNumber, isObject, isString, map as _map } from 'lodash'; import { from, generate, lastValueFrom, Observable, of } from 'rxjs'; import { catchError, first, map, mergeMap, skipWhile, throwIfEmpty, tap } from 'rxjs/operators'; import { SemVer } from 'semver'; @@ -29,6 +29,8 @@ import { LogRowContextQueryDirection, LogRowContextOptions, SupplementaryQueryOptions, + toUtc, + AnnotationEvent, } from '@grafana/data'; import { DataSourceWithBackend, getDataSourceSrv, config, BackendSrvRequest } from '@grafana/runtime'; import { queryLogsVolume } from 'app/core/logsModel'; @@ -49,7 +51,7 @@ import { isPipelineAggregationWithMultipleBucketPaths, } from './components/QueryEditor/MetricAggregationsEditor/aggregations'; import { metricAggregationConfig } from './components/QueryEditor/MetricAggregationsEditor/utils'; -import { trackQuery } from './tracking'; +import { trackAnnotationQuery, trackQuery } from './tracking'; import { Logs, BucketAggregation, @@ -211,8 +213,192 @@ export class ElasticDatasource ); } - annotationQuery(options: any): Promise { - return this.legacyQueryRunner.annotationQuery(options); + annotationQuery(options: any): Promise { + const payload = this.prepareAnnotationRequest(options); + trackAnnotationQuery(options.annotation); + const annotationObservable = config.featureToggles.enableElasticsearchBackendQuerying + ? // TODO: We should migrate this to use query and not resource call + // The plan is to look at this when we start to work on raw query editor for ES + // as we will have to explore how to handle any query + from(this.postResourceRequest('_msearch', payload)) + : this.legacyQueryRunner.request('POST', '_msearch', payload); + + return lastValueFrom( + annotationObservable.pipe( + map((res) => { + const hits = res.responses[0].hits.hits; + return this.processHitsToAnnotationEvents(options.annotation, hits); + }) + ) + ); + } + + private prepareAnnotationRequest(options: { + annotation: { + target: ElasticsearchQuery; + timeField?: string; + timeEndField?: string; + titleField?: string; + query?: string; + index?: string; + }; + range: TimeRange; + }) { + const annotation = options.annotation; + const timeField = annotation.timeField || '@timestamp'; + const timeEndField = annotation.timeEndField || null; + + // the `target.query` is the "new" location for the query. + // normally we would write this code as + // try-the-new-place-then-try-the-old-place, + // but we had the bug at + // https://github.com/grafana/grafana/issues/61107 + // that may have stored annotations where + // both the old and the new place are set, + // and in that scenario the old place needs + // to have priority. + const queryString = annotation.query ?? annotation.target?.query ?? ''; + + const dateRanges = []; + const rangeStart: any = {}; + rangeStart[timeField] = { + from: options.range.from.valueOf(), + to: options.range.to.valueOf(), + format: 'epoch_millis', + }; + dateRanges.push({ range: rangeStart }); + + if (timeEndField) { + const rangeEnd: any = {}; + rangeEnd[timeEndField] = { + from: options.range.from.valueOf(), + to: options.range.to.valueOf(), + format: 'epoch_millis', + }; + dateRanges.push({ range: rangeEnd }); + } + + const queryInterpolated = this.interpolateLuceneQuery(queryString); + const query: any = { + bool: { + filter: [ + { + bool: { + should: dateRanges, + minimum_should_match: 1, + }, + }, + ], + }, + }; + + if (queryInterpolated) { + query.bool.filter.push({ + query_string: { + query: queryInterpolated, + }, + }); + } + const data: any = { + query, + size: 10000, + }; + + const header: any = { + search_type: 'query_then_fetch', + ignore_unavailable: true, + }; + + // @deprecated + // Field annotation.index is deprecated and will be removed in the future + if (annotation.index) { + header.index = annotation.index; + } else { + header.index = this.indexPattern.getIndexList(options.range.from, options.range.to); + } + + const payload = JSON.stringify(header) + '\n' + JSON.stringify(data) + '\n'; + return payload; + } + + private processHitsToAnnotationEvents( + annotation: { + target: ElasticsearchQuery; + timeField?: string; + titleField?: string; + timeEndField?: string; + query?: string; + tagsField?: string; + textField?: string; + index?: string; + }, + hits: Array<{ [key: string]: any }> + ) { + const timeField = annotation.timeField || '@timestamp'; + const timeEndField = annotation.timeEndField || null; + const textField = annotation.textField || 'tags'; + const tagsField = annotation.tagsField || null; + const list: AnnotationEvent[] = []; + + const getFieldFromSource = (source: any, fieldName: any) => { + if (!fieldName) { + return; + } + + const fieldNames = fieldName.split('.'); + let fieldValue = source; + + for (let i = 0; i < fieldNames.length; i++) { + fieldValue = fieldValue[fieldNames[i]]; + if (!fieldValue) { + return ''; + } + } + + return fieldValue; + }; + + for (let i = 0; i < hits.length; i++) { + const source = hits[i]._source; + let time = getFieldFromSource(source, timeField); + if (typeof hits[i].fields !== 'undefined') { + const fields = hits[i].fields; + if (isString(fields[timeField]) || isNumber(fields[timeField])) { + time = fields[timeField]; + } + } + + const event: AnnotationEvent = { + annotation: annotation, + time: toUtc(time).valueOf(), + text: getFieldFromSource(source, textField), + }; + + if (timeEndField) { + const timeEnd = getFieldFromSource(source, timeEndField); + if (timeEnd) { + event.timeEnd = toUtc(timeEnd).valueOf(); + } + } + + // legacy support for title field + if (annotation.titleField) { + const title = getFieldFromSource(source, annotation.titleField); + if (title) { + event.text = title + '\n' + event.text; + } + } + + const tags = getFieldFromSource(source, tagsField); + if (typeof tags === 'string') { + event.tags = tags.split(','); + } else { + event.tags = tags; + } + + list.push(event); + } + return list; } interpolateLuceneQuery(queryString: string, scopedVars?: ScopedVars) { From 356eabc24cbed999830d7a112ef3f111a7dffc26 Mon Sep 17 00:00:00 2001 From: Jack Baldry Date: Wed, 10 May 2023 09:08:17 +0100 Subject: [PATCH 093/179] Use expected types for admonition (#68080) All types are declared lowercased. Signed-off-by: Jack Baldry --- .../dashboards/assess-dashboard-usage/index.md | 2 +- docs/sources/dashboards/dashboard-public/index.md | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/sources/dashboards/assess-dashboard-usage/index.md b/docs/sources/dashboards/assess-dashboard-usage/index.md index af2bca8d257..0190109e194 100644 --- a/docs/sources/dashboards/assess-dashboard-usage/index.md +++ b/docs/sources/dashboards/assess-dashboard-usage/index.md @@ -54,7 +54,7 @@ Dashboard insights show the following information: {{< figure src="/static/img/docs/enterprise/dashboard_insights_stats.png" max-width="400px" class="docs-image--no-shadow" >}}{{< figure src="/static/img/docs/enterprise/dashboard_insights_users.png" max-width="400px" class="docs-image--no-shadow" >}} -{{% admonition type="Note" %}} +{{% admonition type="note" %}} If you've enabled the `publicDashboards` feature toggle, you'll also see a Public dashboards tab in your analytics. diff --git a/docs/sources/dashboards/dashboard-public/index.md b/docs/sources/dashboards/dashboard-public/index.md index 60095c05e30..ce4fc3546fb 100644 --- a/docs/sources/dashboards/dashboard-public/index.md +++ b/docs/sources/dashboards/dashboard-public/index.md @@ -8,13 +8,13 @@ weight: 8 # Public dashboards -{{% admonition type="Note" %}} +{{% admonition type="note" %}} This feature is in [public preview](/docs/release-life-cycle/). {{% /admonition %}} -{{% admonition type="Caution" %}} +{{% admonition type="caution" %}} Making your dashboard public could result in a large number of queries to the data sources used by your dashboard. This can be mitigated by utilizing the enterprise [caching]({{< relref "../../administration/data-source-management/#query-caching" >}}) and/or rate limiting features. @@ -47,7 +47,7 @@ If you are using Docker, use an environment variable to enable public dashboards --env GF_FEATURE_TOGGLES_ENABLE=publicDashboards ``` -{{% admonition type="Note" %}} +{{% admonition type="note" %}} For Grafana Cloud (Pro and Advanced only), contact support to have the feature enabled. @@ -81,7 +81,7 @@ The link no longer works. You must create a new public URL, as in [Make a dashbo ## Email sharing -{{% admonition type="Note" %}} +{{% admonition type="note" %}} Available in [Grafana Enterprise]({{< relref "../../introduction/grafana-enterprise/" >}}) and [Grafana Cloud Pro and Advanced](/docs/grafana-cloud). @@ -91,7 +91,7 @@ Email sharing allows you to share your public dashboard with only specific peopl ### Enable email sharing -{{% admonition type="Note" %}} +{{% admonition type="note" %}} For Grafana Cloud (Pro and Advanced only), contact support to have the feature enabled. @@ -157,7 +157,7 @@ If a Grafana user has read access to the parent dashboard, they can view the pub ## Assess public dashboard usage -> **Note:** Available in [Grafana Enterprise]({{< relref "../../introduction/grafana-enterprise/" >}}) and [Grafana Cloud Pro and Advanced](/docs/grafana-cloud). +> **note:** Available in [Grafana Enterprise]({{< relref "../../introduction/grafana-enterprise/" >}}) and [Grafana Cloud Pro and Advanced](/docs/grafana-cloud). You can check usage analytics about your public dashboard by clicking the insights icon in the dashboard header: @@ -203,7 +203,7 @@ guaranteed because plugin developers can override this functionality. The follow ### Unconfirmed: -{{% admonition type="Note" %}} +{{% admonition type="note" %}} If you've confirmed one of these data sources work with public dashboards, let us know in our [Github](https://github.com/grafana/grafana/discussions/49253) discussion, and we'll mark it as confirmed! From 26d33d8fd47a6a16c0cec82d996bfac5d2dcf126 Mon Sep 17 00:00:00 2001 From: Sladyn Date: Wed, 10 May 2023 01:20:41 -0700 Subject: [PATCH 094/179] CloudWatch: Update metric stat editor to match aws statistics (#66532) --- .../MetricStatEditor.test.tsx | 50 +++++++++++++++++-- .../MetricStatEditor/MetricStatEditor.tsx | 6 ++- .../cloudwatch/standardStatistics.ts | 2 +- .../datasource/cloudwatch/variables.test.ts | 1 + 4 files changed, 53 insertions(+), 6 deletions(-) diff --git a/public/app/plugins/datasource/cloudwatch/components/MetricStatEditor/MetricStatEditor.test.tsx b/public/app/plugins/datasource/cloudwatch/components/MetricStatEditor/MetricStatEditor.test.tsx index bc1126972f3..f405361efb9 100644 --- a/public/app/plugins/datasource/cloudwatch/components/MetricStatEditor/MetricStatEditor.test.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/MetricStatEditor/MetricStatEditor.test.tsx @@ -42,7 +42,6 @@ describe('MetricStatEditor', () => { describe('statistics field', () => { test.each(['Average', 'p23.23', 'p34', '$statistic'])('should accept valid values', async (statistic) => { const onChange = jest.fn(); - render(); const statisticElement = await screen.findByLabelText('Statistic'); @@ -53,9 +52,52 @@ describe('MetricStatEditor', () => { expect(onChange).toHaveBeenCalledWith({ ...props.metricStat, statistic }); }); - test.each(['CustomStat', 'p23,23', '$someUnknownValue'])('should not accept invalid values', async (statistic) => { - const onChange = jest.fn(); + test.each(['CustomStat', 'p23,23', 'tc(80%:)', 'ts', 'wm', 'pr', 'tm', 'tm(10:90)', '$someUnknownValue'])( + 'should not accept invalid values', + async (statistic) => { + const onChange = jest.fn(); + render(); + + const statisticElement = await screen.findByLabelText('Statistic'); + expect(statisticElement).toBeInTheDocument(); + + await userEvent.type(statisticElement, statistic); + fireEvent.keyDown(statisticElement, { keyCode: 13 }); + expect(onChange).not.toHaveBeenCalled(); + } + ); + + test.each([ + 'IQM', + 'tm90', + 'tm23.23', + 'TM(25%:75%)', + 'TM(0.005:0.030)', + 'TM(150:1000)', + 'TM(:0.5)', + 'TM(:95%)', + 'wm98', + 'wm23.23', + 'WM(25%:75%)', + 'WM(0.005:0.030)', + 'WM(150:1000)', + 'WM(:0.5)', + 'PR(10:)', + 'PR(:300)', + 'PR(100:20000)', + 'tc90', + 'tc23.23', + 'TC(0.005:0.030)', + 'TC(:0.5)', + 'TC(25%:75%)', + 'TC(150:1000)', + 'TC(:0.5)', + 'ts90', + 'ts23.23', + 'TS(80%:)', + ])('should accept other valid statistics syntax', async (statistic) => { + const onChange = jest.fn(); render(); const statisticElement = await screen.findByLabelText('Statistic'); @@ -63,7 +105,7 @@ describe('MetricStatEditor', () => { await userEvent.type(statisticElement, statistic); fireEvent.keyDown(statisticElement, { keyCode: 13 }); - expect(onChange).not.toHaveBeenCalled(); + expect(onChange).toHaveBeenCalledWith({ ...props.metricStat, statistic }); }); }); diff --git a/public/app/plugins/datasource/cloudwatch/components/MetricStatEditor/MetricStatEditor.tsx b/public/app/plugins/datasource/cloudwatch/components/MetricStatEditor/MetricStatEditor.tsx index abfe5b6f40c..f6daaafc528 100644 --- a/public/app/plugins/datasource/cloudwatch/components/MetricStatEditor/MetricStatEditor.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/MetricStatEditor/MetricStatEditor.tsx @@ -21,6 +21,10 @@ export type Props = { onChange: (value: MetricStat) => void; }; +const percentileSyntaxRE = /^(p|tm|tc|ts|wm)\d{2}(?:\.\d{1,2})?$/; +const boundariesInnerParenthesesSyntax = `\\d*(\\.\\d+)?%?:\\d*(\\.\\d+)?%?`; +const boundariesSyntaxRE = new RegExp(`^(PR|TM|TC|TS|WM)\\((${boundariesInnerParenthesesSyntax})\\)$`); + export function MetricStatEditor({ refId, metricStat, @@ -116,7 +120,7 @@ export function MetricStatEditor({ if ( !statistic || (!standardStatistics.includes(statistic) && - !/^p\d{2}(?:\.\d{1,2})?$/.test(statistic) && + !(percentileSyntaxRE.test(statistic) || boundariesSyntaxRE.test(statistic)) && !datasource.templateSrv.containsTemplate(statistic)) ) { return; diff --git a/public/app/plugins/datasource/cloudwatch/standardStatistics.ts b/public/app/plugins/datasource/cloudwatch/standardStatistics.ts index 0d03476a07b..43a2e6796a2 100644 --- a/public/app/plugins/datasource/cloudwatch/standardStatistics.ts +++ b/public/app/plugins/datasource/cloudwatch/standardStatistics.ts @@ -1 +1 @@ -export const standardStatistics = ['Average', 'Maximum', 'Minimum', 'Sum', 'SampleCount']; +export const standardStatistics = ['Average', 'Maximum', 'Minimum', 'Sum', 'SampleCount', 'IQM']; diff --git a/public/app/plugins/datasource/cloudwatch/variables.test.ts b/public/app/plugins/datasource/cloudwatch/variables.test.ts index 88350e3e331..56ef68254ec 100644 --- a/public/app/plugins/datasource/cloudwatch/variables.test.ts +++ b/public/app/plugins/datasource/cloudwatch/variables.test.ts @@ -213,6 +213,7 @@ describe('variables', () => { { text: 'Minimum', value: 'Minimum', expandable: true }, { text: 'Sum', value: 'Sum', expandable: true }, { text: 'SampleCount', value: 'SampleCount', expandable: true }, + { text: 'IQM', value: 'IQM', expandable: true }, ]); }); From e1ff43491709c98c811f715bbec210c0d229761c Mon Sep 17 00:00:00 2001 From: Virginia Cepeda Date: Wed, 10 May 2023 05:44:17 -0300 Subject: [PATCH 095/179] Alerting: Change text on cloud AM email addresses for contact points (#68143) --- .../ngalert/notifier/channels_config/available_channels.go | 2 +- .../unified/utils/cloud-alertmanager-notifier-types.ts | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/pkg/services/ngalert/notifier/channels_config/available_channels.go b/pkg/services/ngalert/notifier/channels_config/available_channels.go index fed29b40076..b7006eee0eb 100644 --- a/pkg/services/ngalert/notifier/channels_config/available_channels.go +++ b/pkg/services/ngalert/notifier/channels_config/available_channels.go @@ -255,7 +255,7 @@ func GetAvailableNotifiers() []*NotifierPlugin { }, { Label: "Addresses", - Description: "You can enter multiple email addresses using a \";\" separator", + Description: "You can enter multiple email addresses using a \";\", \"\\n\" or \",\" separator", Element: ElementTypeTextArea, PropertyName: "addresses", Required: true, diff --git a/public/app/features/alerting/unified/utils/cloud-alertmanager-notifier-types.ts b/public/app/features/alerting/unified/utils/cloud-alertmanager-notifier-types.ts index 9f250b46db0..0efafe331ee 100644 --- a/public/app/features/alerting/unified/utils/cloud-alertmanager-notifier-types.ts +++ b/public/app/features/alerting/unified/utils/cloud-alertmanager-notifier-types.ts @@ -77,7 +77,12 @@ export const cloudNotifierTypes: NotifierDTO[] = [ info: '', heading: 'Email settings', options: [ - option('to', 'To', 'The email address to send notifications to.', { required: true }), + option( + 'to', + 'To', + 'The email address to send notifications to. You can enter multiple addresses using a "," separator', + { required: true } + ), option('from', 'From', 'The sender address.'), option('smarthost', 'SMTP host', 'The SMTP host through which emails are sent.'), option('hello', 'Hello', 'The hostname to identify to the SMTP server.'), From 047763978d4e8bcc54ffb89a148a313cd7bcebe8 Mon Sep 17 00:00:00 2001 From: Marcus Andersson Date: Wed, 10 May 2023 11:03:15 +0200 Subject: [PATCH 096/179] Instrumentation: Measure app init load times (#67900) * adding load time metrics for app init. * chore: add the `metrics.ts` to the CODEOWNERS * moved file according to PR feedback. --------- Co-authored-by: Levente Balogh --- .github/CODEOWNERS | 1 + public/app/app.ts | 5 +++ public/app/core/utils/metrics.ts | 35 +++++++++++++++++++ .../app/features/plugins/pluginPreloader.ts | 9 ++++- 4 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 public/app/core/utils/metrics.ts diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index d0da194624f..91f5a5704a2 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -453,6 +453,7 @@ lerna.json @grafana/frontend-ops /public/app/angular/ @torkelo /public/app/app.ts @grafana/frontend-ops /public/app/dev.ts @grafana/frontend-ops +/public/app/core/utils/metrics.ts @grafana/plugins-platform-frontend /public/app/index.ts @grafana/frontend-ops /public/app/AppWrapper.tsx @grafana/frontend-ops /public/app/partials/ @grafana/grafana-frontend-platform diff --git a/public/app/app.ts b/public/app/app.ts index 56c10d5d92f..83734e1c234 100644 --- a/public/app/app.ts +++ b/public/app/app.ts @@ -67,6 +67,7 @@ import { GAEchoBackend } from './core/services/echo/backends/analytics/GABackend import { RudderstackBackend } from './core/services/echo/backends/analytics/RudderstackBackend'; import { GrafanaJavascriptAgentBackend } from './core/services/echo/backends/grafana-javascript-agent/GrafanaJavascriptAgentBackend'; import { KeybindingSrv } from './core/services/keybindingSrv'; +import { startMeasure, stopMeasure } from './core/utils/metrics'; import { initDevFeatures } from './dev'; import { getTimeSrv } from './features/dashboard/services/TimeSrv'; import { initGrafanaLive } from './features/live'; @@ -119,6 +120,8 @@ export class GrafanaApp { setBackendSrv(backendSrv); initEchoSrv(); + // This needs to be done after the `initEchoSrv` since it is being used under the hood. + startMeasure('frontend_app_init'); addClassIfNoOverlayScrollbar(); setLocale(config.bootData.user.locale); setWeekStart(config.bootData.user.weekStart); @@ -219,6 +222,8 @@ export class GrafanaApp { } catch (error) { console.error('Failed to start Grafana', error); window.__grafana_load_failed(); + } finally { + stopMeasure('frontend_app_init'); } } } diff --git a/public/app/core/utils/metrics.ts b/public/app/core/utils/metrics.ts new file mode 100644 index 00000000000..97551a721cc --- /dev/null +++ b/public/app/core/utils/metrics.ts @@ -0,0 +1,35 @@ +import { reportPerformance } from '../services/echo/EchoSrv'; + +export function startMeasure(eventName: string) { + if (!performance) { + return; + } + + try { + performance.mark(`${eventName}_started`); + } catch (error) { + console.error(`[Metrics] Failed to startMeasure ${eventName}`, error); + } +} + +export function stopMeasure(eventName: string) { + if (!performance) { + return; + } + + try { + const started = `${eventName}_started`; + const completed = `${eventName}_completed`; + const measured = `${eventName}_measured`; + + performance.mark(completed); + const measure = performance.measure(measured, started, completed); + reportPerformance(`${eventName}_ms`, measure.duration); + + performance.clearMarks(started); + performance.clearMarks(completed); + performance.clearMeasures(measured); + } catch (error) { + console.error(`[Metrics] Failed to stopMeasure ${eventName}`, error); + } +} diff --git a/public/app/features/plugins/pluginPreloader.ts b/public/app/features/plugins/pluginPreloader.ts index ce87dc5b377..b1ca1b35883 100644 --- a/public/app/features/plugins/pluginPreloader.ts +++ b/public/app/features/plugins/pluginPreloader.ts @@ -1,5 +1,6 @@ import type { PluginExtensionLinkConfig } from '@grafana/data'; import type { AppPluginConfig } from '@grafana/runtime'; +import { startMeasure, stopMeasure } from 'app/core/utils/metrics'; import * as pluginLoader from './plugin_loader'; @@ -10,18 +11,24 @@ export type PluginPreloadResult = { }; export async function preloadPlugins(apps: Record = {}): Promise { + startMeasure('frontend_plugins_preload'); const pluginsToPreload = Object.values(apps).filter((app) => app.preload); - return Promise.all(pluginsToPreload.map(preload)); + const result = await Promise.all(pluginsToPreload.map(preload)); + stopMeasure('frontend_plugins_preload'); + return result; } async function preload(config: AppPluginConfig): Promise { const { path, version, id: pluginId } = config; try { + startMeasure(`frontend_plugin_preload_${pluginId}`); const { plugin } = await pluginLoader.importPluginModule(path, version); const { extensionConfigs = [] } = plugin; return { pluginId, extensionConfigs }; } catch (error) { console.error(`[Plugins] Failed to preload plugin: ${path} (version: ${version})`, error); return { pluginId, extensionConfigs: [], error }; + } finally { + stopMeasure(`frontend_plugin_preload_${pluginId}`); } } From 23d8f7c2fe6b0b1d87e41daf7ad85e72c4a78662 Mon Sep 17 00:00:00 2001 From: Misi Date: Wed, 10 May 2023 11:24:37 +0200 Subject: [PATCH 097/179] RBAC: Fix SearchUsersPermissions when the filter is empty (#68176) Fix SearchUsersPermission action filter --- pkg/services/accesscontrol/acimpl/service.go | 11 ++----- .../accesscontrol/acimpl/service_test.go | 29 ++++++++++++++++++- 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/pkg/services/accesscontrol/acimpl/service.go b/pkg/services/accesscontrol/acimpl/service.go index e2bcfd3fc94..479a5941a9a 100644 --- a/pkg/services/accesscontrol/acimpl/service.go +++ b/pkg/services/accesscontrol/acimpl/service.go @@ -252,15 +252,8 @@ func (s *Service) SearchUsersPermissions(ctx context.Context, user *user.SignedI basicPermissions := map[string][]accesscontrol.Permission{} for role, basicRole := range s.roles { for i := range basicRole.Permissions { - if options.ActionPrefix != "" { - if strings.HasPrefix(basicRole.Permissions[i].Action, options.ActionPrefix) { - basicPermissions[role] = append(basicPermissions[role], basicRole.Permissions[i]) - } - } - if options.Action != "" { - if basicRole.Permissions[i].Action == options.Action { - basicPermissions[role] = append(basicPermissions[role], basicRole.Permissions[i]) - } + if PermissionMatchesSearchOptions(basicRole.Permissions[i], options) { + basicPermissions[role] = append(basicPermissions[role], basicRole.Permissions[i]) } } } diff --git a/pkg/services/accesscontrol/acimpl/service_test.go b/pkg/services/accesscontrol/acimpl/service_test.go index f4fce35c319..6da1b4bbcef 100644 --- a/pkg/services/accesscontrol/acimpl/service_test.go +++ b/pkg/services/accesscontrol/acimpl/service_test.go @@ -384,6 +384,7 @@ func TestService_SearchUsersPermissions(t *testing.T) { tests := []struct { name string siuPermissions map[string][]string + searchOption accesscontrol.SearchOptions ramRoles map[string]*accesscontrol.RoleDTO // BasicRole => RBAC BasicRole storedPerms map[int64][]accesscontrol.Permission // UserID => Permissions storedRoles map[int64][]string // UserID => Roles @@ -393,6 +394,7 @@ func TestService_SearchUsersPermissions(t *testing.T) { { name: "ram only", siuPermissions: listAllPerms, + searchOption: searchOption, ramRoles: map[string]*accesscontrol.RoleDTO{ string(roletype.RoleAdmin): {Permissions: []accesscontrol.Permission{ {Action: accesscontrol.ActionTeamsRead, Scope: "teams:*"}, @@ -413,6 +415,7 @@ func TestService_SearchUsersPermissions(t *testing.T) { { name: "stored only", siuPermissions: listAllPerms, + searchOption: searchOption, storedPerms: map[int64][]accesscontrol.Permission{ 1: {{Action: accesscontrol.ActionTeamsRead, Scope: "teams:id:1"}}, 2: {{Action: accesscontrol.ActionTeamsRead, Scope: "teams:*"}, @@ -431,6 +434,7 @@ func TestService_SearchUsersPermissions(t *testing.T) { { name: "ram and stored", siuPermissions: listAllPerms, + searchOption: searchOption, ramRoles: map[string]*accesscontrol.RoleDTO{ string(roletype.RoleAdmin): {Permissions: []accesscontrol.Permission{ {Action: accesscontrol.ActionTeamsRead, Scope: "teams:*"}, @@ -459,6 +463,7 @@ func TestService_SearchUsersPermissions(t *testing.T) { { name: "view permission on subset of users only", siuPermissions: listSomePerms, + searchOption: searchOption, ramRoles: map[string]*accesscontrol.RoleDTO{ accesscontrol.RoleGrafanaAdmin: {Permissions: []accesscontrol.Permission{ {Action: accesscontrol.ActionTeamsPermissionsRead, Scope: "teams:*"}, @@ -482,6 +487,7 @@ func TestService_SearchUsersPermissions(t *testing.T) { { name: "check action filter on RAM permissions works correctly", siuPermissions: listAllPerms, + searchOption: searchOption, ramRoles: map[string]*accesscontrol.RoleDTO{ accesscontrol.RoleGrafanaAdmin: {Permissions: []accesscontrol.Permission{ {Action: accesscontrol.ActionUsersCreate}, @@ -493,6 +499,27 @@ func TestService_SearchUsersPermissions(t *testing.T) { 1: {{Action: accesscontrol.ActionTeamsPermissionsRead, Scope: "teams:*"}}, }, }, + { + name: "check empty action filter on RAM permissions works correctly", + siuPermissions: listAllPerms, + searchOption: accesscontrol.SearchOptions{}, + ramRoles: map[string]*accesscontrol.RoleDTO{ + accesscontrol.RoleGrafanaAdmin: {Permissions: []accesscontrol.Permission{ + {Action: accesscontrol.ActionTeamsRead, Scope: "teams:*"}, + {Action: accesscontrol.ActionUsersCreate}, + {Action: accesscontrol.ActionTeamsPermissionsRead, Scope: "teams:*"}, + {Action: accesscontrol.ActionAnnotationsRead, Scope: "annotations:*"}, + }}, + }, + storedRoles: map[int64][]string{1: {accesscontrol.RoleGrafanaAdmin}}, + want: map[int64][]accesscontrol.Permission{ + 1: {{Action: accesscontrol.ActionTeamsRead, Scope: "teams:*"}, + {Action: accesscontrol.ActionUsersCreate}, + {Action: accesscontrol.ActionTeamsPermissionsRead, Scope: "teams:*"}, + {Action: accesscontrol.ActionAnnotationsRead, Scope: "annotations:*"}, + }, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -505,7 +532,7 @@ func TestService_SearchUsersPermissions(t *testing.T) { } siu := &user.SignedInUser{OrgID: 2, Permissions: map[int64]map[string][]string{2: tt.siuPermissions}} - got, err := ac.SearchUsersPermissions(ctx, siu, 2, searchOption) + got, err := ac.SearchUsersPermissions(ctx, siu, 2, tt.searchOption) if tt.wantErr { require.NotNil(t, err) return From dcb3b4d97a007be8f268b4d320ca92a7dae95194 Mon Sep 17 00:00:00 2001 From: Tania Date: Wed, 10 May 2023 11:27:13 +0200 Subject: [PATCH 098/179] Codegen: Fix composable kinds docs generation (#68087) * Fix plugins docs and regenerate * fixup! Fix plugins docs and regenerate --- .../barchartpanelcfg/schema-reference.md | 38 +-- .../bargaugepanelcfg/schema-reference.md | 14 +- .../cloudwatchdataquery/schema-reference.md | 2 +- .../dashboardlistpanelcfg/schema-reference.md | 30 +-- .../datagridpanelcfg/schema-reference.md | 26 ++ .../schema-reference.md | 5 +- .../heatmappanelcfg/schema-reference.md | 30 +-- .../histogrampanelcfg/schema-reference.md | 30 +-- .../prometheusdataquery/schema-reference.md | 36 +-- .../statetimelinepanelcfg/schema-reference.md | 18 +- .../statpanelcfg/schema-reference.md | 2 +- .../statushistorypanelcfg/schema-reference.md | 2 +- .../tablepanelcfg/schema-reference.md | 14 +- .../timeseriespanelcfg/schema-reference.md | 224 ++++++++++++++++++ .../trendpanelcfg/schema-reference.md | 216 +++++++++++++++++ .../xychartpanelcfg/schema-reference.md | 48 ++-- public/app/plugins/gen.go | 7 +- 17 files changed, 592 insertions(+), 150 deletions(-) create mode 100644 docs/sources/developers/kinds/composable/datagridpanelcfg/schema-reference.md rename docs/sources/developers/kinds/composable/{phlaredataquery => grafanapyroscopedataquery}/schema-reference.md (92%) create mode 100644 docs/sources/developers/kinds/composable/timeseriespanelcfg/schema-reference.md create mode 100644 docs/sources/developers/kinds/composable/trendpanelcfg/schema-reference.md diff --git a/docs/sources/developers/kinds/composable/barchartpanelcfg/schema-reference.md b/docs/sources/developers/kinds/composable/barchartpanelcfg/schema-reference.md index 42042d782e2..2bfe35d4273 100644 --- a/docs/sources/developers/kinds/composable/barchartpanelcfg/schema-reference.md +++ b/docs/sources/developers/kinds/composable/barchartpanelcfg/schema-reference.md @@ -22,22 +22,22 @@ title: BarChartPanelCfg kind It extends [AxisConfig](#axisconfig) and [HideableFieldConfig](#hideablefieldconfig). -| Property | Type | Required | Default | Description | -|---------------------|-----------------------------------------------------------|----------|---------|-----------------------------------------------------------------------------------------------------------------------------------------| -| `axisCenteredZero` | boolean | No | | *(Inherited from [AxisConfig](#axisconfig))* | -| `axisColorMode` | string | No | | *(Inherited from [AxisConfig](#axisconfig))*
    TODO docs
    Possible values are: `text`, `series`. | -| `axisGridShow` | boolean | No | | *(Inherited from [AxisConfig](#axisconfig))* | -| `axisLabel` | string | No | | *(Inherited from [AxisConfig](#axisconfig))* | -| `axisPlacement` | string | No | | *(Inherited from [AxisConfig](#axisconfig))*
    TODO docs
    Possible values are: `auto`, `top`, `right`, `bottom`, `left`, `hidden`. | -| `axisSoftMax` | number | No | | *(Inherited from [AxisConfig](#axisconfig))* | -| `axisSoftMin` | number | No | | *(Inherited from [AxisConfig](#axisconfig))* | -| `axisWidth` | number | No | | *(Inherited from [AxisConfig](#axisconfig))* | -| `fillOpacity` | integer | No | `80` | Controls the fill opacity of the bars.
    Constraint: `>=0 & <=100`. | -| `gradientMode` | string | No | | TODO docs
    Possible values are: `none`, `opacity`, `hue`, `scheme`. | -| `hideFrom` | [HideSeriesConfig](#hideseriesconfig) | No | | *(Inherited from [HideableFieldConfig](#hideablefieldconfig))*
    TODO docs | -| `lineWidth` | integer | No | `1` | Controls line width of the bars.
    Constraint: `>=0 & <=10`. | -| `scaleDistribution` | [ScaleDistributionConfig](#scaledistributionconfig) | No | | *(Inherited from [AxisConfig](#axisconfig))*
    TODO docs | -| `thresholdsStyle` | [GraphThresholdsStyleConfig](#graphthresholdsstyleconfig) | No | | TODO docs | +| Property | Type | Required | Default | Description | +|---------------------|-----------------------------------------------------------|----------|---------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `axisCenteredZero` | boolean | No | | *(Inherited from [AxisConfig](#axisconfig))* | +| `axisColorMode` | string | No | | *(Inherited from [AxisConfig](#axisconfig))*
    TODO docs
    Possible values are: `text`, `series`. | +| `axisGridShow` | boolean | No | | *(Inherited from [AxisConfig](#axisconfig))* | +| `axisLabel` | string | No | | *(Inherited from [AxisConfig](#axisconfig))* | +| `axisPlacement` | string | No | | *(Inherited from [AxisConfig](#axisconfig))*
    TODO docs
    Possible values are: `auto`, `top`, `right`, `bottom`, `left`, `hidden`. | +| `axisSoftMax` | number | No | | *(Inherited from [AxisConfig](#axisconfig))* | +| `axisSoftMin` | number | No | | *(Inherited from [AxisConfig](#axisconfig))* | +| `axisWidth` | number | No | | *(Inherited from [AxisConfig](#axisconfig))* | +| `fillOpacity` | integer | No | `80` | Controls the fill opacity of the bars.
    Constraint: `>=0 & <=100`. | +| `gradientMode` | string | No | | Set the mode of the gradient fill. Fill gradient is based on the line color. To change the color, use the standard color scheme field option.
    Gradient appearance is influenced by the Fill opacity setting. | +| `hideFrom` | [HideSeriesConfig](#hideseriesconfig) | No | | *(Inherited from [HideableFieldConfig](#hideablefieldconfig))*
    TODO docs | +| `lineWidth` | integer | No | `1` | Controls line width of the bars.
    Constraint: `>=0 & <=10`. | +| `scaleDistribution` | [ScaleDistributionConfig](#scaledistributionconfig) | No | | *(Inherited from [AxisConfig](#axisconfig))*
    TODO docs | +| `thresholdsStyle` | [GraphThresholdsStyleConfig](#graphthresholdsstyleconfig) | No | | TODO docs | ### AxisConfig @@ -101,9 +101,9 @@ It extends [OptionsWithLegend](#optionswithlegend) and [OptionsWithTooltip](#opt | `fullHighlight` | boolean | **Yes** | `false` | Enables mode which highlights the entire bar area and shows tooltip when cursor
    hovers over highlighted area | | `groupWidth` | number | **Yes** | `0.7` | Controls the width of groups. 1 = max with, 0 = min width.
    Constraint: `>=0 & <=1`. | | `legend` | [VizLegendOptions](#vizlegendoptions) | **Yes** | | *(Inherited from [OptionsWithLegend](#optionswithlegend))*
    TODO docs | -| `orientation` | string | **Yes** | | TODO docs
    Possible values are: `auto`, `vertical`, `horizontal`. | -| `showValue` | string | **Yes** | | TODO docs
    Possible values are: `auto`, `never`, `always`. | -| `stacking` | string | **Yes** | | TODO docs
    Possible values are: `none`, `normal`, `percent`. | +| `orientation` | string | **Yes** | | Controls the orientation of the bar chart, either vertical or horizontal. | +| `showValue` | string | **Yes** | | This controls whether values are shown on top or to the left of bars. | +| `stacking` | string | **Yes** | | Controls whether bars are stacked or not, either normally or in percent mode. | | `tooltip` | [VizTooltipOptions](#viztooltipoptions) | **Yes** | | *(Inherited from [OptionsWithTooltip](#optionswithtooltip))*
    TODO docs | | `xTickLabelMaxLength` | integer | **Yes** | | Sets the max length that a label can have before it is truncated.
    Constraint: `>=0 & <=2147483647`. | | `xTickLabelRotation` | integer | **Yes** | `0` | Controls the rotation of the x axis labels.
    Constraint: `>=-90 & <=90`. | diff --git a/docs/sources/developers/kinds/composable/bargaugepanelcfg/schema-reference.md b/docs/sources/developers/kinds/composable/bargaugepanelcfg/schema-reference.md index a4b0ff51d8a..c8b16c51a00 100644 --- a/docs/sources/developers/kinds/composable/bargaugepanelcfg/schema-reference.md +++ b/docs/sources/developers/kinds/composable/bargaugepanelcfg/schema-reference.md @@ -21,28 +21,16 @@ title: BarGaugePanelCfg kind It extends [SingleStatBaseOptions](#singlestatbaseoptions). -<<<<<<< HEAD | Property | Type | Required | Default | Description | |-----------------|-------------------------------------------------|----------|---------|-----------------------------------------------------------------------------------------------------------------------------------------------| | `displayMode` | string | **Yes** | | Enum expressing the possible display modes
    for the bar gauge component of Grafana UI
    Possible values are: `basic`, `lcd`, `gradient`. | | `minVizHeight` | uint32 | **Yes** | `10` | | | `minVizWidth` | uint32 | **Yes** | `0` | | | `showUnfilled` | boolean | **Yes** | `true` | | +| `valueMode` | string | **Yes** | | Allows for the table cell gauge display type to set the gauge mode.
    Possible values are: `color`, `text`, `hidden`. | | `orientation` | string | No | | *(Inherited from [SingleStatBaseOptions](#singlestatbaseoptions))*
    TODO docs
    Possible values are: `auto`, `vertical`, `horizontal`. | | `reduceOptions` | [ReduceDataOptions](#reducedataoptions) | No | | *(Inherited from [SingleStatBaseOptions](#singlestatbaseoptions))*
    TODO docs | | `text` | [VizTextDisplayOptions](#viztextdisplayoptions) | No | | *(Inherited from [SingleStatBaseOptions](#singlestatbaseoptions))*
    TODO docs | -======= -| Property | Type | Required | Description | -|-----------------|-------------------------------------------------|----------|-----------------------------------------------------------------------------------------------------------------------------------------------| -| `displayMode` | string | **Yes** | Enum expressing the possible display modes
    for the bar gauge component of Grafana UI
    Possible values are: `basic`, `lcd`, `gradient`. | -| `minVizHeight` | uint32 | **Yes** | Default: `10`. | -| `minVizWidth` | uint32 | **Yes** | Default: `0`. | -| `showUnfilled` | boolean | **Yes** | Default: `true`. | -| `valueMode` | string | **Yes** | Allows for the table cell gauge display type to set the gauge mode.
    Possible values are: `color`, `text`, `hidden`. | -| `orientation` | string | No | *(Inherited from [SingleStatBaseOptions](#singlestatbaseoptions))*
    TODO docs
    Possible values are: `auto`, `vertical`, `horizontal`. | -| `reduceOptions` | [ReduceDataOptions](#reducedataoptions) | No | *(Inherited from [SingleStatBaseOptions](#singlestatbaseoptions))*
    TODO docs | -| `text` | [VizTextDisplayOptions](#viztextdisplayoptions) | No | *(Inherited from [SingleStatBaseOptions](#singlestatbaseoptions))*
    TODO docs | ->>>>>>> main ### ReduceDataOptions diff --git a/docs/sources/developers/kinds/composable/cloudwatchdataquery/schema-reference.md b/docs/sources/developers/kinds/composable/cloudwatchdataquery/schema-reference.md index 04cda4263be..44926975453 100644 --- a/docs/sources/developers/kinds/composable/cloudwatchdataquery/schema-reference.md +++ b/docs/sources/developers/kinds/composable/cloudwatchdataquery/schema-reference.md @@ -8,7 +8,7 @@ title: CloudWatchDataQuery kind ## CloudWatchDataQuery -#### Maturity: [merged](../../../maturity/#merged) +#### Maturity: [experimental](../../../maturity/#experimental) #### Version: 0.0 diff --git a/docs/sources/developers/kinds/composable/dashboardlistpanelcfg/schema-reference.md b/docs/sources/developers/kinds/composable/dashboardlistpanelcfg/schema-reference.md index 2ee58ccd471..70cf0953e9f 100644 --- a/docs/sources/developers/kinds/composable/dashboardlistpanelcfg/schema-reference.md +++ b/docs/sources/developers/kinds/composable/dashboardlistpanelcfg/schema-reference.md @@ -13,23 +13,23 @@ title: DashboardListPanelCfg kind -| Property | Type | Required | Default | Description | -|----------------|-------------------------|----------|---------|------------------------------------------| -| `PanelLayout` | string | **Yes** | | Possible values are: `list`, `previews`. | -| `PanelOptions` | [object](#paneloptions) | **Yes** | | | +| Property | Type | Required | Default | Description | +|----------------|-------------------------|----------|---------|-------------| +| `PanelOptions` | [object](#paneloptions) | **Yes** | | | ### PanelOptions -| Property | Type | Required | Default | Description | -|----------------------|----------|----------|---------|------------------------------------------| -| `maxItems` | integer | **Yes** | `10` | | -| `query` | string | **Yes** | `` | | -| `showHeadings` | boolean | **Yes** | `true` | | -| `showRecentlyViewed` | boolean | **Yes** | `false` | | -| `showSearch` | boolean | **Yes** | `false` | | -| `showStarred` | boolean | **Yes** | `true` | | -| `tags` | string[] | **Yes** | | | -| `folderId` | integer | No | | | -| `layout` | string | No | | Possible values are: `list`, `previews`. | +| Property | Type | Required | Default | Description | +|----------------------|----------|----------|---------|-------------| +| `includeVars` | boolean | **Yes** | `false` | | +| `keepTime` | boolean | **Yes** | `false` | | +| `maxItems` | integer | **Yes** | `10` | | +| `query` | string | **Yes** | `` | | +| `showHeadings` | boolean | **Yes** | `true` | | +| `showRecentlyViewed` | boolean | **Yes** | `false` | | +| `showSearch` | boolean | **Yes** | `false` | | +| `showStarred` | boolean | **Yes** | `true` | | +| `tags` | string[] | **Yes** | | | +| `folderId` | integer | No | | | diff --git a/docs/sources/developers/kinds/composable/datagridpanelcfg/schema-reference.md b/docs/sources/developers/kinds/composable/datagridpanelcfg/schema-reference.md new file mode 100644 index 00000000000..7960e397144 --- /dev/null +++ b/docs/sources/developers/kinds/composable/datagridpanelcfg/schema-reference.md @@ -0,0 +1,26 @@ +--- +keywords: + - grafana + - schema +title: DatagridPanelCfg kind +--- +> Both documentation generation and kinds schemas are in active development and subject to change without prior notice. + +## DatagridPanelCfg + +#### Maturity: [experimental](../../../maturity/#experimental) +#### Version: 0.0 + + + +| Property | Type | Required | Default | Description | +|----------------|-------------------------|----------|---------|-------------| +| `PanelOptions` | [object](#paneloptions) | **Yes** | | | + +### PanelOptions + +| Property | Type | Required | Default | Description | +|------------------|---------|----------|---------|-----------------------------------| +| `selectedSeries` | integer | **Yes** | `0` | Constraint: `>=0 & <=2147483647`. | + + diff --git a/docs/sources/developers/kinds/composable/phlaredataquery/schema-reference.md b/docs/sources/developers/kinds/composable/grafanapyroscopedataquery/schema-reference.md similarity index 92% rename from docs/sources/developers/kinds/composable/phlaredataquery/schema-reference.md rename to docs/sources/developers/kinds/composable/grafanapyroscopedataquery/schema-reference.md index 94224fc8fd5..e68af482c4d 100644 --- a/docs/sources/developers/kinds/composable/phlaredataquery/schema-reference.md +++ b/docs/sources/developers/kinds/composable/grafanapyroscopedataquery/schema-reference.md @@ -2,11 +2,11 @@ keywords: - grafana - schema -title: PhlareDataQuery kind +title: GrafanaPyroscopeDataQuery kind --- > Both documentation generation and kinds schemas are in active development and subject to change without prior notice. -## PhlareDataQuery +## GrafanaPyroscopeDataQuery #### Maturity: [experimental](../../../maturity/#experimental) #### Version: 0.0 @@ -23,6 +23,7 @@ It extends [DataQuery](#dataquery). | `refId` | string | **Yes** | | *(Inherited from [DataQuery](#dataquery))*
    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. | | `datasource` | | No | | *(Inherited from [DataQuery](#dataquery))*
    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 | | `hide` | boolean | No | | *(Inherited from [DataQuery](#dataquery))*
    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) | +| `maxNodes` | integer | No | | Sets the maximum number of nodes in the flamegraph. | | `queryType` | string | No | | *(Inherited from [DataQuery](#dataquery))*
    Specify the query flavor
    TODO make this required and give it a default | ### DataQuery diff --git a/docs/sources/developers/kinds/composable/heatmappanelcfg/schema-reference.md b/docs/sources/developers/kinds/composable/heatmappanelcfg/schema-reference.md index a94cf9395e9..3c2a99716e1 100644 --- a/docs/sources/developers/kinds/composable/heatmappanelcfg/schema-reference.md +++ b/docs/sources/developers/kinds/composable/heatmappanelcfg/schema-reference.md @@ -126,21 +126,21 @@ TODO docs ### PanelOptions -| Property | Type | Required | Default | Description | -|----------------|---------------------------------------------------------|----------|----------------------------------------------------------------------------|---------------------------------------------------------------| -| `color` | [object](#color) | **Yes** | `map[exponent:0.5 fill:dark-orange reverse:false scheme:Oranges steps:64]` | Controls the color options | -| `exemplars` | [ExemplarConfig](#exemplarconfig) | **Yes** | | Controls exemplar options | -| `legend` | [HeatmapLegend](#heatmaplegend) | **Yes** | | Controls legend options | -| `showValue` | string | **Yes** | | TODO docs
    Possible values are: `auto`, `never`, `always`. | -| `tooltip` | [HeatmapTooltip](#heatmaptooltip) | **Yes** | | Controls tooltip options | -| `yAxis` | [YAxisConfig](#yaxisconfig) | **Yes** | | Configuration options for the yAxis | -| `calculate` | boolean | No | `false` | Controls if the heatmap should be calculated from data | -| `calculation` | [HeatmapCalculationOptions](#heatmapcalculationoptions) | No | | | -| `cellGap` | integer | No | `1` | Controls gap between cells
    Constraint: `>=0 & <=25`. | -| `cellRadius` | number | No | | Controls cell radius | -| `cellValues` | [object](#cellvalues) | No | `map[]` | Controls cell value unit | -| `filterValues` | [object](#filtervalues) | No | `map[le:1e-09]` | Filters values between a given range | -| `rowsFrame` | [RowsHeatmapOptions](#rowsheatmapoptions) | No | | Controls frame rows options | +| Property | Type | Required | Default | Description | +|----------------|---------------------------------------------------------|----------|----------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `color` | [object](#color) | **Yes** | `map[exponent:0.5 fill:dark-orange reverse:false scheme:Oranges steps:64]` | Controls the color options | +| `exemplars` | [ExemplarConfig](#exemplarconfig) | **Yes** | | Controls exemplar options | +| `legend` | [HeatmapLegend](#heatmaplegend) | **Yes** | | Controls legend options | +| `showValue` | string | **Yes** | | | *{
    layout: ui.HeatmapCellLayout & "auto" // TODO: fix after remove when https://github.com/grafana/cuetsy/issues/74 is fixed
    }
    Controls the display of the value in the cell | +| `tooltip` | [HeatmapTooltip](#heatmaptooltip) | **Yes** | | Controls tooltip options | +| `yAxis` | [YAxisConfig](#yaxisconfig) | **Yes** | | Configuration options for the yAxis | +| `calculate` | boolean | No | `false` | Controls if the heatmap should be calculated from data | +| `calculation` | [HeatmapCalculationOptions](#heatmapcalculationoptions) | No | | | +| `cellGap` | integer | No | `1` | Controls gap between cells
    Constraint: `>=0 & <=25`. | +| `cellRadius` | number | No | | Controls cell radius | +| `cellValues` | [object](#cellvalues) | No | `map[]` | Controls cell value unit | +| `filterValues` | [object](#filtervalues) | No | `map[le:1e-09]` | Filters values between a given range | +| `rowsFrame` | [RowsHeatmapOptions](#rowsheatmapoptions) | No | | Controls frame rows options | ### HeatmapCalculationOptions diff --git a/docs/sources/developers/kinds/composable/histogrampanelcfg/schema-reference.md b/docs/sources/developers/kinds/composable/histogrampanelcfg/schema-reference.md index aff139666d8..d2cd33902ea 100644 --- a/docs/sources/developers/kinds/composable/histogrampanelcfg/schema-reference.md +++ b/docs/sources/developers/kinds/composable/histogrampanelcfg/schema-reference.md @@ -22,21 +22,21 @@ title: HistogramPanelCfg kind It extends [AxisConfig](#axisconfig) and [HideableFieldConfig](#hideablefieldconfig). -| Property | Type | Required | Default | Description | -|---------------------|-----------------------------------------------------|----------|---------|-----------------------------------------------------------------------------------------------------------------------------------------| -| `axisCenteredZero` | boolean | No | | *(Inherited from [AxisConfig](#axisconfig))* | -| `axisColorMode` | string | No | | *(Inherited from [AxisConfig](#axisconfig))*
    TODO docs
    Possible values are: `text`, `series`. | -| `axisGridShow` | boolean | No | | *(Inherited from [AxisConfig](#axisconfig))* | -| `axisLabel` | string | No | | *(Inherited from [AxisConfig](#axisconfig))* | -| `axisPlacement` | string | No | | *(Inherited from [AxisConfig](#axisconfig))*
    TODO docs
    Possible values are: `auto`, `top`, `right`, `bottom`, `left`, `hidden`. | -| `axisSoftMax` | number | No | | *(Inherited from [AxisConfig](#axisconfig))* | -| `axisSoftMin` | number | No | | *(Inherited from [AxisConfig](#axisconfig))* | -| `axisWidth` | number | No | | *(Inherited from [AxisConfig](#axisconfig))* | -| `fillOpacity` | integer | No | `80` | Controls the fill opacity of the bars.
    Constraint: `>=0 & <=100`. | -| `gradientMode` | string | No | | TODO docs
    Possible values are: `none`, `opacity`, `hue`, `scheme`. | -| `hideFrom` | [HideSeriesConfig](#hideseriesconfig) | No | | *(Inherited from [HideableFieldConfig](#hideablefieldconfig))*
    TODO docs | -| `lineWidth` | integer | No | `1` | Controls line width of the bars.
    Constraint: `>=0 & <=10`. | -| `scaleDistribution` | [ScaleDistributionConfig](#scaledistributionconfig) | No | | *(Inherited from [AxisConfig](#axisconfig))*
    TODO docs | +| Property | Type | Required | Default | Description | +|---------------------|-----------------------------------------------------|----------|---------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `axisCenteredZero` | boolean | No | | *(Inherited from [AxisConfig](#axisconfig))* | +| `axisColorMode` | string | No | | *(Inherited from [AxisConfig](#axisconfig))*
    TODO docs
    Possible values are: `text`, `series`. | +| `axisGridShow` | boolean | No | | *(Inherited from [AxisConfig](#axisconfig))* | +| `axisLabel` | string | No | | *(Inherited from [AxisConfig](#axisconfig))* | +| `axisPlacement` | string | No | | *(Inherited from [AxisConfig](#axisconfig))*
    TODO docs
    Possible values are: `auto`, `top`, `right`, `bottom`, `left`, `hidden`. | +| `axisSoftMax` | number | No | | *(Inherited from [AxisConfig](#axisconfig))* | +| `axisSoftMin` | number | No | | *(Inherited from [AxisConfig](#axisconfig))* | +| `axisWidth` | number | No | | *(Inherited from [AxisConfig](#axisconfig))* | +| `fillOpacity` | integer | No | `80` | Controls the fill opacity of the bars.
    Constraint: `>=0 & <=100`. | +| `gradientMode` | string | No | | Set the mode of the gradient fill. Fill gradient is based on the line color. To change the color, use the standard color scheme field option.
    Gradient appearance is influenced by the Fill opacity setting. | +| `hideFrom` | [HideSeriesConfig](#hideseriesconfig) | No | | *(Inherited from [HideableFieldConfig](#hideablefieldconfig))*
    TODO docs | +| `lineWidth` | integer | No | `1` | Controls line width of the bars.
    Constraint: `>=0 & <=10`. | +| `scaleDistribution` | [ScaleDistributionConfig](#scaledistributionconfig) | No | | *(Inherited from [AxisConfig](#axisconfig))*
    TODO docs | ### AxisConfig diff --git a/docs/sources/developers/kinds/composable/prometheusdataquery/schema-reference.md b/docs/sources/developers/kinds/composable/prometheusdataquery/schema-reference.md index 2645ed1d5c9..fdbc3b04692 100644 --- a/docs/sources/developers/kinds/composable/prometheusdataquery/schema-reference.md +++ b/docs/sources/developers/kinds/composable/prometheusdataquery/schema-reference.md @@ -15,18 +15,18 @@ title: PrometheusDataQuery kind It extends [DataQuery](#dataquery). -| Property | Type | Required | Description | -|--------------|---------|----------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `expr` | string | **Yes** | The actual expression/query that will be evaluated by Prometheus | -| `refId` | string | **Yes** | *(Inherited from [DataQuery](#dataquery))*
    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. | -| `datasource` | | No | *(Inherited from [DataQuery](#dataquery))*
    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 | -| `editorMode` | string | No | Possible values are: `code`, `builder`. | -| `exemplar` | boolean | No | Execute an additional query to identify interesting raw samples relevant for the given expr | -| `format` | string | No | Possible values are: `time_series`, `table`, `heatmap`. | -| `hide` | boolean | No | *(Inherited from [DataQuery](#dataquery))*
    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) | -| `instant` | boolean | No | Returns only the latest value that Prometheus has scraped for the requested time series | -| `queryType` | string | No | *(Inherited from [DataQuery](#dataquery))*
    Specify the query flavor
    TODO make this required and give it a default | -| `range` | boolean | No | Returns a Range vector, comprised of a set of time series containing a range of data points over time for each time series | +| Property | Type | Required | Default | Description | +|--------------|---------|----------|---------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `expr` | string | **Yes** | | The actual expression/query that will be evaluated by Prometheus | +| `refId` | string | **Yes** | | *(Inherited from [DataQuery](#dataquery))*
    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. | +| `datasource` | | No | | *(Inherited from [DataQuery](#dataquery))*
    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 | +| `editorMode` | string | No | | Possible values are: `code`, `builder`. | +| `exemplar` | boolean | No | | Execute an additional query to identify interesting raw samples relevant for the given expr | +| `format` | string | No | | Possible values are: `time_series`, `table`, `heatmap`. | +| `hide` | boolean | No | | *(Inherited from [DataQuery](#dataquery))*
    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) | +| `instant` | boolean | No | | Returns only the latest value that Prometheus has scraped for the requested time series | +| `queryType` | string | No | | *(Inherited from [DataQuery](#dataquery))*
    Specify the query flavor
    TODO make this required and give it a default | +| `range` | boolean | No | | Returns a Range vector, comprised of a set of time series containing a range of data points over time for each time series | ### DataQuery @@ -34,11 +34,11 @@ These are the common properties available to all queries in all datasources. Specific implementations will *extend* this interface, adding the required properties for the given context. -| Property | Type | Required | Description | -|--------------|---------|----------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `refId` | string | **Yes** | 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. | -| `datasource` | | No | 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 | -| `hide` | boolean | No | 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) | -| `queryType` | string | No | Specify the query flavor
    TODO make this required and give it a default | +| Property | Type | Required | Default | Description | +|--------------|---------|----------|---------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `refId` | string | **Yes** | | 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. | +| `datasource` | | No | | 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 | +| `hide` | boolean | No | | 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) | +| `queryType` | string | No | | Specify the query flavor
    TODO make this required and give it a default | diff --git a/docs/sources/developers/kinds/composable/statetimelinepanelcfg/schema-reference.md b/docs/sources/developers/kinds/composable/statetimelinepanelcfg/schema-reference.md index 2bb36773f31..174d173249a 100644 --- a/docs/sources/developers/kinds/composable/statetimelinepanelcfg/schema-reference.md +++ b/docs/sources/developers/kinds/composable/statetimelinepanelcfg/schema-reference.md @@ -50,15 +50,15 @@ TODO docs It extends [OptionsWithLegend](#optionswithlegend) and [OptionsWithTooltip](#optionswithtooltip) and [OptionsWithTimezones](#optionswithtimezones). -| Property | Type | Required | Default | Description | -|---------------|-----------------------------------------|----------|---------|-----------------------------------------------------------------------------------------------------------------| -| `legend` | [VizLegendOptions](#vizlegendoptions) | **Yes** | | *(Inherited from [OptionsWithLegend](#optionswithlegend))*
    TODO docs | -| `rowHeight` | number | **Yes** | `0.9` | Controls the row height | -| `showValue` | string | **Yes** | | TODO docs
    Possible values are: `auto`, `never`, `always`. | -| `tooltip` | [VizTooltipOptions](#viztooltipoptions) | **Yes** | | *(Inherited from [OptionsWithTooltip](#optionswithtooltip))*
    TODO docs | -| `alignValue` | string | No | | Controls the value alignment in the TimelineChart component
    Possible values are: `center`, `left`, `right`. | -| `mergeValues` | boolean | No | `true` | Merge equal consecutive values | -| `timezone` | string[] | No | | *(Inherited from [OptionsWithTimezones](#optionswithtimezones))* | +| Property | Type | Required | Default | Description | +|---------------|-----------------------------------------|----------|---------|----------------------------------------------------------------------------| +| `legend` | [VizLegendOptions](#vizlegendoptions) | **Yes** | | *(Inherited from [OptionsWithLegend](#optionswithlegend))*
    TODO docs | +| `rowHeight` | number | **Yes** | `0.9` | Controls the row height | +| `showValue` | string | **Yes** | | Show timeline values on chart | +| `tooltip` | [VizTooltipOptions](#viztooltipoptions) | **Yes** | | *(Inherited from [OptionsWithTooltip](#optionswithtooltip))*
    TODO docs | +| `alignValue` | string | No | | Controls value alignment on the timelines | +| `mergeValues` | boolean | No | `true` | Merge equal consecutive values | +| `timezone` | string[] | No | | *(Inherited from [OptionsWithTimezones](#optionswithtimezones))* | ### OptionsWithLegend diff --git a/docs/sources/developers/kinds/composable/statpanelcfg/schema-reference.md b/docs/sources/developers/kinds/composable/statpanelcfg/schema-reference.md index c7a13e43cee..5aa0c1ab84a 100644 --- a/docs/sources/developers/kinds/composable/statpanelcfg/schema-reference.md +++ b/docs/sources/developers/kinds/composable/statpanelcfg/schema-reference.md @@ -23,7 +23,7 @@ It extends [SingleStatBaseOptions](#singlestatbaseoptions). | Property | Type | Required | Default | Description | |-----------------|-------------------------------------------------|----------|---------|---------------------------------------------------------------------------------------------------------------------------------------------| -| `colorMode` | string | **Yes** | | TODO docs
    Possible values are: `value`, `background`, `none`. | +| `colorMode` | string | **Yes** | | TODO docs
    Possible values are: `value`, `background`, `background_solid`, `none`. | | `graphMode` | string | **Yes** | | TODO docs
    Possible values are: `none`, `line`, `area`. | | `justifyMode` | string | **Yes** | | TODO docs
    Possible values are: `auto`, `center`. | | `textMode` | string | **Yes** | | TODO docs
    Possible values are: `auto`, `value`, `value_and_name`, `name`, `none`. | diff --git a/docs/sources/developers/kinds/composable/statushistorypanelcfg/schema-reference.md b/docs/sources/developers/kinds/composable/statushistorypanelcfg/schema-reference.md index 4cfa74dcd2b..baaf987b83b 100644 --- a/docs/sources/developers/kinds/composable/statushistorypanelcfg/schema-reference.md +++ b/docs/sources/developers/kinds/composable/statushistorypanelcfg/schema-reference.md @@ -54,7 +54,7 @@ It extends [OptionsWithLegend](#optionswithlegend) and [OptionsWithTooltip](#opt |-------------|-----------------------------------------|----------|---------|----------------------------------------------------------------------------| | `legend` | [VizLegendOptions](#vizlegendoptions) | **Yes** | | *(Inherited from [OptionsWithLegend](#optionswithlegend))*
    TODO docs | | `rowHeight` | number | **Yes** | `0.9` | Set the height of the rows
    Constraint: `>=0 & <=1`. | -| `showValue` | string | **Yes** | | TODO docs
    Possible values are: `auto`, `never`, `always`. | +| `showValue` | string | **Yes** | | Show values on the columns | | `tooltip` | [VizTooltipOptions](#viztooltipoptions) | **Yes** | | *(Inherited from [OptionsWithTooltip](#optionswithtooltip))*
    TODO docs | | `colWidth` | number | No | `0.9` | Controls the column width | | `timezone` | string[] | No | | *(Inherited from [OptionsWithTimezones](#optionswithtimezones))* | diff --git a/docs/sources/developers/kinds/composable/tablepanelcfg/schema-reference.md b/docs/sources/developers/kinds/composable/tablepanelcfg/schema-reference.md index 27d2e502fe0..e03d4d6f868 100644 --- a/docs/sources/developers/kinds/composable/tablepanelcfg/schema-reference.md +++ b/docs/sources/developers/kinds/composable/tablepanelcfg/schema-reference.md @@ -19,26 +19,14 @@ title: TablePanelCfg kind ### PanelOptions -<<<<<<< HEAD | Property | Type | Required | Default | Description | |-----------------|---------------------------------------------------|----------|----------------------------------------------|--------------------------------------------------------------------| | `frameIndex` | number | **Yes** | `0` | Represents the index of the selected frame | | `showHeader` | boolean | **Yes** | `true` | Controls whether the panel should show the header | +| `cellHeight` | string | No | | Controls the height of the rows | | `footer` | [object](#footer) | No | `map[countRows:false reducer:[] show:false]` | Controls footer options | -| `showRowNums` | boolean | No | `false` | Controls whether the columns should be numbered | | `showTypeIcons` | boolean | No | `false` | Controls whether the header should show icons for the column types | | `sortBy` | [TableSortByFieldState](#tablesortbyfieldstate)[] | No | | Used to control row sorting | -======= -| Property | Type | Required | Description | -|-----------------|---------------------------------------------------|----------|--------------------------------------------------------------------------------------| -| `frameIndex` | number | **Yes** | Represents the index of the selected frame Default: `0`. | -| `showHeader` | boolean | **Yes** | Controls whether the panel should show the header Default: `true`. | -| `cellHeight` | string | No | Height of a table cell
    Possible values are: `sm`, `md`, `lg`. | -| `footer` | [object](#footer) | No | Controls footer options Default: `map[countRows:false reducer:[] show:false]`. | -| `showRowNums` | boolean | No | Controls whether the columns should be numbered Default: `false`. | -| `showTypeIcons` | boolean | No | Controls whether the header should show icons for the column types Default: `false`. | -| `sortBy` | [TableSortByFieldState](#tablesortbyfieldstate)[] | No | Used to control row sorting | ->>>>>>> main ### TableSortByFieldState diff --git a/docs/sources/developers/kinds/composable/timeseriespanelcfg/schema-reference.md b/docs/sources/developers/kinds/composable/timeseriespanelcfg/schema-reference.md new file mode 100644 index 00000000000..4e1a7d041ae --- /dev/null +++ b/docs/sources/developers/kinds/composable/timeseriespanelcfg/schema-reference.md @@ -0,0 +1,224 @@ +--- +keywords: + - grafana + - schema +title: TimeSeriesPanelCfg kind +--- +> Both documentation generation and kinds schemas are in active development and subject to change without prior notice. + +## TimeSeriesPanelCfg + +#### Maturity: [merged](../../../maturity/#merged) +#### Version: 0.0 + + + +| Property | Type | Required | Default | Description | +|--------------------|---------------------------------------|----------|---------|-------------| +| `PanelFieldConfig` | [GraphFieldConfig](#graphfieldconfig) | **Yes** | | TODO docs | +| `PanelOptions` | [object](#paneloptions) | **Yes** | | | + +### GraphFieldConfig + +TODO docs + +It extends [LineConfig](#lineconfig) and [FillConfig](#fillconfig) and [PointsConfig](#pointsconfig) and [AxisConfig](#axisconfig) and [BarConfig](#barconfig) and [StackableFieldConfig](#stackablefieldconfig) and [HideableFieldConfig](#hideablefieldconfig). + +| Property | Type | Required | Default | Description | +|---------------------|-----------------------------------------------------------|----------|---------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `axisCenteredZero` | boolean | No | | *(Inherited from [AxisConfig](#axisconfig))* | +| `axisColorMode` | string | No | | *(Inherited from [AxisConfig](#axisconfig))*
    TODO docs
    Possible values are: `text`, `series`. | +| `axisGridShow` | boolean | No | | *(Inherited from [AxisConfig](#axisconfig))* | +| `axisLabel` | string | No | | *(Inherited from [AxisConfig](#axisconfig))* | +| `axisPlacement` | string | No | | *(Inherited from [AxisConfig](#axisconfig))*
    TODO docs
    Possible values are: `auto`, `top`, `right`, `bottom`, `left`, `hidden`. | +| `axisSoftMax` | number | No | | *(Inherited from [AxisConfig](#axisconfig))* | +| `axisSoftMin` | number | No | | *(Inherited from [AxisConfig](#axisconfig))* | +| `axisWidth` | number | No | | *(Inherited from [AxisConfig](#axisconfig))* | +| `barAlignment` | integer | No | | *(Inherited from [BarConfig](#barconfig))*
    TODO docs
    Possible values are: `-1`, `0`, `1`. | +| `barMaxWidth` | number | No | | *(Inherited from [BarConfig](#barconfig))* | +| `barWidthFactor` | number | No | | *(Inherited from [BarConfig](#barconfig))* | +| `drawStyle` | string | No | | TODO docs
    Possible values are: `line`, `bars`, `points`. | +| `fillBelowTo` | string | No | | *(Inherited from [FillConfig](#fillconfig))* | +| `fillColor` | string | No | | *(Inherited from [FillConfig](#fillconfig))* | +| `fillOpacity` | number | No | | *(Inherited from [FillConfig](#fillconfig))* | +| `gradientMode` | string | No | | TODO docs
    Possible values are: `none`, `opacity`, `hue`, `scheme`. | +| `hideFrom` | [HideSeriesConfig](#hideseriesconfig) | No | | *(Inherited from [HideableFieldConfig](#hideablefieldconfig))*
    TODO docs | +| `lineColor` | string | No | | *(Inherited from [LineConfig](#lineconfig))* | +| `lineInterpolation` | string | No | | *(Inherited from [LineConfig](#lineconfig))*
    TODO docs
    Possible values are: `linear`, `smooth`, `stepBefore`, `stepAfter`. | +| `lineStyle` | [LineStyle](#linestyle) | No | | *(Inherited from [LineConfig](#lineconfig))*
    TODO docs | +| `lineWidth` | number | No | | *(Inherited from [LineConfig](#lineconfig))* | +| `pointColor` | string | No | | *(Inherited from [PointsConfig](#pointsconfig))* | +| `pointSize` | number | No | | *(Inherited from [PointsConfig](#pointsconfig))* | +| `pointSymbol` | string | No | | *(Inherited from [PointsConfig](#pointsconfig))* | +| `scaleDistribution` | [ScaleDistributionConfig](#scaledistributionconfig) | No | | *(Inherited from [AxisConfig](#axisconfig))*
    TODO docs | +| `showPoints` | string | No | | *(Inherited from [PointsConfig](#pointsconfig))*
    TODO docs
    Possible values are: `auto`, `never`, `always`. | +| `spanNulls` | | No | | *(Inherited from [LineConfig](#lineconfig))*
    Indicate if null values should be treated as gaps or connected.
    When the value is a number, it represents the maximum delta in the
    X axis that should be considered connected. For timeseries, this is milliseconds | +| `stacking` | [StackingConfig](#stackingconfig) | No | | *(Inherited from [StackableFieldConfig](#stackablefieldconfig))*
    TODO docs | +| `thresholdsStyle` | [GraphThresholdsStyleConfig](#graphthresholdsstyleconfig) | No | | TODO docs | +| `transform` | string | No | | TODO docs
    Possible values are: `constant`, `negative-Y`. | + +### AxisConfig + +TODO docs + +| Property | Type | Required | Default | Description | +|---------------------|-----------------------------------------------------|----------|---------|----------------------------------------------------------------------------------------| +| `axisCenteredZero` | boolean | No | | | +| `axisColorMode` | string | No | | TODO docs
    Possible values are: `text`, `series`. | +| `axisGridShow` | boolean | No | | | +| `axisLabel` | string | No | | | +| `axisPlacement` | string | No | | TODO docs
    Possible values are: `auto`, `top`, `right`, `bottom`, `left`, `hidden`. | +| `axisSoftMax` | number | No | | | +| `axisSoftMin` | number | No | | | +| `axisWidth` | number | No | | | +| `scaleDistribution` | [ScaleDistributionConfig](#scaledistributionconfig) | No | | TODO docs | + +### ScaleDistributionConfig + +TODO docs + +| Property | Type | Required | Default | Description | +|-------------------|--------|----------|---------|--------------------------------------------------------------------------| +| `type` | string | **Yes** | | TODO docs
    Possible values are: `linear`, `log`, `ordinal`, `symlog`. | +| `linearThreshold` | number | No | | | +| `log` | number | No | | | + +### BarConfig + +TODO docs + +| Property | Type | Required | Default | Description | +|------------------|---------|----------|---------|----------------------------------------------------| +| `barAlignment` | integer | No | | TODO docs
    Possible values are: `-1`, `0`, `1`. | +| `barMaxWidth` | number | No | | | +| `barWidthFactor` | number | No | | | + +### FillConfig + +TODO docs + +| Property | Type | Required | Default | Description | +|---------------|--------|----------|---------|-------------| +| `fillBelowTo` | string | No | | | +| `fillColor` | string | No | | | +| `fillOpacity` | number | No | | | + +### GraphThresholdsStyleConfig + +TODO docs + +| Property | Type | Required | Default | Description | +|----------|--------|----------|---------|-----------------------------------------------------------------------------------------------------------| +| `mode` | string | **Yes** | | TODO docs
    Possible values are: `off`, `line`, `dashed`, `area`, `line+area`, `dashed+area`, `series`. | + +### HideSeriesConfig + +TODO docs + +| Property | Type | Required | Default | Description | +|-----------|---------|----------|---------|-------------| +| `legend` | boolean | **Yes** | | | +| `tooltip` | boolean | **Yes** | | | +| `viz` | boolean | **Yes** | | | + +### HideableFieldConfig + +TODO docs + +| Property | Type | Required | Default | Description | +|------------|---------------------------------------|----------|---------|-------------| +| `hideFrom` | [HideSeriesConfig](#hideseriesconfig) | No | | TODO docs | + +### LineConfig + +TODO docs + +| Property | Type | Required | Default | Description | +|---------------------|-------------------------|----------|---------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `lineColor` | string | No | | | +| `lineInterpolation` | string | No | | TODO docs
    Possible values are: `linear`, `smooth`, `stepBefore`, `stepAfter`. | +| `lineStyle` | [LineStyle](#linestyle) | No | | TODO docs | +| `lineWidth` | number | No | | | +| `spanNulls` | | No | | Indicate if null values should be treated as gaps or connected.
    When the value is a number, it represents the maximum delta in the
    X axis that should be considered connected. For timeseries, this is milliseconds | + +### LineStyle + +TODO docs + +| Property | Type | Required | Default | Description | +|----------|----------|----------|---------|--------------------------------------------------------| +| `dash` | number[] | No | | | +| `fill` | string | No | | Possible values are: `solid`, `dash`, `dot`, `square`. | + +### PointsConfig + +TODO docs + +| Property | Type | Required | Default | Description | +|---------------|--------|----------|---------|---------------------------------------------------------------| +| `pointColor` | string | No | | | +| `pointSize` | number | No | | | +| `pointSymbol` | string | No | | | +| `showPoints` | string | No | | TODO docs
    Possible values are: `auto`, `never`, `always`. | + +### StackableFieldConfig + +TODO docs + +| Property | Type | Required | Default | Description | +|------------|-----------------------------------|----------|---------|-------------| +| `stacking` | [StackingConfig](#stackingconfig) | No | | TODO docs | + +### StackingConfig + +TODO docs + +| Property | Type | Required | Default | Description | +|----------|--------|----------|---------|-----------------------------------------------------------------| +| `group` | string | No | | | +| `mode` | string | No | | TODO docs
    Possible values are: `none`, `normal`, `percent`. | + +### PanelOptions + +It extends [OptionsWithTimezones](#optionswithtimezones). + +| Property | Type | Required | Default | Description | +|------------|-----------------------------------------|----------|---------|------------------------------------------------------------------| +| `legend` | [VizLegendOptions](#vizlegendoptions) | **Yes** | | TODO docs | +| `tooltip` | [VizTooltipOptions](#viztooltipoptions) | **Yes** | | TODO docs | +| `timezone` | string[] | No | | *(Inherited from [OptionsWithTimezones](#optionswithtimezones))* | + +### OptionsWithTimezones + +TODO docs + +| Property | Type | Required | Default | Description | +|------------|----------|----------|---------|-------------| +| `timezone` | string[] | No | | | + +### VizLegendOptions + +TODO docs + +| Property | Type | Required | Default | Description | +|---------------|----------|----------|---------|-----------------------------------------------------------------------------------------------------------------------------------------| +| `calcs` | string[] | **Yes** | | | +| `displayMode` | string | **Yes** | | TODO docs
    Note: "hidden" needs to remain as an option for plugins compatibility
    Possible values are: `list`, `table`, `hidden`. | +| `placement` | string | **Yes** | | TODO docs
    Possible values are: `bottom`, `right`. | +| `showLegend` | boolean | **Yes** | | | +| `asTable` | boolean | No | | | +| `isVisible` | boolean | No | | | +| `sortBy` | string | No | | | +| `sortDesc` | boolean | No | | | +| `width` | number | No | | | + +### VizTooltipOptions + +TODO docs + +| Property | Type | Required | Default | Description | +|----------|--------|----------|---------|---------------------------------------------------------------| +| `mode` | string | **Yes** | | TODO docs
    Possible values are: `single`, `multi`, `none`. | +| `sort` | string | **Yes** | | TODO docs
    Possible values are: `asc`, `desc`, `none`. | + + diff --git a/docs/sources/developers/kinds/composable/trendpanelcfg/schema-reference.md b/docs/sources/developers/kinds/composable/trendpanelcfg/schema-reference.md new file mode 100644 index 00000000000..e1a019b0d5e --- /dev/null +++ b/docs/sources/developers/kinds/composable/trendpanelcfg/schema-reference.md @@ -0,0 +1,216 @@ +--- +keywords: + - grafana + - schema +title: TrendPanelCfg kind +--- +> Both documentation generation and kinds schemas are in active development and subject to change without prior notice. + +## TrendPanelCfg + +#### Maturity: [merged](../../../maturity/#merged) +#### Version: 0.0 + + + +| Property | Type | Required | Default | Description | +|--------------------|---------------------------------------|----------|---------|----------------------------------------------------------------------| +| `PanelFieldConfig` | [GraphFieldConfig](#graphfieldconfig) | **Yes** | | TODO docs | +| `PanelOptions` | [object](#paneloptions) | **Yes** | | Identical to timeseries... except it does not have timezone settings | + +### GraphFieldConfig + +TODO docs + +It extends [LineConfig](#lineconfig) and [FillConfig](#fillconfig) and [PointsConfig](#pointsconfig) and [AxisConfig](#axisconfig) and [BarConfig](#barconfig) and [StackableFieldConfig](#stackablefieldconfig) and [HideableFieldConfig](#hideablefieldconfig). + +| Property | Type | Required | Default | Description | +|---------------------|-----------------------------------------------------------|----------|---------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `axisCenteredZero` | boolean | No | | *(Inherited from [AxisConfig](#axisconfig))* | +| `axisColorMode` | string | No | | *(Inherited from [AxisConfig](#axisconfig))*
    TODO docs
    Possible values are: `text`, `series`. | +| `axisGridShow` | boolean | No | | *(Inherited from [AxisConfig](#axisconfig))* | +| `axisLabel` | string | No | | *(Inherited from [AxisConfig](#axisconfig))* | +| `axisPlacement` | string | No | | *(Inherited from [AxisConfig](#axisconfig))*
    TODO docs
    Possible values are: `auto`, `top`, `right`, `bottom`, `left`, `hidden`. | +| `axisSoftMax` | number | No | | *(Inherited from [AxisConfig](#axisconfig))* | +| `axisSoftMin` | number | No | | *(Inherited from [AxisConfig](#axisconfig))* | +| `axisWidth` | number | No | | *(Inherited from [AxisConfig](#axisconfig))* | +| `barAlignment` | integer | No | | *(Inherited from [BarConfig](#barconfig))*
    TODO docs
    Possible values are: `-1`, `0`, `1`. | +| `barMaxWidth` | number | No | | *(Inherited from [BarConfig](#barconfig))* | +| `barWidthFactor` | number | No | | *(Inherited from [BarConfig](#barconfig))* | +| `drawStyle` | string | No | | TODO docs
    Possible values are: `line`, `bars`, `points`. | +| `fillBelowTo` | string | No | | *(Inherited from [FillConfig](#fillconfig))* | +| `fillColor` | string | No | | *(Inherited from [FillConfig](#fillconfig))* | +| `fillOpacity` | number | No | | *(Inherited from [FillConfig](#fillconfig))* | +| `gradientMode` | string | No | | TODO docs
    Possible values are: `none`, `opacity`, `hue`, `scheme`. | +| `hideFrom` | [HideSeriesConfig](#hideseriesconfig) | No | | *(Inherited from [HideableFieldConfig](#hideablefieldconfig))*
    TODO docs | +| `lineColor` | string | No | | *(Inherited from [LineConfig](#lineconfig))* | +| `lineInterpolation` | string | No | | *(Inherited from [LineConfig](#lineconfig))*
    TODO docs
    Possible values are: `linear`, `smooth`, `stepBefore`, `stepAfter`. | +| `lineStyle` | [LineStyle](#linestyle) | No | | *(Inherited from [LineConfig](#lineconfig))*
    TODO docs | +| `lineWidth` | number | No | | *(Inherited from [LineConfig](#lineconfig))* | +| `pointColor` | string | No | | *(Inherited from [PointsConfig](#pointsconfig))* | +| `pointSize` | number | No | | *(Inherited from [PointsConfig](#pointsconfig))* | +| `pointSymbol` | string | No | | *(Inherited from [PointsConfig](#pointsconfig))* | +| `scaleDistribution` | [ScaleDistributionConfig](#scaledistributionconfig) | No | | *(Inherited from [AxisConfig](#axisconfig))*
    TODO docs | +| `showPoints` | string | No | | *(Inherited from [PointsConfig](#pointsconfig))*
    TODO docs
    Possible values are: `auto`, `never`, `always`. | +| `spanNulls` | | No | | *(Inherited from [LineConfig](#lineconfig))*
    Indicate if null values should be treated as gaps or connected.
    When the value is a number, it represents the maximum delta in the
    X axis that should be considered connected. For timeseries, this is milliseconds | +| `stacking` | [StackingConfig](#stackingconfig) | No | | *(Inherited from [StackableFieldConfig](#stackablefieldconfig))*
    TODO docs | +| `thresholdsStyle` | [GraphThresholdsStyleConfig](#graphthresholdsstyleconfig) | No | | TODO docs | +| `transform` | string | No | | TODO docs
    Possible values are: `constant`, `negative-Y`. | + +### AxisConfig + +TODO docs + +| Property | Type | Required | Default | Description | +|---------------------|-----------------------------------------------------|----------|---------|----------------------------------------------------------------------------------------| +| `axisCenteredZero` | boolean | No | | | +| `axisColorMode` | string | No | | TODO docs
    Possible values are: `text`, `series`. | +| `axisGridShow` | boolean | No | | | +| `axisLabel` | string | No | | | +| `axisPlacement` | string | No | | TODO docs
    Possible values are: `auto`, `top`, `right`, `bottom`, `left`, `hidden`. | +| `axisSoftMax` | number | No | | | +| `axisSoftMin` | number | No | | | +| `axisWidth` | number | No | | | +| `scaleDistribution` | [ScaleDistributionConfig](#scaledistributionconfig) | No | | TODO docs | + +### ScaleDistributionConfig + +TODO docs + +| Property | Type | Required | Default | Description | +|-------------------|--------|----------|---------|--------------------------------------------------------------------------| +| `type` | string | **Yes** | | TODO docs
    Possible values are: `linear`, `log`, `ordinal`, `symlog`. | +| `linearThreshold` | number | No | | | +| `log` | number | No | | | + +### BarConfig + +TODO docs + +| Property | Type | Required | Default | Description | +|------------------|---------|----------|---------|----------------------------------------------------| +| `barAlignment` | integer | No | | TODO docs
    Possible values are: `-1`, `0`, `1`. | +| `barMaxWidth` | number | No | | | +| `barWidthFactor` | number | No | | | + +### FillConfig + +TODO docs + +| Property | Type | Required | Default | Description | +|---------------|--------|----------|---------|-------------| +| `fillBelowTo` | string | No | | | +| `fillColor` | string | No | | | +| `fillOpacity` | number | No | | | + +### GraphThresholdsStyleConfig + +TODO docs + +| Property | Type | Required | Default | Description | +|----------|--------|----------|---------|-----------------------------------------------------------------------------------------------------------| +| `mode` | string | **Yes** | | TODO docs
    Possible values are: `off`, `line`, `dashed`, `area`, `line+area`, `dashed+area`, `series`. | + +### HideSeriesConfig + +TODO docs + +| Property | Type | Required | Default | Description | +|-----------|---------|----------|---------|-------------| +| `legend` | boolean | **Yes** | | | +| `tooltip` | boolean | **Yes** | | | +| `viz` | boolean | **Yes** | | | + +### HideableFieldConfig + +TODO docs + +| Property | Type | Required | Default | Description | +|------------|---------------------------------------|----------|---------|-------------| +| `hideFrom` | [HideSeriesConfig](#hideseriesconfig) | No | | TODO docs | + +### LineConfig + +TODO docs + +| Property | Type | Required | Default | Description | +|---------------------|-------------------------|----------|---------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `lineColor` | string | No | | | +| `lineInterpolation` | string | No | | TODO docs
    Possible values are: `linear`, `smooth`, `stepBefore`, `stepAfter`. | +| `lineStyle` | [LineStyle](#linestyle) | No | | TODO docs | +| `lineWidth` | number | No | | | +| `spanNulls` | | No | | Indicate if null values should be treated as gaps or connected.
    When the value is a number, it represents the maximum delta in the
    X axis that should be considered connected. For timeseries, this is milliseconds | + +### LineStyle + +TODO docs + +| Property | Type | Required | Default | Description | +|----------|----------|----------|---------|--------------------------------------------------------| +| `dash` | number[] | No | | | +| `fill` | string | No | | Possible values are: `solid`, `dash`, `dot`, `square`. | + +### PointsConfig + +TODO docs + +| Property | Type | Required | Default | Description | +|---------------|--------|----------|---------|---------------------------------------------------------------| +| `pointColor` | string | No | | | +| `pointSize` | number | No | | | +| `pointSymbol` | string | No | | | +| `showPoints` | string | No | | TODO docs
    Possible values are: `auto`, `never`, `always`. | + +### StackableFieldConfig + +TODO docs + +| Property | Type | Required | Default | Description | +|------------|-----------------------------------|----------|---------|-------------| +| `stacking` | [StackingConfig](#stackingconfig) | No | | TODO docs | + +### StackingConfig + +TODO docs + +| Property | Type | Required | Default | Description | +|----------|--------|----------|---------|-----------------------------------------------------------------| +| `group` | string | No | | | +| `mode` | string | No | | TODO docs
    Possible values are: `none`, `normal`, `percent`. | + +### PanelOptions + +Identical to timeseries... except it does not have timezone settings + +| Property | Type | Required | Default | Description | +|-----------|-----------------------------------------|----------|---------|-------------------------------------------------------| +| `legend` | [VizLegendOptions](#vizlegendoptions) | **Yes** | | TODO docs | +| `tooltip` | [VizTooltipOptions](#viztooltipoptions) | **Yes** | | TODO docs | +| `xField` | string | No | | Name of the x field to use (defaults to first number) | + +### VizLegendOptions + +TODO docs + +| Property | Type | Required | Default | Description | +|---------------|----------|----------|---------|-----------------------------------------------------------------------------------------------------------------------------------------| +| `calcs` | string[] | **Yes** | | | +| `displayMode` | string | **Yes** | | TODO docs
    Note: "hidden" needs to remain as an option for plugins compatibility
    Possible values are: `list`, `table`, `hidden`. | +| `placement` | string | **Yes** | | TODO docs
    Possible values are: `bottom`, `right`. | +| `showLegend` | boolean | **Yes** | | | +| `asTable` | boolean | No | | | +| `isVisible` | boolean | No | | | +| `sortBy` | string | No | | | +| `sortDesc` | boolean | No | | | +| `width` | number | No | | | + +### VizTooltipOptions + +TODO docs + +| Property | Type | Required | Default | Description | +|----------|--------|----------|---------|---------------------------------------------------------------| +| `mode` | string | **Yes** | | TODO docs
    Possible values are: `single`, `multi`, `none`. | +| `sort` | string | **Yes** | | TODO docs
    Possible values are: `asc`, `desc`, `none`. | + + diff --git a/docs/sources/developers/kinds/composable/xychartpanelcfg/schema-reference.md b/docs/sources/developers/kinds/composable/xychartpanelcfg/schema-reference.md index 59c8f980f33..7f00947e8b3 100644 --- a/docs/sources/developers/kinds/composable/xychartpanelcfg/schema-reference.md +++ b/docs/sources/developers/kinds/composable/xychartpanelcfg/schema-reference.md @@ -92,11 +92,11 @@ It extends [ScatterFieldConfig](#scatterfieldconfig). | `hideFrom` | [HideSeriesConfig](#hideseriesconfig) | No | | *(Inherited from [ScatterFieldConfig](#scatterfieldconfig))*
    TODO docs | | `labelValue` | [TextDimensionConfig](#textdimensionconfig) | No | | *(Inherited from [ScatterFieldConfig](#scatterfieldconfig))* | | `label` | string | No | | *(Inherited from [ScatterFieldConfig](#scatterfieldconfig))*
    TODO docs
    Possible values are: `auto`, `never`, `always`. | -| `lineColor` | [ColorDimensionConfig](#colordimensionconfig) | No | | *(Inherited from [ScatterFieldConfig](#scatterfieldconfig))*
    This is actually an empty interface used mainly for naming? | +| `lineColor` | [ColorDimensionConfig](#colordimensionconfig) | No | | *(Inherited from [ScatterFieldConfig](#scatterfieldconfig))* | | `lineStyle` | [LineStyle](#linestyle) | No | | *(Inherited from [ScatterFieldConfig](#scatterfieldconfig))*
    TODO docs | | `lineWidth` | integer | No | | *(Inherited from [ScatterFieldConfig](#scatterfieldconfig))*
    Constraint: `>=0 & <=2147483647`. | | `name` | string | No | | | -| `pointColor` | [ColorDimensionConfig](#colordimensionconfig) | No | | *(Inherited from [ScatterFieldConfig](#scatterfieldconfig))*
    This is actually an empty interface used mainly for naming? | +| `pointColor` | [ColorDimensionConfig](#colordimensionconfig) | No | | *(Inherited from [ScatterFieldConfig](#scatterfieldconfig))* | | `pointSize` | [ScaleDimensionConfig](#scaledimensionconfig) | No | | *(Inherited from [ScatterFieldConfig](#scatterfieldconfig))* | | `scaleDistribution` | [ScaleDistributionConfig](#scaledistributionconfig) | No | | *(Inherited from [ScatterFieldConfig](#scatterfieldconfig))*
    TODO docs | | `show` | string | No | | *(Inherited from [ScatterFieldConfig](#scatterfieldconfig))*
    Possible values are: `points`, `lines`, `points+lines`. | @@ -105,21 +105,18 @@ It extends [ScatterFieldConfig](#scatterfieldconfig). ### ColorDimensionConfig -This is actually an empty interface used mainly for naming? - It extends [BaseDimensionConfig](#basedimensionconfig). -| Property | Type | Required | Default | Description | -|----------|--------|----------|---------|----------------------------------------------------------------| -| `fixed` | | **Yes** | | *(Inherited from [BaseDimensionConfig](#basedimensionconfig))* | -| `field` | string | No | | *(Inherited from [BaseDimensionConfig](#basedimensionconfig))* | +| Property | Type | Required | Default | Description | +|----------|--------|----------|---------|--------------------------------------------------------------------------------------------------------------| +| `field` | string | No | | *(Inherited from [BaseDimensionConfig](#basedimensionconfig))*
    fixed: T -- will be added by each element | +| `fixed` | string | No | | | ### BaseDimensionConfig -| Property | Type | Required | Default | Description | -|----------|--------|----------|---------|-------------| -| `fixed` | | **Yes** | | | -| `field` | string | No | | | +| Property | Type | Required | Default | Description | +|----------|--------|----------|---------|-------------------------------------------| +| `field` | string | No | | fixed: T -- will be added by each element | ### HideSeriesConfig @@ -144,12 +141,13 @@ TODO docs It extends [BaseDimensionConfig](#basedimensionconfig). -| Property | Type | Required | Default | Description | -|----------|---------|----------|---------|----------------------------------------------------------------| -| `fixed` | | **Yes** | | *(Inherited from [BaseDimensionConfig](#basedimensionconfig))* | -| `max` | integer | **Yes** | | | -| `min` | integer | **Yes** | | | -| `field` | string | No | | *(Inherited from [BaseDimensionConfig](#basedimensionconfig))* | +| Property | Type | Required | Default | Description | +|----------|--------|----------|---------|--------------------------------------------------------------------------------------------------------------| +| `max` | number | **Yes** | | | +| `min` | number | **Yes** | | | +| `field` | string | No | | *(Inherited from [BaseDimensionConfig](#basedimensionconfig))*
    fixed: T -- will be added by each element | +| `fixed` | number | No | | | +| `mode` | string | No | | Possible values are: `linear`, `quad`. | ### ScaleDistributionConfig @@ -178,10 +176,10 @@ It extends [HideableFieldConfig](#hideablefieldconfig) and [AxisConfig](#axiscon | `hideFrom` | [HideSeriesConfig](#hideseriesconfig) | No | | *(Inherited from [HideableFieldConfig](#hideablefieldconfig))*
    TODO docs | | `labelValue` | [TextDimensionConfig](#textdimensionconfig) | No | | | | `label` | string | No | | TODO docs
    Possible values are: `auto`, `never`, `always`. | -| `lineColor` | [ColorDimensionConfig](#colordimensionconfig) | No | | This is actually an empty interface used mainly for naming? | +| `lineColor` | [ColorDimensionConfig](#colordimensionconfig) | No | | | | `lineStyle` | [LineStyle](#linestyle) | No | | TODO docs | | `lineWidth` | integer | No | | Constraint: `>=0 & <=2147483647`. | -| `pointColor` | [ColorDimensionConfig](#colordimensionconfig) | No | | This is actually an empty interface used mainly for naming? | +| `pointColor` | [ColorDimensionConfig](#colordimensionconfig) | No | | | | `pointSize` | [ScaleDimensionConfig](#scaledimensionconfig) | No | | | | `scaleDistribution` | [ScaleDistributionConfig](#scaledistributionconfig) | No | | *(Inherited from [AxisConfig](#axisconfig))*
    TODO docs | | `show` | string | No | | Possible values are: `points`, `lines`, `points+lines`. | @@ -214,11 +212,11 @@ TODO docs It extends [BaseDimensionConfig](#basedimensionconfig). -| Property | Type | Required | Default | Description | -|----------|--------|----------|---------|----------------------------------------------------------------| -| `fixed` | | **Yes** | | *(Inherited from [BaseDimensionConfig](#basedimensionconfig))* | -| `mode` | string | **Yes** | | Possible values are: `fixed`, `field`, `template`. | -| `field` | string | No | | *(Inherited from [BaseDimensionConfig](#basedimensionconfig))* | +| Property | Type | Required | Default | Description | +|----------|--------|----------|---------|--------------------------------------------------------------------------------------------------------------| +| `mode` | string | **Yes** | | Possible values are: `fixed`, `field`, `template`. | +| `field` | string | No | | *(Inherited from [BaseDimensionConfig](#basedimensionconfig))*
    fixed: T -- will be added by each element | +| `fixed` | string | No | | | ### XYDimensionConfig diff --git a/public/app/plugins/gen.go b/public/app/plugins/gen.go index ac1191e9fb6..440a23433a9 100644 --- a/public/app/plugins/gen.go +++ b/public/app/plugins/gen.go @@ -20,6 +20,7 @@ import ( "github.com/grafana/grafana/pkg/cuectx" "github.com/grafana/grafana/pkg/plugins/codegen" "github.com/grafana/grafana/pkg/plugins/pfs" + "github.com/grafana/thema" ) var skipPlugins = map[string]bool{ @@ -52,7 +53,7 @@ func main() { codegen.PluginTreeListJenny(), codegen.PluginGoTypesJenny("pkg/tsdb"), codegen.PluginTSTypesJenny("public/app/plugins", adaptToPipeline(corecodegen.TSTypesJenny{})), - kind2pd(corecodegen.DocsJenny( + kind2pd(rt, corecodegen.DocsJenny( filepath.Join("docs", "sources", "developers", "kinds", "composable"), )), ) @@ -89,9 +90,9 @@ func adaptToPipeline(j codejen.OneToOne[corecodegen.SchemaForGen]) codejen.OneTo }) } -func kind2pd(j codejen.OneToOne[kindsys.Kind]) codejen.OneToOne[*pfs.PluginDecl] { +func kind2pd(rt *thema.Runtime, j codejen.OneToOne[kindsys.Kind]) codejen.OneToOne[*pfs.PluginDecl] { return codejen.AdaptOneToOne(j, func(pd *pfs.PluginDecl) kindsys.Kind { - kd, err := kindsys.BindComposable(nil, pd.KindDecl) + kd, err := kindsys.BindComposable(rt, pd.KindDecl) if err != nil { return nil } From 765da9c841361c348d16a92a8cb162bfc6a94f25 Mon Sep 17 00:00:00 2001 From: Ashley Harrison Date: Wed, 10 May 2023 10:51:57 +0100 Subject: [PATCH 099/179] Nested folders: remove behaviour where selecting all children selects parent (#68084) remove behaviour where selecting all children selects parent --- .../browse-dashboards/state/reducers.test.ts | 56 +------------------ .../browse-dashboards/state/reducers.ts | 9 +-- 2 files changed, 4 insertions(+), 61 deletions(-) diff --git a/public/app/features/browse-dashboards/state/reducers.test.ts b/public/app/features/browse-dashboards/state/reducers.test.ts index 1bf0ce0d41a..6d3334347a1 100644 --- a/public/app/features/browse-dashboards/state/reducers.test.ts +++ b/public/app/features/browse-dashboards/state/reducers.test.ts @@ -201,56 +201,6 @@ describe('browse-dashboards reducers', () => { }); }); - it('selects ancestors when all their children are now selected', () => { - const state = createInitialState(); - - const rootDashboard = wellFormedDashboard(1).item; - const parentFolder = wellFormedFolder(2).item; - const childDashboard = wellFormedDashboard(3, {}, { parentUID: parentFolder.uid }).item; - const childFolder = wellFormedFolder(4, {}, { parentUID: parentFolder.uid }).item; - const grandchildDashboard = wellFormedDashboard(5, {}, { parentUID: childFolder.uid }).item; - - state.rootItems = [parentFolder, rootDashboard]; - state.childrenByParentUID[parentFolder.uid] = [childDashboard, childFolder]; - state.childrenByParentUID[childFolder.uid] = [grandchildDashboard]; - - // Selected the deepest grandchild dashboard - setItemSelectionState(state, { - type: 'setItemSelectionState', - payload: { item: grandchildDashboard, isSelected: true }, - }); - - expect(state.selectedItems).toEqual({ - $all: false, - dashboard: { - [grandchildDashboard.uid]: true, - }, - folder: { - [parentFolder.uid]: false, - [childFolder.uid]: true, // is selected because all it's children (grandchildDashboard) is selected - }, - panel: {}, - }); - - setItemSelectionState(state, { - type: 'setItemSelectionState', - payload: { item: childDashboard, isSelected: true }, - }); - - expect(state.selectedItems).toEqual({ - $all: false, - dashboard: { - [childDashboard.uid]: true, - [grandchildDashboard.uid]: true, - }, - folder: { - [parentFolder.uid]: true, // is now selected because we also selected its other child - [childFolder.uid]: true, - }, - panel: {}, - }); - }); - it('selects the $all header checkbox when all descendants are now selected', () => { const state = createInitialState(); @@ -264,16 +214,16 @@ describe('browse-dashboards reducers', () => { state.selectedItems.dashboard = { [rootDashboard.uid]: true, [childDashboardA.uid]: true }; - // Selected the deepest grandchild dashboard + // Selects the root folder setItemSelectionState(state, { type: 'setItemSelectionState', - payload: { item: childDashboardB, isSelected: true }, + payload: { item: rootFolder, isSelected: true }, }); expect(state.selectedItems.$all).toBeTruthy(); }); - it('unselects the $all header checkbox a descendant is unselected', () => { + it('unselects the $all header checkbox if a descendant is unselected', () => { const state = createInitialState(); const rootDashboard = wellFormedDashboard(1).item; diff --git a/public/app/features/browse-dashboards/state/reducers.ts b/public/app/features/browse-dashboards/state/reducers.ts index 3b8f23c9950..f9dde18434e 100644 --- a/public/app/features/browse-dashboards/state/reducers.ts +++ b/public/app/features/browse-dashboards/state/reducers.ts @@ -78,14 +78,7 @@ export function setItemSelectionState( break; } - if (isSelected) { - // If we're selecting an item, check all ancestors and see if all their children are - // now selected and update them appropriately - const children = state.childrenByParentUID[parent.uid]; - - const allChildrenSelected = children?.every((v) => state.selectedItems[v.kind][v.uid]) ?? false; - state.selectedItems[parent.kind][parent.uid] = allChildrenSelected; - } else { + if (!isSelected) { // A folder cannot be selected if any of it's children are unselected state.selectedItems[parent.kind][parent.uid] = false; } From 5145f4d3f19700a57084d321a958cd7a5aa49998 Mon Sep 17 00:00:00 2001 From: bojankezele <79632099+bojankezele@users.noreply.github.com> Date: Wed, 10 May 2023 11:53:38 +0200 Subject: [PATCH 100/179] Update index.md (#67979) removing an extra ':' --- docs/sources/alerting/set-up/configure-alertmanager/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/alerting/set-up/configure-alertmanager/index.md b/docs/sources/alerting/set-up/configure-alertmanager/index.md index fffd215c752..488b188ce9e 100644 --- a/docs/sources/alerting/set-up/configure-alertmanager/index.md +++ b/docs/sources/alerting/set-up/configure-alertmanager/index.md @@ -35,6 +35,6 @@ To add an external Alertmanager, complete the following steps. If you are provisioning your data source, set the flag `handleGrafanaManagedAlerts` in the `jsonData` field to `true` to send Grafana-managed alerts to this Alertmanager. - **Note:**: Prometheus, Grafana Mimir, and Cortex implementations of Alertmanager are supported. For Prometheus, contact points and notification policies are read-only in the Grafana Alerting UI. + **Note:** Prometheus, Grafana Mimir, and Cortex implementations of Alertmanager are supported. For Prometheus, contact points and notification policies are read-only in the Grafana Alerting UI. 1. Click **Save & test**. From 1e8cad625bcecc48ae8bc6eeb0284633fde9be10 Mon Sep 17 00:00:00 2001 From: Horst Gutmann Date: Wed, 10 May 2023 11:54:43 +0200 Subject: [PATCH 101/179] CI: Remove windows backend tests from release pipelines (#68166) Remove windows backend tests from release pipelines These are run in other places so there is no benefit to also include them here. --- .drone.yml | 123 +----------------------------- scripts/drone/events/release.star | 33 ++++---- 2 files changed, 18 insertions(+), 138 deletions(-) diff --git a/.drone.yml b/.drone.yml index dbeddd1e2fd..c48aee5d8b8 100644 --- a/.drone.yml +++ b/.drone.yml @@ -2816,132 +2816,11 @@ volumes: path: /var/run/docker.sock name: docker --- -clone: - retries: 3 -depends_on: [] -environment: - EDITION: oss -image_pull_secrets: -- dockerconfigjson -kind: pipeline -name: release-oss-test-backend-windows -platform: - arch: amd64 - os: windows - version: "1809" -services: [] -steps: -- commands: [] - image: grafana/grafana-ci-windows-test:0.1.0 - name: windows-init -- commands: - - go install github.com/google/wire/cmd/wire@v0.5.0 - - wire gen -tags oss ./pkg/server - depends_on: - - windows-init - image: grafana/grafana-ci-windows-test:0.1.0 - name: wire-install -- commands: - - go test -tags requires_buildifer -short -covermode=atomic -timeout=5m ./pkg/... - depends_on: - - wire-install - failure: ignore - image: grafana/grafana-ci-windows-test:0.1.0 - name: test-backend -trigger: - event: - exclude: - - promote - ref: - exclude: - - refs/tags/*-cloud* - include: - - refs/tags/v* -type: docker -volumes: -- host: - path: //./pipe/docker_engine/ - name: docker ---- -clone: - disable: true -depends_on: [] -environment: - EDITION: enterprise -image_pull_secrets: -- dockerconfigjson -kind: pipeline -name: release-enterprise-test-backend-windows -platform: - arch: amd64 - os: windows - version: "1809" -services: [] -steps: -- commands: - - $$ProgressPreference = "SilentlyContinue" - - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.30/windows/grabpl.exe - -OutFile grabpl.exe - image: grafana/ci-wix:0.1.1 - name: grabpl -- commands: - - git clone "https://$$env:GITHUB_TOKEN@github.com/grafana/grafana-enterprise.git" - - cd grafana-enterprise - - git checkout ${DRONE_TAG} - environment: - GITHUB_TOKEN: - from_secret: github_token - image: grafana/ci-wix:0.1.1 - name: clone -- commands: - - cp -r grafana-enterprise C:\App\grafana-enterprise - - rm -r -force grafana-enterprise - - cp grabpl.exe C:\App\grabpl.exe - - rm -force grabpl.exe - - C:\App\grabpl.exe init-enterprise --github-token $$env:GITHUB_TOKEN C:\App\grafana-enterprise - - cp C:\App\grabpl.exe grabpl.exe - depends_on: - - clone - environment: - GITHUB_TOKEN: - from_secret: github_token - image: grafana/ci-wix:0.1.1 - name: windows-init -- commands: - - go install github.com/google/wire/cmd/wire@v0.5.0 - - wire gen -tags enterprise ./pkg/server - depends_on: - - windows-init - image: grafana/grafana-ci-windows-test:0.1.0 - name: wire-install -- commands: - - go test -tags requires_buildifer -short -covermode=atomic -timeout=5m ./pkg/... - depends_on: - - wire-install - failure: ignore - image: grafana/grafana-ci-windows-test:0.1.0 - name: test-backend -trigger: - event: - exclude: - - promote - ref: - exclude: - - refs/tags/*-cloud* - include: - - refs/tags/v* -type: docker -volumes: -- host: - path: //./pipe/docker_engine/ - name: docker ---- clone: retries: 3 depends_on: - release-oss-build-e2e-publish - release-oss-test-frontend -- release-oss-test-backend-windows environment: EDITION: oss image_pull_secrets: @@ -7343,6 +7222,6 @@ kind: secret name: github_token --- kind: signature -hmac: dc3090ab1f4561e295654e1aa01e61a3fbb8bbe17eb5c935f3e808f545a75b39 +hmac: 3e9459f86eb6cc4301481b61d507a8c27b15309c096f7608db29ced62f571689 ... diff --git a/scripts/drone/events/release.star b/scripts/drone/events/release.star index ee0819dbcb1..94b841e84bc 100644 --- a/scripts/drone/events/release.star +++ b/scripts/drone/events/release.star @@ -218,20 +218,10 @@ def oss_pipelines(ver_mode = ver_mode, trigger = release_trigger): integration_test_steps = [] volumes = [] - windows_pipeline = pipeline( - name = "{}-oss-windows".format(ver_mode), - edition = "oss", - trigger = trigger, - steps = get_windows_steps(edition = "oss", ver_mode = ver_mode), - platform = "windows", - depends_on = [ - "{}-oss-build-e2e-publish".format(ver_mode), - "{}-oss-test-frontend".format(ver_mode), - "{}-oss-test-backend-windows".format(ver_mode), - ], - environment = environment, - ) - + windows_pipeline_dependencies = [ + "{}-oss-build-e2e-publish".format(ver_mode), + "{}-oss-test-frontend".format(ver_mode), + ] pipelines = [ pipeline( name = "{}-oss-build-e2e-publish".format(ver_mode), @@ -244,11 +234,12 @@ def oss_pipelines(ver_mode = ver_mode, trigger = release_trigger): ), test_frontend(trigger, ver_mode), test_backend(trigger, ver_mode), - windows_test_backend(trigger, "oss", ver_mode), - windows_test_backend(trigger, "enterprise", ver_mode), ] if ver_mode not in ("release"): + pipelines.append(windows_test_backend(trigger, "oss", ver_mode)) + pipelines.append(windows_test_backend(trigger, "enterprise", ver_mode)) + windows_pipeline_dependencies.append("{}-oss-test-backend-windows".format(ver_mode)) pipelines.append(pipeline( name = "{}-oss-integration-tests".format(ver_mode), edition = "oss", @@ -266,6 +257,16 @@ def oss_pipelines(ver_mode = ver_mode, trigger = release_trigger): volumes = volumes, )) + windows_pipeline = pipeline( + name = "{}-oss-windows".format(ver_mode), + edition = "oss", + trigger = trigger, + steps = get_windows_steps(edition = "oss", ver_mode = ver_mode), + platform = "windows", + depends_on = windows_pipeline_dependencies, + environment = environment, + ) + pipelines.append(windows_pipeline) return pipelines From 135b0a25aa14ee670f5e9979fba3fff876690fbd Mon Sep 17 00:00:00 2001 From: Ashley Harrison Date: Wed, 10 May 2023 11:13:27 +0100 Subject: [PATCH 102/179] Nested folders: Redo search query when actions complete (#68102) redo search query if searching when actions complete --- .../components/BrowseActions/BrowseActions.tsx | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/public/app/features/browse-dashboards/components/BrowseActions/BrowseActions.tsx b/public/app/features/browse-dashboards/components/BrowseActions/BrowseActions.tsx index 68479753977..07b607d1c7f 100644 --- a/public/app/features/browse-dashboards/components/BrowseActions/BrowseActions.tsx +++ b/public/app/features/browse-dashboards/components/BrowseActions/BrowseActions.tsx @@ -4,6 +4,7 @@ import React from 'react'; import { GrafanaTheme2 } from '@grafana/data'; import { Button, useStyles2 } from '@grafana/ui'; import appEvents from 'app/core/app_events'; +import { useSearchStateManager } from 'app/features/search/state/SearchStateManager'; import { useDispatch, useSelector } from 'app/types'; import { ShowModalReactEvent } from 'app/types/events'; @@ -33,6 +34,8 @@ export function BrowseActions() { const selectedFolders = Object.keys(selectedItems.folder).filter((uid) => selectedItems.folder[uid]); const rootItems = useSelector(rootItemsSelector); const childrenByParentUID = useSelector(childrenByParentUIDSelector); + const [, stateManager] = useSearchStateManager(); + const isSearching = stateManager.hasSearchFilters(); const onActionComplete = (parentsToRefresh: Set) => { dispatch( @@ -40,8 +43,14 @@ export function BrowseActions() { isSelected: false, }) ); - for (const parentUID of parentsToRefresh) { - dispatch(fetchChildren(parentUID)); + if (isSearching) { + // Redo search query + stateManager.doSearchWithDebounce(); + } else { + // Refetch parents + for (const parentUID of parentsToRefresh) { + dispatch(fetchChildren(parentUID)); + } } }; From e059ce9c8aae3f24fef4721de9ba524d4ae20df4 Mon Sep 17 00:00:00 2001 From: Andreas Christou Date: Wed, 10 May 2023 11:27:02 +0100 Subject: [PATCH 103/179] AzureMonitor: Correctly set resource parameter for Logs queries (#68133) * Update to ensure the resources parameter is passed through for logs queries only * Use string slices for resources to ensure order remains the same * Fix lint * clarify test --- .../azure-log-analytics-datasource.go | 26 ++++++++++++------- .../azure-log-analytics-datasource_test.go | 23 ++++++++++++++++ 2 files changed, 39 insertions(+), 10 deletions(-) diff --git a/pkg/tsdb/azuremonitor/loganalytics/azure-log-analytics-datasource.go b/pkg/tsdb/azuremonitor/loganalytics/azure-log-analytics-datasource.go index f48b7dd93a6..3791c3674e8 100644 --- a/pkg/tsdb/azuremonitor/loganalytics/azure-log-analytics-datasource.go +++ b/pkg/tsdb/azuremonitor/loganalytics/azure-log-analytics-datasource.go @@ -162,14 +162,20 @@ func (e *AzureLogAnalyticsDatasource) buildQueries(ctx context.Context, logger l } } - queryString = buildTracesQuery(operationId, queryJSONModel.AzureTraces.TraceTypes, queryJSONModel.AzureTraces.Filters, &resultFormat, resourcesMap) + queryResources := make([]string, 0) + for resource := range resourcesMap { + queryResources = append(queryResources, resource) + } + sort.Strings(queryResources) + + queryString = buildTracesQuery(operationId, queryJSONModel.AzureTraces.TraceTypes, queryJSONModel.AzureTraces.Filters, &resultFormat, queryResources) traceIdVariable := "${__data.fields.traceID}" if operationId == "" { - traceExploreQuery = buildTracesQuery(traceIdVariable, queryJSONModel.AzureTraces.TraceTypes, queryJSONModel.AzureTraces.Filters, &resultFormat, resourcesMap) - traceLogsExploreQuery = buildTracesLogsQuery(traceIdVariable, resourcesMap) + traceExploreQuery = buildTracesQuery(traceIdVariable, queryJSONModel.AzureTraces.TraceTypes, queryJSONModel.AzureTraces.Filters, &resultFormat, queryResources) + traceLogsExploreQuery = buildTracesLogsQuery(traceIdVariable, queryResources) } else { traceExploreQuery = queryString - traceLogsExploreQuery = buildTracesLogsQuery(operationId, resourcesMap) + traceLogsExploreQuery = buildTracesLogsQuery(operationId, queryResources) } traceExploreQuery, err = macros.KqlInterpolate(logger, query, dsInfo, traceExploreQuery, "TimeGenerated") if err != nil { @@ -353,7 +359,7 @@ func (e *AzureLogAnalyticsDatasource) executeQuery(ctx context.Context, logger l ResultFormat *dataquery.AzureMonitorQueryAzureLogAnalyticsResultFormat "json:\"resultFormat,omitempty\"" Workspace *string "json:\"workspace,omitempty\"" }{ - Resources: queryJSONModel.AzureTraces.Resources, + Resources: []string{queryJSONModel.AzureTraces.Resources[0]}, Query: &query.TraceLogsExploreQuery, }, QueryType: &logsQueryType, @@ -405,7 +411,7 @@ func (e *AzureLogAnalyticsDatasource) createRequest(ctx context.Context, logger "query": query.Query, "timespan": timespan, } - if len(query.Resources) > 1 { + if len(query.Resources) > 1 && query.QueryType == string(dataquery.AzureQueryTypeAzureLogAnalytics) { body["workspaces"] = query.Resources } jsonValue, err := json.Marshal(body) @@ -674,7 +680,7 @@ func encodeQuery(rawQuery string) (string, error) { return base64.StdEncoding.EncodeToString(b.Bytes()), nil } -func buildTracesQuery(operationId string, traceTypes []string, filters []types.TracesFilters, resultFormat *string, resources map[string]bool) string { +func buildTracesQuery(operationId string, traceTypes []string, filters []types.TracesFilters, resultFormat *string, resources []string) string { types := traceTypes if len(types) == 0 { types = Tables @@ -696,7 +702,7 @@ func buildTracesQuery(operationId string, traceTypes []string, filters []types.T resourcesQuery := strings.Join(filteredTypes, ",") if len(resources) > 0 { intermediate := make([]string, 0) - for resource := range resources { + for _, resource := range resources { resourceSplit := strings.SplitAfter(resource, "/") resourceName := resourceSplit[len(resourceSplit)-1] for _, table := range filteredTypes { @@ -768,13 +774,13 @@ func buildTracesQuery(operationId string, traceTypes []string, filters []types.T return baseQuery + whereClause + propertiesStaticQuery + errorProperty + propertiesQuery + filtersClause + projectClause } -func buildTracesLogsQuery(operationId string, resources map[string]bool) string { +func buildTracesLogsQuery(operationId string, resources []string) string { types := Tables sort.Strings(types) selectors := "union " + strings.Join(types, ",\n") + "\n" if len(resources) > 0 { intermediate := make([]string, 0) - for resource := range resources { + for _, resource := range resources { resourceSplit := strings.SplitAfter(resource, "/") resourceName := resourceSplit[len(resourceSplit)-1] for _, table := range types { diff --git a/pkg/tsdb/azuremonitor/loganalytics/azure-log-analytics-datasource_test.go b/pkg/tsdb/azuremonitor/loganalytics/azure-log-analytics-datasource_test.go index 28f4127ee93..a587e466fe4 100644 --- a/pkg/tsdb/azuremonitor/loganalytics/azure-log-analytics-datasource_test.go +++ b/pkg/tsdb/azuremonitor/loganalytics/azure-log-analytics-datasource_test.go @@ -1223,6 +1223,7 @@ func TestLogAnalyticsCreateRequest(t *testing.T) { req, err := ds.createRequest(ctx, logger, url, &AzureLogAnalyticsQuery{ Resources: []string{"r1", "r2"}, Query: "Perf", + QueryType: string(dataquery.AzureQueryTypeAzureLogAnalytics), }) require.NoError(t, err) expectedBody := `{"query":"Perf","timespan":"0001-01-01T00:00:00Z/0001-01-01T00:00:00Z","workspaces":["r1","r2"]}` @@ -1240,6 +1241,7 @@ func TestLogAnalyticsCreateRequest(t *testing.T) { req, err := ds.createRequest(ctx, logger, url, &AzureLogAnalyticsQuery{ Resources: []string{"r1", "r2"}, Query: "Perf", + QueryType: string(dataquery.AzureQueryTypeAzureLogAnalytics), TimeRange: backend.TimeRange{ From: from, To: to, @@ -1253,6 +1255,27 @@ func TestLogAnalyticsCreateRequest(t *testing.T) { t.Errorf("Unexpected Body: %v", cmp.Diff(string(body), expectedBody)) } }) + + t.Run("does not pass multiple resources for traces queries", func(t *testing.T) { + ds := AzureLogAnalyticsDatasource{} + from := time.Now() + to := from.Add(3 * time.Hour) + req, err := ds.createRequest(ctx, logger, url, &AzureLogAnalyticsQuery{ + Resources: []string{"r1", "r2"}, + QueryType: string(dataquery.AzureQueryTypeAzureTraces), + TimeRange: backend.TimeRange{ + From: from, + To: to, + }, + }) + require.NoError(t, err) + expectedBody := fmt.Sprintf(`{"query":"","timespan":"%s/%s"}`, from.Format(time.RFC3339), to.Format(time.RFC3339)) + body, err := io.ReadAll(req.Body) + require.NoError(t, err) + if !cmp.Equal(string(body), expectedBody) { + t.Errorf("Unexpected Body: %v", cmp.Diff(string(body), expectedBody)) + } + }) } func Test_executeQueryErrorWithDifferentLogAnalyticsCreds(t *testing.T) { From 0b6ae0d1197303a7afc8397937ec9c5d6243f187 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Wed, 10 May 2023 13:30:50 +0300 Subject: [PATCH 104/179] Settings: Do not hide sensitive values if it's empty (#68088) * Settings: Do not hide sensitive values if it's empty * Fix implementation * Add tests for RedactedValue function --- pkg/setting/setting.go | 4 ++++ pkg/setting/setting_test.go | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go index 4865681deb5..7a3ac78fb8a 100644 --- a/pkg/setting/setting.go +++ b/pkg/setting/setting.go @@ -561,6 +561,10 @@ func ToAbsUrl(relativeUrl string) string { } func RedactedValue(key, value string) string { + if value == "" { + return "" + } + uppercased := strings.ToUpper(key) // Sensitive information: password, secrets etc for _, pattern := range []string{ diff --git a/pkg/setting/setting_test.go b/pkg/setting/setting_test.go index 9ab2dd9cc98..ac93ceff817 100644 --- a/pkg/setting/setting_test.go +++ b/pkg/setting/setting_test.go @@ -782,3 +782,37 @@ func TestAlertingEnabled(t *testing.T) { }) } } + +func TestRedactedValue(t *testing.T) { + testCases := []struct { + desc string + key string + value string + expected string + }{ + { + desc: "non-sensitive key", + key: "admin_user", + value: "admin", + expected: "admin", + }, + { + desc: "sensitive key with non-empty value", + key: "private_key_path", + value: "/path/to/key", + expected: RedactedPassword, + }, + { + desc: "sensitive key with empty value", + key: "private_key_path", + value: "", + expected: "", + }, + } + + for _, tc := range testCases { + t.Run(tc.desc, func(t *testing.T) { + require.Equal(t, tc.expected, RedactedValue(tc.key, tc.value)) + }) + } +} From b99e9668a91669029339bdcfbd0616ff574e1af8 Mon Sep 17 00:00:00 2001 From: Giordano Ricci Date: Wed, 10 May 2023 11:57:08 +0100 Subject: [PATCH 105/179] QueryRow: Make toggle actions screen-readers accessible (#67998) --- e2e/panels-suite/panelEdit_queries.spec.ts | 4 +- .../src/selectors/components.ts | 2 +- .../QueryOperationAction.test.tsx | 61 ++++++++++++++----- .../QueryOperationAction.tsx | 32 ++++++---- .../TransformationOperationRow.tsx | 19 +++--- .../query/components/QueryEditorRow.tsx | 15 +++-- .../query/components/QueryEditorRows.test.tsx | 7 +-- 7 files changed, 93 insertions(+), 47 deletions(-) diff --git a/e2e/panels-suite/panelEdit_queries.spec.ts b/e2e/panels-suite/panelEdit_queries.spec.ts index da1e8ef66b6..5f80d931f05 100644 --- a/e2e/panels-suite/panelEdit_queries.spec.ts +++ b/e2e/panels-suite/panelEdit_queries.spec.ts @@ -65,7 +65,7 @@ e2e.scenario({ }); // Disable row with refId A - e2e.components.QueryEditorRow.actionButton('Disable/enable query').eq(1).should('be.visible').click(); + e2e.components.QueryEditorRow.actionButton('Disable query').eq(1).should('be.visible').click(); expectInspectorResultAndClose((keys) => { const length = keys.length; @@ -73,7 +73,7 @@ e2e.scenario({ }); // Enable row with refId B - e2e.components.QueryEditorRow.actionButton('Disable/enable query').eq(1).should('be.visible').click(); + e2e.components.QueryEditorRow.actionButton('Disable query').eq(1).should('be.visible').click(); expectInspectorResultAndClose((keys) => { const length = keys.length; diff --git a/packages/grafana-e2e-selectors/src/selectors/components.ts b/packages/grafana-e2e-selectors/src/selectors/components.ts index 1e303539e32..7b13bd96fcc 100644 --- a/packages/grafana-e2e-selectors/src/selectors/components.ts +++ b/packages/grafana-e2e-selectors/src/selectors/components.ts @@ -193,7 +193,7 @@ export const Components = { rows: 'Query editor row', }, QueryEditorRow: { - actionButton: (title: string) => `${title} query operation action`, + actionButton: (title: string) => `${title}`, title: (refId: string) => `Query editor row title ${refId}`, container: (refId: string) => `Query editor row ${refId}`, }, diff --git a/public/app/core/components/QueryOperationRow/QueryOperationAction.test.tsx b/public/app/core/components/QueryOperationRow/QueryOperationAction.test.tsx index 7c964e958b1..b7d583a01a2 100644 --- a/public/app/core/components/QueryOperationRow/QueryOperationAction.test.tsx +++ b/public/app/core/components/QueryOperationRow/QueryOperationAction.test.tsx @@ -1,24 +1,24 @@ import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import React from 'react'; +import React, { ComponentPropsWithoutRef } from 'react'; import { selectors } from '@grafana/e2e-selectors'; -import { QueryOperationAction, QueryOperationActionProps } from './QueryOperationAction'; - -const setup = (propOverrides?: Partial) => { - const props: QueryOperationActionProps = { - icon: 'panel-add', - title: 'test', - onClick: jest.fn(), - disabled: false, - ...propOverrides, - }; - - render(); -}; +import { QueryOperationAction, QueryOperationToggleAction } from './QueryOperationAction'; describe('QueryOperationAction tests', () => { + function setup(propOverrides?: Partial>) { + const props: ComponentPropsWithoutRef = { + icon: 'panel-add', + title: 'test', + onClick: jest.fn(), + disabled: false, + ...propOverrides, + }; + + render(); + } + it('should render component', () => { setup(); @@ -51,3 +51,36 @@ describe('QueryOperationAction tests', () => { expect(clickSpy).not.toHaveBeenCalled(); }); }); + +describe('QueryOperationToggleAction', () => { + function setup(active: boolean) { + const props: ComponentPropsWithoutRef = { + icon: 'panel-add', + title: 'test', + onClick: () => {}, + active, + }; + + return render(); + } + + it('should correctly set pressed state', () => { + setup(false); + + expect( + screen.getByRole('button', { + name: selectors.components.QueryEditorRow.actionButton('test'), + pressed: false, + }) + ).toBeInTheDocument(); + + setup(true); + + expect( + screen.getByRole('button', { + name: selectors.components.QueryEditorRow.actionButton('test'), + pressed: true, + }) + ).toBeInTheDocument(); + }); +}); diff --git a/public/app/core/components/QueryOperationRow/QueryOperationAction.tsx b/public/app/core/components/QueryOperationRow/QueryOperationAction.tsx index 5918d6067e9..fe69509ac4a 100644 --- a/public/app/core/components/QueryOperationRow/QueryOperationAction.tsx +++ b/public/app/core/components/QueryOperationRow/QueryOperationAction.tsx @@ -5,33 +5,43 @@ import { GrafanaTheme2 } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; import { IconButton, IconName, useStyles2 } from '@grafana/ui'; -export interface QueryOperationActionProps { +interface BaseQueryOperationActionProps { icon: IconName; title: string; onClick: (e: React.MouseEvent) => void; disabled?: boolean; - active?: boolean; } -export const QueryOperationAction = ({ icon, active, disabled, title, onClick }: QueryOperationActionProps) => { +function BaseQueryOperationAction(props: QueryOperationActionProps | QueryOperationToggleActionProps) { const styles = useStyles2(getStyles); return ( -
    +
    ); -}; +} -QueryOperationAction.displayName = 'QueryOperationAction'; +interface QueryOperationActionProps extends BaseQueryOperationActionProps {} +export function QueryOperationAction(props: QueryOperationActionProps) { + return ; +} + +interface QueryOperationToggleActionProps extends BaseQueryOperationActionProps { + active: boolean; +} +export const QueryOperationToggleAction = (props: QueryOperationToggleActionProps) => { + return ; +}; const getStyles = (theme: GrafanaTheme2) => { return { diff --git a/public/app/features/dashboard/components/TransformationsEditor/TransformationOperationRow.tsx b/public/app/features/dashboard/components/TransformationsEditor/TransformationOperationRow.tsx index fce34b37f12..dd4cde96514 100644 --- a/public/app/features/dashboard/components/TransformationsEditor/TransformationOperationRow.tsx +++ b/public/app/features/dashboard/components/TransformationsEditor/TransformationOperationRow.tsx @@ -5,7 +5,10 @@ import { DataFrame, DataTransformerConfig, TransformerRegistryItem, FrameMatcher import { reportInteraction } from '@grafana/runtime'; import { HorizontalGroup } from '@grafana/ui'; import { OperationRowHelp } from 'app/core/components/QueryOperationRow/OperationRowHelp'; -import { QueryOperationAction } from 'app/core/components/QueryOperationRow/QueryOperationAction'; +import { + QueryOperationAction, + QueryOperationToggleAction, +} from 'app/core/components/QueryOperationRow/QueryOperationAction'; import { QueryOperationRow, QueryOperationRowRenderProps, @@ -37,7 +40,7 @@ export const TransformationOperationRow = ({ }: TransformationOperationRowProps) => { const [showDebug, toggleDebug] = useToggle(false); const [showHelp, toggleHelp] = useToggle(false); - const disabled = configs[index].transformation.disabled; + const disabled = !!configs[index].transformation.disabled; const filter = configs[index].transformation.filter != null; const showFilter = filter || data.length > 1; @@ -85,29 +88,29 @@ export const TransformationOperationRow = ({ return ( {uiConfig.state && } - {showFilter && ( - )} - - onDisableToggle(index), 'disabled', disabled)} active={disabled} diff --git a/public/app/features/query/components/QueryEditorRow.tsx b/public/app/features/query/components/QueryEditorRow.tsx index 7127fef7677..36d3091649a 100644 --- a/public/app/features/query/components/QueryEditorRow.tsx +++ b/public/app/features/query/components/QueryEditorRow.tsx @@ -25,7 +25,10 @@ import { selectors } from '@grafana/e2e-selectors'; import { AngularComponent, getAngularLoader, getDataSourceSrv } from '@grafana/runtime'; import { Badge, ErrorBoundaryAlert, HorizontalGroup } from '@grafana/ui'; import { OperationRowHelp } from 'app/core/components/QueryOperationRow/OperationRowHelp'; -import { QueryOperationAction } from 'app/core/components/QueryOperationRow/QueryOperationAction'; +import { + QueryOperationAction, + QueryOperationToggleAction, +} from 'app/core/components/QueryOperationRow/QueryOperationAction'; import { QueryOperationRow, QueryOperationRowRenderProps, @@ -430,15 +433,15 @@ export class QueryEditorRow extends PureComponent { const { query, hideDisableQuery = false } = this.props; const { hasTextEditMode, datasource, showingHelp } = this.state; - const isDisabled = query.hide; + const isDisabled = !!query.hide; const hasEditorHelp = datasource?.components?.QueryEditorHelp; return ( {hasEditorHelp && ( - extends PureComponent {!hideDisableQuery ? ( - { renderScenario({ onAddQuery, onQueryCopied }); const queryEditorRows = await screen.findAllByTestId('query-editor-row'); queryEditorRows.map(async (childQuery) => { - const duplicateQueryButton = queryByLabelText( - childQuery, - 'Duplicate query query operation action' - ) as HTMLElement; + const duplicateQueryButton = queryByLabelText(childQuery, 'Duplicate query') as HTMLElement; expect(duplicateQueryButton).toBeInTheDocument(); @@ -135,7 +132,7 @@ describe('QueryEditorRows', () => { const queryEditorRows = await screen.findAllByTestId('query-editor-row'); queryEditorRows.map(async (childQuery) => { - const deleteQueryButton = queryByLabelText(childQuery, 'Remove query query operation action') as HTMLElement; + const deleteQueryButton = queryByLabelText(childQuery, 'Remove query') as HTMLElement; expect(deleteQueryButton).toBeInTheDocument(); From 5e25334663931e5d7450cbfce44e2c30d9c28ef6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 10 May 2023 12:27:04 +0100 Subject: [PATCH 106/179] Update React Aria (#63786) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Ashley Harrison --- package.json | 20 +- packages/grafana-ui/package.json | 14 +- yarn.lock | 760 ++++++++++++++----------------- 3 files changed, 364 insertions(+), 430 deletions(-) diff --git a/package.json b/package.json index b3ac921b2f2..ac78d9eb01e 100644 --- a/package.json +++ b/package.json @@ -275,16 +275,16 @@ "@popperjs/core": "2.11.6", "@prometheus-io/lezer-promql": "^0.37.0-rc.1", "@pyroscope/flamegraph": "^0.35.5", - "@react-aria/button": "3.6.1", - "@react-aria/dialog": "3.3.1", - "@react-aria/focus": "3.8.0", - "@react-aria/interactions": "3.11.0", - "@react-aria/menu": "3.6.1", - "@react-aria/overlays": "3.10.1", - "@react-aria/utils": "3.13.1", - "@react-stately/collections": "3.4.1", - "@react-stately/menu": "3.4.1", - "@react-stately/tree": "3.3.1", + "@react-aria/button": "3.7.1", + "@react-aria/dialog": "3.5.1", + "@react-aria/focus": "3.12.0", + "@react-aria/interactions": "3.15.0", + "@react-aria/menu": "3.9.0", + "@react-aria/overlays": "3.14.0", + "@react-aria/utils": "3.16.0", + "@react-stately/collections": "3.7.0", + "@react-stately/menu": "3.5.1", + "@react-stately/tree": "3.6.0", "@reduxjs/toolkit": "1.9.3", "@remix-run/router": "^1.5.0", "@testing-library/react-hooks": "^8.0.1", diff --git a/packages/grafana-ui/package.json b/packages/grafana-ui/package.json index 1acc87faa7e..c412af24671 100644 --- a/packages/grafana-ui/package.json +++ b/packages/grafana-ui/package.json @@ -56,13 +56,13 @@ "@leeoniya/ufuzzy": "1.0.6", "@monaco-editor/react": "4.4.6", "@popperjs/core": "2.11.6", - "@react-aria/button": "3.6.1", - "@react-aria/dialog": "3.3.1", - "@react-aria/focus": "3.8.0", - "@react-aria/menu": "3.6.1", - "@react-aria/overlays": "3.10.1", - "@react-aria/utils": "3.13.1", - "@react-stately/menu": "3.4.1", + "@react-aria/button": "3.7.1", + "@react-aria/dialog": "3.5.1", + "@react-aria/focus": "3.12.0", + "@react-aria/menu": "3.9.0", + "@react-aria/overlays": "3.14.0", + "@react-aria/utils": "3.16.0", + "@react-stately/menu": "3.5.1", "ansicolor": "1.1.100", "calculate-size": "1.1.1", "classnames": "2.3.2", diff --git a/yarn.lock b/yarn.lock index 30e257a576a..a0bd709ba30 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1647,7 +1647,7 @@ __metadata: languageName: node linkType: hard -"@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.10.1, @babel/runtime@npm:^7.11.1, @babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.12.0, @babel/runtime@npm:^7.12.1, @babel/runtime@npm:^7.12.13, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.13.10, @babel/runtime@npm:^7.14.0, @babel/runtime@npm:^7.14.5, @babel/runtime@npm:^7.15.4, @babel/runtime@npm:^7.17.2, @babel/runtime@npm:^7.17.8, @babel/runtime@npm:^7.18.0, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.20.0, @babel/runtime@npm:^7.20.7, @babel/runtime@npm:^7.3.1, @babel/runtime@npm:^7.5.0, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.6.2, @babel/runtime@npm:^7.7.2, @babel/runtime@npm:^7.7.6, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7, @babel/runtime@npm:^7.9.2": +"@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.10.1, @babel/runtime@npm:^7.11.1, @babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.12.0, @babel/runtime@npm:^7.12.1, @babel/runtime@npm:^7.12.13, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.13.10, @babel/runtime@npm:^7.14.0, @babel/runtime@npm:^7.14.5, @babel/runtime@npm:^7.15.4, @babel/runtime@npm:^7.17.2, @babel/runtime@npm:^7.17.8, @babel/runtime@npm:^7.18.0, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.20.0, @babel/runtime@npm:^7.20.7, @babel/runtime@npm:^7.3.1, @babel/runtime@npm:^7.5.0, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.7.2, @babel/runtime@npm:^7.7.6, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7, @babel/runtime@npm:^7.9.2": version: 7.21.0 resolution: "@babel/runtime@npm:7.21.0" dependencies: @@ -3132,13 +3132,13 @@ __metadata: "@mdx-js/react": 1.6.22 "@monaco-editor/react": 4.4.6 "@popperjs/core": 2.11.6 - "@react-aria/button": 3.6.1 - "@react-aria/dialog": 3.3.1 - "@react-aria/focus": 3.8.0 - "@react-aria/menu": 3.6.1 - "@react-aria/overlays": 3.10.1 - "@react-aria/utils": 3.13.1 - "@react-stately/menu": 3.4.1 + "@react-aria/button": 3.7.1 + "@react-aria/dialog": 3.5.1 + "@react-aria/focus": 3.12.0 + "@react-aria/menu": 3.9.0 + "@react-aria/overlays": 3.14.0 + "@react-aria/utils": 3.16.0 + "@react-stately/menu": 3.5.1 "@rollup/plugin-node-resolve": 15.0.1 "@storybook/addon-a11y": 6.5.16 "@storybook/addon-actions": 6.5.16 @@ -3334,40 +3334,40 @@ __metadata: languageName: node linkType: hard -"@internationalized/date@npm:^3.0.1": - version: 3.0.1 - resolution: "@internationalized/date@npm:3.0.1" +"@internationalized/date@npm:^3.2.0": + version: 3.2.0 + resolution: "@internationalized/date@npm:3.2.0" dependencies: - "@babel/runtime": ^7.6.2 - checksum: ff51a00550322a5df3d3051e8ffdf3d7741851149e8ba300883e01402249602e87cc50b27b972753d9af88c5374df83c24adf58cae5e269100cb946a3b12cd56 + "@swc/helpers": ^0.4.14 + checksum: 5267e8f58a22074975daafa20d3014067e46f86e1f477e7fc63afb110d34a63de87dddb81ad6535d5f8803ecd28d20207ba21b03a6d3f4a329875d4acedf3302 languageName: node linkType: hard -"@internationalized/message@npm:^3.0.9": - version: 3.0.9 - resolution: "@internationalized/message@npm:3.0.9" +"@internationalized/message@npm:^3.1.0": + version: 3.1.0 + resolution: "@internationalized/message@npm:3.1.0" dependencies: - "@babel/runtime": ^7.6.2 + "@swc/helpers": ^0.4.14 intl-messageformat: ^10.1.0 - checksum: b3f7f5a8e1d8df99efb3463ca07edb976ecf95d28de19a47d92fb19c093052b1a092aeaa226dc69d07143854bdbeb8519a0ac8ba8c900c4b0f565151d735ca7f + checksum: 4e0be02342938369a384275be0fcc90677af7d710137b247248346c590f9f33314bf4c760a95205388f5c1e95dfaad2603988cdb2fefac06a677d59bee64dd11 languageName: node linkType: hard -"@internationalized/number@npm:^3.1.1": - version: 3.1.1 - resolution: "@internationalized/number@npm:3.1.1" +"@internationalized/number@npm:^3.2.0": + version: 3.2.0 + resolution: "@internationalized/number@npm:3.2.0" dependencies: - "@babel/runtime": ^7.6.2 - checksum: 9979ea1ca7388de75193c9d36f19d928fbcb715d456d153c30cafadd2ce1ceae011f55c966d424f4561ec04de14d3b48b8fe16a9e2737273829a813c4f7203a3 + "@swc/helpers": ^0.4.14 + checksum: 1e61b62a4f763b4327fa5687948792a95eb03b919696c64b27835e6e217462997e1b23d4fc984f45568bcb13174df0db7c0f5177d25fde9824d5a42333fc369a languageName: node linkType: hard -"@internationalized/string@npm:^3.0.0": - version: 3.0.0 - resolution: "@internationalized/string@npm:3.0.0" +"@internationalized/string@npm:^3.1.0": + version: 3.1.0 + resolution: "@internationalized/string@npm:3.1.0" dependencies: - "@babel/runtime": ^7.6.2 - checksum: fc347cf80cd4ee009d1c467dca2c6908a919ad152086bf5e8c1a0aede0383fb317695fc5d82abe033ec90ad62108297130b653b63b9529f2e032999798ae4a81 + "@swc/helpers": ^0.4.14 + checksum: 0a47b1dcc2d75207ff1f7e9ffe300cfec94a3b9f361f309c76dfa0614babb8e48f788c6d23c33637f337b752c458731e495ca9c398eb00756efc229e591b12e9 languageName: node linkType: hard @@ -5815,357 +5815,282 @@ __metadata: languageName: node linkType: hard -"@react-aria/button@npm:3.6.1": - version: 3.6.1 - resolution: "@react-aria/button@npm:3.6.1" +"@react-aria/button@npm:3.7.1": + version: 3.7.1 + resolution: "@react-aria/button@npm:3.7.1" dependencies: - "@babel/runtime": ^7.6.2 - "@react-aria/focus": ^3.8.0 - "@react-aria/interactions": ^3.11.0 - "@react-aria/utils": ^3.13.3 - "@react-stately/toggle": ^3.4.1 - "@react-types/button": ^3.6.1 - "@react-types/shared": ^3.14.1 + "@react-aria/focus": ^3.12.0 + "@react-aria/interactions": ^3.15.0 + "@react-aria/utils": ^3.16.0 + "@react-stately/toggle": ^3.5.1 + "@react-types/button": ^3.7.2 + "@react-types/shared": ^3.18.0 + "@swc/helpers": ^0.4.14 peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - checksum: b7c520e7d7b885314cd3455f7b50cfd47f423740873718a2fed9a5721904cd29673efb210101896464afd9392adfec3cba546d118b8f4e6e84cb9ab7ee4b7018 + checksum: 3bf25222748b841262e76385ca3e862f7229bffab714262950a71e32391bb86564d2729777f00f58e511580ae2f5ab0d34951a2186137340b3640f2db7bdb80a languageName: node linkType: hard -"@react-aria/dialog@npm:3.3.1": - version: 3.3.1 - resolution: "@react-aria/dialog@npm:3.3.1" - dependencies: - "@babel/runtime": ^7.6.2 - "@react-aria/focus": ^3.8.0 - "@react-aria/utils": ^3.13.3 - "@react-stately/overlays": ^3.4.1 - "@react-types/dialog": ^3.4.3 - "@react-types/shared": ^3.14.1 - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - checksum: ba924f07a7974b0f9a90902c8b2a41490536194e38b53dc069b4ba275c4a557f4aa4ab87e22e2b1f69d8aa8ef42ede142bf45273bf3e55ff9a38cd687d0ee235 - languageName: node - linkType: hard - -"@react-aria/focus@npm:3.8.0, @react-aria/focus@npm:^3.8.0": - version: 3.8.0 - resolution: "@react-aria/focus@npm:3.8.0" - dependencies: - "@babel/runtime": ^7.6.2 - "@react-aria/interactions": ^3.11.0 - "@react-aria/utils": ^3.13.3 - "@react-types/shared": ^3.14.1 - clsx: ^1.1.1 - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - checksum: 2250e610c3753d008e01d39bed41d961bf795a4cec8873b76fda0adc3ad48811ae5cad0d2e222cca41c43454666d492e130113533e1609fd3cea8721108863a3 - languageName: node - linkType: hard - -"@react-aria/i18n@npm:^3.6.0": - version: 3.6.0 - resolution: "@react-aria/i18n@npm:3.6.0" - dependencies: - "@babel/runtime": ^7.6.2 - "@internationalized/date": ^3.0.1 - "@internationalized/message": ^3.0.9 - "@internationalized/number": ^3.1.1 - "@internationalized/string": ^3.0.0 - "@react-aria/ssr": ^3.3.0 - "@react-aria/utils": ^3.13.3 - "@react-types/shared": ^3.14.1 - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - checksum: ede9cd611e15fe2975556dfe695bdcb67cbcb8d2dfff7677174f86f1418421491fbbbfd8eab40e724a8db24877d2f980df6e50d26d29d5b3e607ca39b42befc3 - languageName: node - linkType: hard - -"@react-aria/interactions@npm:3.11.0, @react-aria/interactions@npm:^3.11.0": - version: 3.11.0 - resolution: "@react-aria/interactions@npm:3.11.0" - dependencies: - "@babel/runtime": ^7.6.2 - "@react-aria/utils": ^3.13.3 - "@react-types/shared": ^3.14.1 - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - checksum: 668658282c937a12d6c9791025d5a672110f9cfa7452d3178fec56cb4b32682fd4d389d44498d788a8619668bb537ce9a8dcd1a6d2ad9fd25aa778dbc5e62bc9 - languageName: node - linkType: hard - -"@react-aria/menu@npm:3.6.1": - version: 3.6.1 - resolution: "@react-aria/menu@npm:3.6.1" - dependencies: - "@babel/runtime": ^7.6.2 - "@react-aria/i18n": ^3.6.0 - "@react-aria/interactions": ^3.11.0 - "@react-aria/overlays": ^3.10.1 - "@react-aria/selection": ^3.10.1 - "@react-aria/utils": ^3.13.3 - "@react-stately/collections": ^3.4.3 - "@react-stately/menu": ^3.4.1 - "@react-stately/tree": ^3.3.3 - "@react-types/button": ^3.6.1 - "@react-types/menu": ^3.7.1 - "@react-types/shared": ^3.14.1 - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - checksum: a2632174aa2abfdd6a4e01430f543b8b7f68b49eb27d29418468364962f438234d5c06b1c37c8cd33da52d1e9c752bb1df9c9c7e8a3938c962ed25f2a8031661 - languageName: node - linkType: hard - -"@react-aria/overlays@npm:3.10.1, @react-aria/overlays@npm:^3.10.1": - version: 3.10.1 - resolution: "@react-aria/overlays@npm:3.10.1" - dependencies: - "@babel/runtime": ^7.6.2 - "@react-aria/i18n": ^3.6.0 - "@react-aria/interactions": ^3.11.0 - "@react-aria/ssr": ^3.3.0 - "@react-aria/utils": ^3.13.3 - "@react-aria/visually-hidden": ^3.4.1 - "@react-stately/overlays": ^3.4.1 - "@react-types/button": ^3.6.1 - "@react-types/overlays": ^3.6.3 - "@react-types/shared": ^3.14.1 - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - checksum: b83ec155d34a2cfe7c26d4b4bd5b620c3895642521717c99212aa0878fb4716cc42665a7f80b844185ee4ee4f7e1a367b42399724fa769079d46f29b1c7b67ef - languageName: node - linkType: hard - -"@react-aria/selection@npm:^3.10.1": - version: 3.10.1 - resolution: "@react-aria/selection@npm:3.10.1" - dependencies: - "@babel/runtime": ^7.6.2 - "@react-aria/focus": ^3.8.0 - "@react-aria/i18n": ^3.6.0 - "@react-aria/interactions": ^3.11.0 - "@react-aria/utils": ^3.13.3 - "@react-stately/collections": ^3.4.3 - "@react-stately/selection": ^3.10.3 - "@react-types/shared": ^3.14.1 - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - checksum: 10fce36a292c7da796c10cf8f781b5a242528d846af76676ed7bc9468e66a92f7208d433636a9f95947ee845ee6f54df942fbbd66c06658b57f11619d76a57fd - languageName: node - linkType: hard - -"@react-aria/ssr@npm:^3.2.0": - version: 3.2.0 - resolution: "@react-aria/ssr@npm:3.2.0" - dependencies: - "@babel/runtime": ^7.6.2 - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - checksum: 30468b436f9d56636eb3fb89cbbbf861c55ad841e255e529d225a0efd9c23628badaee72c42ab3870b5f17a9d5b5163564a64e9b928565230a090cb4f7de23bf - languageName: node - linkType: hard - -"@react-aria/ssr@npm:^3.3.0": - version: 3.3.0 - resolution: "@react-aria/ssr@npm:3.3.0" - dependencies: - "@babel/runtime": ^7.6.2 - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - checksum: 0b7677ef521c65452460601dce3c264b67baa75ef7c99e9755ea55913765054156b6157c9c42e3d56aba86d1704b8b2aeb7672e4084f2f375fe1ec481e33c8c6 - languageName: node - linkType: hard - -"@react-aria/utils@npm:3.13.1": - version: 3.13.1 - resolution: "@react-aria/utils@npm:3.13.1" - dependencies: - "@babel/runtime": ^7.6.2 - "@react-aria/ssr": ^3.2.0 - "@react-stately/utils": ^3.5.0 - "@react-types/shared": ^3.13.1 - clsx: ^1.1.1 - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - checksum: 378b32809677114cff580fce100e151921a78ffc9ef75e45aaea3dd765c1e73055e73743fb868eb690a3729592cb1ba9d8a008adf39b0f7932d9f98377ddc07f - languageName: node - linkType: hard - -"@react-aria/utils@npm:^3.13.3": - version: 3.13.3 - resolution: "@react-aria/utils@npm:3.13.3" - dependencies: - "@babel/runtime": ^7.6.2 - "@react-aria/ssr": ^3.3.0 - "@react-stately/utils": ^3.5.1 - "@react-types/shared": ^3.14.1 - clsx: ^1.1.1 - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - checksum: b6d87ddb8e1d93b00405473099390c854647d81c0419de53cc4a7f02bdcca6d030776fba9f4b241400af13082bafc820dd5ce05c168e8f5a2c43a1b2660fb2ad - languageName: node - linkType: hard - -"@react-aria/visually-hidden@npm:^3.4.1": - version: 3.4.1 - resolution: "@react-aria/visually-hidden@npm:3.4.1" - dependencies: - "@babel/runtime": ^7.6.2 - "@react-aria/interactions": ^3.11.0 - "@react-aria/utils": ^3.13.3 - "@react-types/shared": ^3.14.1 - clsx: ^1.1.1 - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - checksum: aea61d3ffbc2ac36074227cce1d41847ec250756a822874592d44830124da06b5e2f5b10b0726a38fe4263f19b5bc8fd7d7e141d2c4dc8853411913ff730fd8f - languageName: node - linkType: hard - -"@react-stately/collections@npm:3.4.1, @react-stately/collections@npm:^3.4.1": - version: 3.4.1 - resolution: "@react-stately/collections@npm:3.4.1" - dependencies: - "@babel/runtime": ^7.6.2 - "@react-types/shared": ^3.13.1 - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - checksum: 62ca0e423e56bf93ef853e0e6cccfd6d1a11f72229efa94a45bd26d40041772149fbe61a48fee9a8163013909dd1c177d5a034dd23605aceb0099b808df9bea0 - languageName: node - linkType: hard - -"@react-stately/collections@npm:^3.4.3": - version: 3.4.3 - resolution: "@react-stately/collections@npm:3.4.3" - dependencies: - "@babel/runtime": ^7.6.2 - "@react-types/shared": ^3.14.1 - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - checksum: f9045cdac0b20f7d7464ac37c0402511f7c5a727676d0cfefef74a553247d0dd1c816ea5804aac318d85ea5708599f9c9c2e8bd37165b5c6eec100e27f3832b9 - languageName: node - linkType: hard - -"@react-stately/menu@npm:3.4.1, @react-stately/menu@npm:^3.4.1": - version: 3.4.1 - resolution: "@react-stately/menu@npm:3.4.1" - dependencies: - "@babel/runtime": ^7.6.2 - "@react-stately/overlays": ^3.4.1 - "@react-stately/utils": ^3.5.1 - "@react-types/menu": ^3.7.1 - "@react-types/shared": ^3.14.1 - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - checksum: a944d6e3a3caf400ffc52738ee8d586db6c6846d0ecc009de4bbedc88202f63d6bbddbd3d577f730f98f28404b077676af4c307f4ba09314c79cf56087a5aa8c - languageName: node - linkType: hard - -"@react-stately/overlays@npm:^3.4.1": - version: 3.4.1 - resolution: "@react-stately/overlays@npm:3.4.1" - dependencies: - "@babel/runtime": ^7.6.2 - "@react-stately/utils": ^3.5.1 - "@react-types/overlays": ^3.6.3 - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - checksum: 3e0e8711c55198b75cb23a682530969c997fdd21c280a9a1356327ff3806252a70ef13e4efc7734902edfd58d6c2cc9d2624a37d8394ad44e9d33b09186510e3 - languageName: node - linkType: hard - -"@react-stately/selection@npm:^3.10.1": - version: 3.10.1 - resolution: "@react-stately/selection@npm:3.10.1" - dependencies: - "@babel/runtime": ^7.6.2 - "@react-stately/collections": ^3.4.1 - "@react-stately/utils": ^3.5.0 - "@react-types/shared": ^3.13.1 - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - checksum: b2efb8332f71e537f07340a9152a69b4f48958b75c38c4708456b94bd45b79f39fd434e68da9b41af80fb896e62ae561d6f2832c2ceeafd9fd2709ab283019e2 - languageName: node - linkType: hard - -"@react-stately/selection@npm:^3.10.3": - version: 3.10.3 - resolution: "@react-stately/selection@npm:3.10.3" - dependencies: - "@babel/runtime": ^7.6.2 - "@react-stately/collections": ^3.4.3 - "@react-stately/utils": ^3.5.1 - "@react-types/shared": ^3.14.1 - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - checksum: f65af198fa9199bc6bcf76279e2131b605e3ce449cc61d404de34993c81f499d0aba34916e8e8fd867d01ae60786ea3c3b725f3c73153674812bf29e64c6a531 - languageName: node - linkType: hard - -"@react-stately/toggle@npm:^3.4.1": - version: 3.4.1 - resolution: "@react-stately/toggle@npm:3.4.1" - dependencies: - "@babel/runtime": ^7.6.2 - "@react-stately/utils": ^3.5.1 - "@react-types/checkbox": ^3.3.3 - "@react-types/shared": ^3.14.1 - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - checksum: 6cc297ac5c840aa20a6d304947a4d869b857c9dc522b7e77cf798f1815ebd5e5ae1f00aeb812fa452fbbfada1069e814b9e1aaf2751b747f875f8b88d88c21fe - languageName: node - linkType: hard - -"@react-stately/tree@npm:3.3.1": - version: 3.3.1 - resolution: "@react-stately/tree@npm:3.3.1" - dependencies: - "@babel/runtime": ^7.6.2 - "@react-stately/collections": ^3.4.1 - "@react-stately/selection": ^3.10.1 - "@react-stately/utils": ^3.5.0 - "@react-types/shared": ^3.13.1 - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - checksum: 51af2268226f2827fdbd1735e0d878ffe77f1154cffd9436334d30bb91d0997c8b1f860cc4569c286cb20cbe3ff45020f986a9e2dbb6c67ecc6d35a35c9a3b1e - languageName: node - linkType: hard - -"@react-stately/tree@npm:^3.3.3": - version: 3.3.3 - resolution: "@react-stately/tree@npm:3.3.3" - dependencies: - "@babel/runtime": ^7.6.2 - "@react-stately/collections": ^3.4.3 - "@react-stately/selection": ^3.10.3 - "@react-stately/utils": ^3.5.1 - "@react-types/shared": ^3.14.1 - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - checksum: 4e1a94cb478124a2443e84dbf0160dd3a5298e79478336f07003b8c5fcdb26043c65a94439a17315cf00e7f66bf6fd5e3e6fbcb44bced3352554d8f7be94899a - languageName: node - linkType: hard - -"@react-stately/utils@npm:^3.5.0": - version: 3.5.0 - resolution: "@react-stately/utils@npm:3.5.0" - dependencies: - "@babel/runtime": ^7.6.2 - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - checksum: e948b783a01325a5396c46915508981152fcc02a8738c848342c8ecfbc64bdefc14530616d8ccfb6680fc85f24d0546f3df3686b189a9fbaa86ccbbe29e2fe1b - languageName: node - linkType: hard - -"@react-stately/utils@npm:^3.5.1": +"@react-aria/dialog@npm:3.5.1": version: 3.5.1 - resolution: "@react-stately/utils@npm:3.5.1" + resolution: "@react-aria/dialog@npm:3.5.1" dependencies: - "@babel/runtime": ^7.6.2 + "@react-aria/focus": ^3.12.0 + "@react-aria/overlays": ^3.14.0 + "@react-aria/utils": ^3.16.0 + "@react-stately/overlays": ^3.5.1 + "@react-types/dialog": ^3.5.1 + "@react-types/shared": ^3.18.0 + "@swc/helpers": ^0.4.14 peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - checksum: f748331ae393f97b3e6fcccd37b767358f49229520b9500f82ed4c620bff36ef3c01d4ba9679ac7b9d6d78c5f6e711186c98bd0e6482ec27a6fbf26c5d0aa3cc + checksum: 09b45d2d79635cb56be62d1a376b086e27786361183cc4eed1f394a84e7f0ed28bcbbd8df244be4c20c5325c71bc46b5b4ba36a4500b7984d2dfd1d20624698f + languageName: node + linkType: hard + +"@react-aria/focus@npm:3.12.0, @react-aria/focus@npm:^3.12.0": + version: 3.12.0 + resolution: "@react-aria/focus@npm:3.12.0" + dependencies: + "@react-aria/interactions": ^3.15.0 + "@react-aria/utils": ^3.16.0 + "@react-types/shared": ^3.18.0 + "@swc/helpers": ^0.4.14 + clsx: ^1.1.1 + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: 132ce93c09c027d6c8064d3817983563cf75cfd88792ac081d8fba00668a34e3b9eaf50608627f87c72814e720bd25750da8aa7ad783628298b5d17600210d77 + languageName: node + linkType: hard + +"@react-aria/i18n@npm:^3.7.1": + version: 3.7.1 + resolution: "@react-aria/i18n@npm:3.7.1" + dependencies: + "@internationalized/date": ^3.2.0 + "@internationalized/message": ^3.1.0 + "@internationalized/number": ^3.2.0 + "@internationalized/string": ^3.1.0 + "@react-aria/ssr": ^3.6.0 + "@react-aria/utils": ^3.16.0 + "@react-types/shared": ^3.18.0 + "@swc/helpers": ^0.4.14 + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: 14cc797048425544c1b64ce29ea3f5bcac9a38202a927d04f0947a906dd9953aa30942f41028994cde1a6e6cad304db87410c82f332931bbae280cf26f7fb1a8 + languageName: node + linkType: hard + +"@react-aria/interactions@npm:3.15.0, @react-aria/interactions@npm:^3.15.0": + version: 3.15.0 + resolution: "@react-aria/interactions@npm:3.15.0" + dependencies: + "@react-aria/ssr": ^3.6.0 + "@react-aria/utils": ^3.16.0 + "@react-types/shared": ^3.18.0 + "@swc/helpers": ^0.4.14 + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: ba3fa1f7130a2bfcb9dfb7978617f52f084958d76dc2418b3d176cae2b130d0549881948be5fdc47447e9b7a999be54ab19ac674293a0f6e729db40f94db9372 + languageName: node + linkType: hard + +"@react-aria/menu@npm:3.9.0": + version: 3.9.0 + resolution: "@react-aria/menu@npm:3.9.0" + dependencies: + "@react-aria/i18n": ^3.7.1 + "@react-aria/interactions": ^3.15.0 + "@react-aria/overlays": ^3.14.0 + "@react-aria/selection": ^3.14.0 + "@react-aria/utils": ^3.16.0 + "@react-stately/collections": ^3.7.0 + "@react-stately/menu": ^3.5.1 + "@react-stately/tree": ^3.6.0 + "@react-types/button": ^3.7.2 + "@react-types/menu": ^3.9.0 + "@react-types/shared": ^3.18.0 + "@swc/helpers": ^0.4.14 + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: b4af432b5db204c7af2371423ba38ff7725825cedc1a7a4df4587fbad304b33c805ffd3916ab2b94512b030573fe5eb6d54e305c7c7cb99b7da0db4d9ed1e503 + languageName: node + linkType: hard + +"@react-aria/overlays@npm:3.14.0, @react-aria/overlays@npm:^3.14.0": + version: 3.14.0 + resolution: "@react-aria/overlays@npm:3.14.0" + dependencies: + "@react-aria/focus": ^3.12.0 + "@react-aria/i18n": ^3.7.1 + "@react-aria/interactions": ^3.15.0 + "@react-aria/ssr": ^3.6.0 + "@react-aria/utils": ^3.16.0 + "@react-aria/visually-hidden": ^3.8.0 + "@react-stately/overlays": ^3.5.1 + "@react-types/button": ^3.7.2 + "@react-types/overlays": ^3.7.1 + "@react-types/shared": ^3.18.0 + "@swc/helpers": ^0.4.14 + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: 0abba8831ae66a11665d70ef6202965d62787de4a6bb7fca0923fec2b2439e9bd68a85314d9dda6cd1d19e3be61bebfd3cee21fcb49bf42a12c721d4f39fe8c6 + languageName: node + linkType: hard + +"@react-aria/selection@npm:^3.14.0": + version: 3.14.0 + resolution: "@react-aria/selection@npm:3.14.0" + dependencies: + "@react-aria/focus": ^3.12.0 + "@react-aria/i18n": ^3.7.1 + "@react-aria/interactions": ^3.15.0 + "@react-aria/utils": ^3.16.0 + "@react-stately/collections": ^3.7.0 + "@react-stately/selection": ^3.13.0 + "@react-types/shared": ^3.18.0 + "@swc/helpers": ^0.4.14 + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: f2ddf43d29ef4501310d68d2961fe8fa48af1247aae51b8b8125f1a8facfba33598d029b4e12355c5e16c3a3b87e1077ade539075521cd554290b867c3d4a829 + languageName: node + linkType: hard + +"@react-aria/ssr@npm:^3.6.0": + version: 3.6.0 + resolution: "@react-aria/ssr@npm:3.6.0" + dependencies: + "@swc/helpers": ^0.4.14 + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: fab5cf0efb6eea28ae27a74a1ae1724536731f6ea556f7e22f1100e809af5a27c7bfcf6898a0b4d880b374e4b11b782aeadb19b34e26ec10e4e75beb820293e1 + languageName: node + linkType: hard + +"@react-aria/utils@npm:3.16.0, @react-aria/utils@npm:^3.16.0": + version: 3.16.0 + resolution: "@react-aria/utils@npm:3.16.0" + dependencies: + "@react-aria/ssr": ^3.6.0 + "@react-stately/utils": ^3.6.0 + "@react-types/shared": ^3.18.0 + "@swc/helpers": ^0.4.14 + clsx: ^1.1.1 + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: e2ad55088d77fad425cb812eef77171cebba128759e12d85dba3bcf57fdb635a52075410238561ded049393744f0e7deac5bd0faf1db3a043663c202012c3a69 + languageName: node + linkType: hard + +"@react-aria/visually-hidden@npm:^3.8.0": + version: 3.8.0 + resolution: "@react-aria/visually-hidden@npm:3.8.0" + dependencies: + "@react-aria/interactions": ^3.15.0 + "@react-aria/utils": ^3.16.0 + "@react-types/shared": ^3.18.0 + "@swc/helpers": ^0.4.14 + clsx: ^1.1.1 + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: f73aeb3df18ae0f033456f451821c9794145e6dc06b48964ebbe0ad87ab09eca606fb6ddaa6d46e90bbd20860a96272126a912e6f1073a7f3b397ca7b3d7b2e2 + languageName: node + linkType: hard + +"@react-stately/collections@npm:3.7.0, @react-stately/collections@npm:^3.7.0": + version: 3.7.0 + resolution: "@react-stately/collections@npm:3.7.0" + dependencies: + "@react-types/shared": ^3.18.0 + "@swc/helpers": ^0.4.14 + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: efd247453321d7512add441276163c556bab02adb7e1852b4785f7fca1169b22fcac810ee236c0f20364bae12077001ec738cae26ce4f0c43189f6e5acd06027 + languageName: node + linkType: hard + +"@react-stately/menu@npm:3.5.1, @react-stately/menu@npm:^3.5.1": + version: 3.5.1 + resolution: "@react-stately/menu@npm:3.5.1" + dependencies: + "@react-stately/overlays": ^3.5.1 + "@react-stately/utils": ^3.6.0 + "@react-types/menu": ^3.9.0 + "@react-types/shared": ^3.18.0 + "@swc/helpers": ^0.4.14 + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: 1ca6566ffddd7f5811b8175f822028ae706bc7bb74691cf4a39b8d059e837fc056c73a614957711dc8092d195b57016794c67bf2fe8e031980a432062fe9c126 + languageName: node + linkType: hard + +"@react-stately/overlays@npm:^3.5.1": + version: 3.5.1 + resolution: "@react-stately/overlays@npm:3.5.1" + dependencies: + "@react-stately/utils": ^3.6.0 + "@react-types/overlays": ^3.7.1 + "@swc/helpers": ^0.4.14 + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: ce6bf1ba6547a5b457a0dd34421f8dbdaddf3346ed60e750ec9c2ff6aa38789249cd02077cc2c21010d3df1390d86752fa4a022870e66075d82f51e9339f4bf1 + languageName: node + linkType: hard + +"@react-stately/selection@npm:^3.13.0": + version: 3.13.0 + resolution: "@react-stately/selection@npm:3.13.0" + dependencies: + "@react-stately/collections": ^3.7.0 + "@react-stately/utils": ^3.6.0 + "@react-types/shared": ^3.18.0 + "@swc/helpers": ^0.4.14 + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: ba33a8374e4d688cb51b0ced7715926fb830a8571118a3e4b4c795b3ba9fc7eec09d26147184984ec5f0c38bd6a9ec756cfa9cc136bac8c7a4dd7fc39d1fb7d3 + languageName: node + linkType: hard + +"@react-stately/toggle@npm:^3.5.1": + version: 3.5.1 + resolution: "@react-stately/toggle@npm:3.5.1" + dependencies: + "@react-stately/utils": ^3.6.0 + "@react-types/checkbox": ^3.4.3 + "@react-types/shared": ^3.18.0 + "@swc/helpers": ^0.4.14 + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: 0908abfb99cef2e8c39cc787c0ef0982bd227f1263d3282050056e5f9ae109fa1c0ab9b28bebca3ec094d8ba57e6763c0fcc10f853b4079440456e90df8d5593 + languageName: node + linkType: hard + +"@react-stately/tree@npm:3.6.0, @react-stately/tree@npm:^3.6.0": + version: 3.6.0 + resolution: "@react-stately/tree@npm:3.6.0" + dependencies: + "@react-stately/collections": ^3.7.0 + "@react-stately/selection": ^3.13.0 + "@react-stately/utils": ^3.6.0 + "@react-types/shared": ^3.18.0 + "@swc/helpers": ^0.4.14 + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: ef1f94d105dda9e64672c9ae739cc978a84de644d828527a35089474d0653e8b11beff92ec0c601feb6daccda6d0da3c7546b34502a56e66fea34c6773260195 + languageName: node + linkType: hard + +"@react-stately/utils@npm:^3.6.0": + version: 3.6.0 + resolution: "@react-stately/utils@npm:3.6.0" + dependencies: + "@swc/helpers": ^0.4.14 + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: d2ff4cfed5555b112ad71b9bc1837abd777d8fa225043c476b7c9417f8b21a0bcddad0d7127e0acdbf4d85dc9a260c9ae97722b4e9507e6243b412c2724c5f54 languageName: node linkType: hard @@ -6180,37 +6105,37 @@ __metadata: languageName: node linkType: hard -"@react-types/button@npm:^3.6.1": - version: 3.6.1 - resolution: "@react-types/button@npm:3.6.1" +"@react-types/button@npm:^3.7.2": + version: 3.7.2 + resolution: "@react-types/button@npm:3.7.2" dependencies: - "@react-types/shared": ^3.14.1 + "@react-types/shared": ^3.18.0 peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - checksum: c9a177a436be81fe26cc3a876e73b708b235a3c713318b9956cabf942476996cb11e59fe614300647a3d96089873c9d7036dda24e83bf7e5a4c2aa836726f0dc + checksum: e41e749148bc34fae930fb7f60102333e49fa88da8623718189404018d1ad288d6fb06237eea37c936d2068b3354b5105eda22a56927626c67d639e009ee4af0 languageName: node linkType: hard -"@react-types/checkbox@npm:^3.3.3": - version: 3.3.3 - resolution: "@react-types/checkbox@npm:3.3.3" - dependencies: - "@react-types/shared": ^3.14.1 - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - checksum: d1da491ff3bf14f894dbeab5ace3a397ead306d2cc4a820d2a653e038a5628495417feb10a4e07c05dcfce208ae9303c35de7e57d1b21a6b59ca1acca11b80d8 - languageName: node - linkType: hard - -"@react-types/dialog@npm:^3.4.3": +"@react-types/checkbox@npm:^3.4.3": version: 3.4.3 - resolution: "@react-types/dialog@npm:3.4.3" + resolution: "@react-types/checkbox@npm:3.4.3" dependencies: - "@react-types/overlays": ^3.6.3 - "@react-types/shared": ^3.14.1 + "@react-types/shared": ^3.18.0 peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - checksum: 96946e273cfb81d0d536e6f4077e8a1ca19a5a9cb3ae2e26e0993f15650726584574dffc1af1374f4ab16e8940d82e275d44f8d54719da72b6584ff85867197b + checksum: 7b39cc56392d96ba8804228ac72859bf6ea6d35e8a32562bcca88bb705e012e9ce5af14a8027a4d3c6f440d2fdfa4014eabc33d551547b23a9a9a399c5af5331 + languageName: node + linkType: hard + +"@react-types/dialog@npm:^3.5.1": + version: 3.5.1 + resolution: "@react-types/dialog@npm:3.5.1" + dependencies: + "@react-types/overlays": ^3.7.1 + "@react-types/shared": ^3.18.0 + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: 06bc0425c280dc52b18c2569b81473c32d1950b7f7db5430b53bee60ed69f94aecad36f05d81dc3151bf0e9662b39c3149c3a1f9e77eb22c119500a616cb88d8 languageName: node linkType: hard @@ -6226,15 +6151,15 @@ __metadata: languageName: node linkType: hard -"@react-types/menu@npm:^3.7.1": - version: 3.7.1 - resolution: "@react-types/menu@npm:3.7.1" +"@react-types/menu@npm:^3.9.0": + version: 3.9.0 + resolution: "@react-types/menu@npm:3.9.0" dependencies: - "@react-types/overlays": ^3.6.3 - "@react-types/shared": ^3.14.1 + "@react-types/overlays": ^3.7.1 + "@react-types/shared": ^3.18.0 peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - checksum: 349443d1bd23bf64a9af57bef57d8ebfebfc6e82dbcef5cfd8ba778afc998f8dc3cebaae80728e6017b0d12b9e5aaea783254df36dd1b82a048b9e3c0e095795 + checksum: 9bcbad3bed295f252f9684b1ec4692c398740cf1520c84688aba24ebd2d9c89945a469413a043b6abba8bf0ceeb291571be8c17c80be9b4b8e89fdf07ba5bbef languageName: node linkType: hard @@ -6249,14 +6174,14 @@ __metadata: languageName: node linkType: hard -"@react-types/overlays@npm:^3.6.3": - version: 3.6.3 - resolution: "@react-types/overlays@npm:3.6.3" +"@react-types/overlays@npm:^3.7.1": + version: 3.7.1 + resolution: "@react-types/overlays@npm:3.7.1" dependencies: - "@react-types/shared": ^3.14.1 + "@react-types/shared": ^3.18.0 peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - checksum: 8688db82adeda13e922f9805a5c9bd9f64e97e91c0ebf32409964e9d661828a4bb31907551dcdcd611807efa9824ff78aa8cb2ee4b0acfab001cbff5572336d4 + checksum: fa3a4e59c925042226d06db049347c2e14c8ab8216560088dc8e6e0a4bfe5f522786bea3e13d3ecbd7f2a885a214eaef2178f9d412f40277a3a054f91562308b languageName: node linkType: hard @@ -6269,7 +6194,7 @@ __metadata: languageName: node linkType: hard -"@react-types/shared@npm:^3.13.1, @react-types/shared@npm:^3.14.1, @react-types/shared@npm:^3.15.0": +"@react-types/shared@npm:^3.15.0": version: 3.15.0 resolution: "@react-types/shared@npm:3.15.0" peerDependencies: @@ -6287,6 +6212,15 @@ __metadata: languageName: node linkType: hard +"@react-types/shared@npm:^3.18.0": + version: 3.18.0 + resolution: "@react-types/shared@npm:3.18.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: 8fc56701e91f4f943f5b7c7cf968f103527c8dd5803430bd46e9210c24d3b410132fb3ded63841cf3c5cb20558385746b2705ba61204ff425c30fb1f0a64e342 + languageName: node + linkType: hard + "@reduxjs/toolkit@npm:1.9.3": version: 1.9.3 resolution: "@reduxjs/toolkit@npm:1.9.3" @@ -8106,7 +8040,7 @@ __metadata: languageName: node linkType: hard -"@swc/helpers@npm:0.4.14": +"@swc/helpers@npm:0.4.14, @swc/helpers@npm:^0.4.14": version: 0.4.14 resolution: "@swc/helpers@npm:0.4.14" dependencies: @@ -18687,16 +18621,16 @@ __metadata: "@popperjs/core": 2.11.6 "@prometheus-io/lezer-promql": ^0.37.0-rc.1 "@pyroscope/flamegraph": ^0.35.5 - "@react-aria/button": 3.6.1 - "@react-aria/dialog": 3.3.1 - "@react-aria/focus": 3.8.0 - "@react-aria/interactions": 3.11.0 - "@react-aria/menu": 3.6.1 - "@react-aria/overlays": 3.10.1 - "@react-aria/utils": 3.13.1 - "@react-stately/collections": 3.4.1 - "@react-stately/menu": 3.4.1 - "@react-stately/tree": 3.3.1 + "@react-aria/button": 3.7.1 + "@react-aria/dialog": 3.5.1 + "@react-aria/focus": 3.12.0 + "@react-aria/interactions": 3.15.0 + "@react-aria/menu": 3.9.0 + "@react-aria/overlays": 3.14.0 + "@react-aria/utils": 3.16.0 + "@react-stately/collections": 3.7.0 + "@react-stately/menu": 3.5.1 + "@react-stately/tree": 3.6.0 "@react-types/button": 3.7.1 "@react-types/menu": 3.7.2 "@react-types/overlays": 3.6.4 From b0e681c3391887fc279eb2147fe834707efae623 Mon Sep 17 00:00:00 2001 From: Emil Tullstedt Date: Wed, 10 May 2023 13:47:35 +0200 Subject: [PATCH 107/179] Chore: Update target in go.mod to 1.20 and golangci-lint to v1.52.2 (#67102) --- .bingo/Variables.mk | 6 +- .bingo/golangci-lint.mod | 2 +- .bingo/golangci-lint.sum | 71 +++++++++++++++++++++ .bingo/variables.env | 2 +- .github/workflows/codeql-analysis.yml | 2 +- .github/workflows/pr-codeql-analysis-go.yml | 2 +- .golangci.toml | 2 +- go.mod | 2 +- pkg/tsdb/mssql/mssql_test.go | 4 +- pkg/tsdb/mysql/mysql_test.go | 4 +- pkg/tsdb/postgres/postgres_test.go | 4 +- 11 files changed, 86 insertions(+), 15 deletions(-) diff --git a/.bingo/Variables.mk b/.bingo/Variables.mk index 140dd9509ae..083df69b254 100644 --- a/.bingo/Variables.mk +++ b/.bingo/Variables.mk @@ -36,11 +36,11 @@ $(DRONE): $(BINGO_DIR)/drone.mod @# Manual modification: CGo is disabled to make the Drone CLI build successfully on Darwin/arm64 machines. @cd $(BINGO_DIR) && CGO_ENABLED=0 GOWORK=off $(GO) build -mod=mod -modfile=drone.mod -o=$(GOBIN)/drone-v1.5.0 "github.com/drone/drone-cli/drone" -GOLANGCI_LINT := $(GOBIN)/golangci-lint-v1.51.2 +GOLANGCI_LINT := $(GOBIN)/golangci-lint-v1.52.2 $(GOLANGCI_LINT): $(BINGO_DIR)/golangci-lint.mod @# Install binary/ries using Go 1.14+ build command. This is using bwplotka/bingo-controlled, separate go module with pinned dependencies. - @echo "(re)installing $(GOBIN)/golangci-lint-v1.51.2" - @cd $(BINGO_DIR) && GOWORK=off $(GO) build -mod=mod -modfile=golangci-lint.mod -o=$(GOBIN)/golangci-lint-v1.51.2 "github.com/golangci/golangci-lint/cmd/golangci-lint" + @echo "(re)installing $(GOBIN)/golangci-lint-v1.52.2" + @cd $(BINGO_DIR) && GOWORK=off $(GO) build -mod=mod -modfile=golangci-lint.mod -o=$(GOBIN)/golangci-lint-v1.52.2 "github.com/golangci/golangci-lint/cmd/golangci-lint" JB := $(GOBIN)/jb-v0.5.1 $(JB): $(BINGO_DIR)/jb.mod diff --git a/.bingo/golangci-lint.mod b/.bingo/golangci-lint.mod index e77222bf540..f2b0d090b8f 100644 --- a/.bingo/golangci-lint.mod +++ b/.bingo/golangci-lint.mod @@ -2,4 +2,4 @@ module _ // Auto generated by https://github.com/bwplotka/bingo. DO NOT EDIT go 1.20 -require github.com/golangci/golangci-lint v1.51.2 // cmd/golangci-lint +require github.com/golangci/golangci-lint v1.52.2 // cmd/golangci-lint diff --git a/.bingo/golangci-lint.sum b/.bingo/golangci-lint.sum index e5e159ef1d3..dfcfa3a23f5 100644 --- a/.bingo/golangci-lint.sum +++ b/.bingo/golangci-lint.sum @@ -42,10 +42,16 @@ cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3f dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/Abirdcfly/dupword v0.0.9 h1:MxprGjKq3yDBICXDgEEsyGirIXfMYXkLNT/agPsE1tk= github.com/Abirdcfly/dupword v0.0.9/go.mod h1:PzmHVLLZ27MvHSzV7eFmMXSFArWXZPZmfuuziuUrf2g= +github.com/Abirdcfly/dupword v0.0.11 h1:z6v8rMETchZXUIuHxYNmlUAuKuB21PeaSymTed16wgU= +github.com/Abirdcfly/dupword v0.0.11/go.mod h1:wH8mVGuf3CP5fsBTkfWwwwKTjDnVVCxtU8d8rgeVYXA= github.com/Antonboom/errname v0.1.7 h1:mBBDKvEYwPl4WFFNwec1CZO096G6vzK9vvDQzAwkako= github.com/Antonboom/errname v0.1.7/go.mod h1:g0ONh16msHIPgJSGsecu1G/dcF2hlYR/0SddnIAGavU= +github.com/Antonboom/errname v0.1.9 h1:BZDX4r3l4TBZxZ2o2LNrlGxSHran4d1u4veZdoORTT4= +github.com/Antonboom/errname v0.1.9/go.mod h1:nLTcJzevREuAsgTbG85UsuiWpMpAqbKD1HNZ29OzE58= github.com/Antonboom/nilnil v0.1.1 h1:PHhrh5ANKFWRBh7TdYmyyq2gyT2lotnvFvvFbylF81Q= github.com/Antonboom/nilnil v0.1.1/go.mod h1:L1jBqoWM7AOeTD+tSquifKSesRHs4ZdaxvZR+xdJEaI= +github.com/Antonboom/nilnil v0.1.3 h1:6RTbx3d2mcEu3Zwq9TowQpQMVpP75zugwOtqY1RTtcE= +github.com/Antonboom/nilnil v0.1.3/go.mod h1:iOov/7gRcXkeEU+EMGpBu2ORih3iyVEiWjeste1SJm8= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= @@ -69,6 +75,8 @@ github.com/alingse/asasalint v0.0.11 h1:SFwnQXJ49Kx/1GghOFz1XGqHYKp21Kq1nHad/0WQ github.com/alingse/asasalint v0.0.11/go.mod h1:nCaoMhw7a9kSJObvQyVzNTPBDbNpdocqrSP7t/cW5+I= github.com/ashanbrown/forbidigo v1.4.0 h1:spdPbupaSqtWORq1Q4eHBoPBmHtwVyLKwaedbSLc5Sw= github.com/ashanbrown/forbidigo v1.4.0/go.mod h1:IvgwB5Y4fzqSAj/WVXKWigoTkB0dzI2FBbpKWuh7ph8= +github.com/ashanbrown/forbidigo v1.5.1 h1:WXhzLjOlnuDYPYQo/eFlcFMi8X/kLfvWLYu6CSoebis= +github.com/ashanbrown/forbidigo v1.5.1/go.mod h1:Y8j9jy9ZYAEHXdu723cUlraTqbzjKF1MUyfOKL+AjcU= github.com/ashanbrown/makezero v1.1.1 h1:iCQ87C0V0vSyO+M9E/FZYbu65auqH0lnsOkf5FcB28s= github.com/ashanbrown/makezero v1.1.1/go.mod h1:i1bJLCRSCHOcOa9Y6MyF2FTfMZMFdHvxKHxgO5Z1axI= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= @@ -83,8 +91,12 @@ github.com/bombsimon/wsl/v3 v3.4.0 h1:RkSxjT3tmlptwfgEgTgU+KYKLI35p/tviNXNXiL2aN github.com/bombsimon/wsl/v3 v3.4.0/go.mod h1:KkIB+TXkqy6MvK9BDZVbZxKNYsE1/oLRJbIFtf14qqo= github.com/breml/bidichk v0.2.3 h1:qe6ggxpTfA8E75hdjWPZ581sY3a2lnl0IRxLQFelECI= github.com/breml/bidichk v0.2.3/go.mod h1:8u2C6DnAy0g2cEq+k/A2+tr9O1s+vHGxWn0LTc70T2A= +github.com/breml/bidichk v0.2.4 h1:i3yedFWWQ7YzjdZJHnPo9d/xURinSq3OM+gyM43K4/8= +github.com/breml/bidichk v0.2.4/go.mod h1:7Zk0kRFt1LIZxtQdl9W9JwGAcLTTkOs+tN7wuEYGJ3s= github.com/breml/errchkjson v0.3.0 h1:YdDqhfqMT+I1vIxPSas44P+9Z9HzJwCeAzjB8PxP1xw= github.com/breml/errchkjson v0.3.0/go.mod h1:9Cogkyv9gcT8HREpzi3TiqBxCqDzo8awa92zSDFcofU= +github.com/breml/errchkjson v0.3.1 h1:hlIeXuspTyt8Y/UmP5qy1JocGNR00KQHgfaNtRAjoxQ= +github.com/breml/errchkjson v0.3.1/go.mod h1:XroxrzKjdiutFyW3nWhw34VGg7kiMsDQox73yWCGI2U= github.com/butuzov/ireturn v0.1.1 h1:QvrO2QF2+/Cx1WA/vETCIYBKtRjc30vesdoPUNo1EbY= github.com/butuzov/ireturn v0.1.1/go.mod h1:Wh6Zl3IMtTpaIKbmwzqi6olnM9ptYQxxVacMsOEFPoc= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= @@ -93,8 +105,12 @@ github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cb github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/charithe/durationcheck v0.0.9 h1:mPP4ucLrf/rKZiIG/a9IPXHGlh8p4CzgpyTy6EEutYk= github.com/charithe/durationcheck v0.0.9/go.mod h1:SSbRIBVfMjCi/kEB6K65XEA83D6prSM8ap1UCpNKtgg= +github.com/charithe/durationcheck v0.0.10 h1:wgw73BiocdBDQPik+zcEoBG/ob8uyBHf2iyoHGPf5w4= +github.com/charithe/durationcheck v0.0.10/go.mod h1:bCWXb7gYRysD1CU3C+u4ceO49LoGOY1C1L6uouGNreQ= github.com/chavacava/garif v0.0.0-20221024190013-b3ef35877348 h1:cy5GCEZLUCshCGCRRUjxHrDUqkB4l5cuUt3ShEckQEo= github.com/chavacava/garif v0.0.0-20221024190013-b3ef35877348/go.mod h1:f/miWtG3SSuTxKsNK3o58H1xl+XV6ZIfbC6p7lPPB8U= +github.com/chavacava/garif v0.0.0-20230227094218-b8c73b2037b8 h1:W9o46d2kbNL06lq7UNDPV0zYLzkrde/bjIqO02eoll0= +github.com/chavacava/garif v0.0.0-20230227094218-b8c73b2037b8/go.mod h1:gakxgyXaaPkxvLw1XQxNGK4I37ys9iBRzNUx/B7pUCo= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -107,6 +123,8 @@ github.com/curioswitch/go-reassign v0.2.0 h1:G9UZyOcpk/d7Gd6mqYgd8XYWFMw/znxwGDU github.com/curioswitch/go-reassign v0.2.0/go.mod h1:x6OpXuWvgfQaMGks2BZybTngWjT84hqJfKoO8Tt/Roc= github.com/daixiang0/gci v0.9.1 h1:jBrwBmBZTDsGsXiaCTLIe9diotp1X4X64zodFrh7l+c= github.com/daixiang0/gci v0.9.1/go.mod h1:EpVfrztufwVgQRXjnX4zuNinEpLj5OmMjtu/+MB0V0c= +github.com/daixiang0/gci v0.10.1 h1:eheNA3ljF6SxnPD/vE4lCBusVHmV3Rs3dkKvFrJ7MR0= +github.com/daixiang0/gci v0.10.1/go.mod h1:xtHP9N7AHdNvtRNfcx9gwTDfw7FRJx4bZUsiEfiNNAI= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -124,6 +142,8 @@ github.com/ettle/strcase v0.1.1 h1:htFueZyVeE1XNnMEfbqp5r67qAN/4r6ya1ysq8Q+Zcw= github.com/ettle/strcase v0.1.1/go.mod h1:hzDLsPC7/lwKyBOywSHEP89nt2pDgdy+No1NBA9o9VY= github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w= github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg= +github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= +github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4= github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= github.com/firefart/nonamedreturns v1.0.4 h1:abzI1p7mAEPYuR4A+VLKn4eNDOycjYo2phmY9sfv40Y= @@ -134,6 +154,8 @@ github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo= github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA= github.com/go-critic/go-critic v0.6.7 h1:1evPrElnLQ2LZtJfmNDzlieDhjnq36SLgNzisx06oPM= github.com/go-critic/go-critic v0.6.7/go.mod h1:fYZUijFdcnxgx6wPjQA2QEjIRaNCT0gO8bhexy6/QmE= +github.com/go-critic/go-critic v0.7.0 h1:tqbKzB8pqi0NsRZ+1pyU4aweAF7A7QN0Pi4Q02+rYnQ= +github.com/go-critic/go-critic v0.7.0/go.mod h1:moYzd7GdVXE2C2hYTwd7h0CPcqlUeclsyBRwMa38v64= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -148,6 +170,8 @@ github.com/go-toolsmith/astcast v1.1.0 h1:+JN9xZV1A+Re+95pgnMgDboWNVnIMMQXwfBwLR github.com/go-toolsmith/astcast v1.1.0/go.mod h1:qdcuFWeGGS2xX5bLM/c3U9lewg7+Zu4mr+xPwZIB4ZU= github.com/go-toolsmith/astcopy v1.0.3 h1:r0bgSRlMOAgO+BdQnVAcpMSMkrQCnV6ZJmIkrJgcJj0= github.com/go-toolsmith/astcopy v1.0.3/go.mod h1:4TcEdbElGc9twQEYpVo/aieIXfHhiuLh4aLAck6dO7Y= +github.com/go-toolsmith/astcopy v1.1.0 h1:YGwBN0WM+ekI/6SS6+52zLDEf8Yvp3n2seZITCUBt5s= +github.com/go-toolsmith/astcopy v1.1.0/go.mod h1:hXM6gan18VA1T/daUEHCFcYiW8Ai1tIwIzHY6srfEAw= github.com/go-toolsmith/astequal v1.0.2/go.mod h1:9Ai4UglvtR+4up+bAD4+hCj7iTo4m/OXVTSLnCyTAx4= github.com/go-toolsmith/astequal v1.0.3/go.mod h1:9Ai4UglvtR+4up+bAD4+hCj7iTo4m/OXVTSLnCyTAx4= github.com/go-toolsmith/astequal v1.1.0 h1:kHKm1AWqClYn15R0K1KKE4RG614D46n+nqUQ06E1dTw= @@ -206,6 +230,8 @@ github.com/golangci/gofmt v0.0.0-20220901101216-f2edd75033f2 h1:amWTbTGqOZ71ruzr github.com/golangci/gofmt v0.0.0-20220901101216-f2edd75033f2/go.mod h1:9wOXstvyDRshQ9LggQuzBCGysxs3b6Uo/1MvYCR2NMs= github.com/golangci/golangci-lint v1.51.2 h1:yIcsT1X9ZYHdSpeWXRT1ORC/FPGSqDHbHsu9uk4FK7M= github.com/golangci/golangci-lint v1.51.2/go.mod h1:KH9Q7/3glwpYSknxUgUyLlAv46A8fsSKo1hH2wDvkr8= +github.com/golangci/golangci-lint v1.52.2 h1:FrPElUUI5rrHXg1mQ7KxI1MXPAw5lBVskiz7U7a8a1A= +github.com/golangci/golangci-lint v1.52.2/go.mod h1:S5fhC5sHM5kE22/HcATKd1XLWQxX+y7mHj8B5H91Q/0= github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0 h1:MfyDlzVjl1hoaPzPD4Gpb/QgoRfSBR0jdhwGyAWwMSA= github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0/go.mod h1:66R6K6P6VWk9I95jvqGxkqJxVWGFy9XlDwLwVz1RCFg= github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca h1:kNY3/svz5T29MYHubXix4aDDuE3RWHkPvopM/EDv/MA= @@ -228,6 +254,7 @@ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= @@ -301,12 +328,16 @@ github.com/julz/importas v0.1.0 h1:F78HnrsjY3cR7j0etXy5+TU1Zuy7Xt08X/1aJnH5xXY= github.com/julz/importas v0.1.0/go.mod h1:oSFU2R4XK/P7kNBrnL/FEQlDGN1/6WoxXEjSSXO0DV0= github.com/junk1tm/musttag v0.4.5 h1:d+mpJ1vn6WFEVKHwkgJiIedis1u/EawKOuUTygAUtCo= github.com/junk1tm/musttag v0.4.5/go.mod h1:XkcL/9O6RmD88JBXb+I15nYRl9W4ExhgQeCBEhfMC8U= +github.com/junk1tm/musttag v0.5.0 h1:bV1DTdi38Hi4pG4OVWa7Kap0hi0o7EczuK6wQt9zPOM= +github.com/junk1tm/musttag v0.5.0/go.mod h1:PcR7BA+oREQYvHwgjIDmw3exJeds5JzRcvEJTfjrA0M= github.com/kisielk/errcheck v1.6.3 h1:dEKh+GLHcWm2oN34nMvDzn1sqI0i0WxPvrgiJA5JuM8= github.com/kisielk/errcheck v1.6.3/go.mod h1:nXw/i/MfnvRHqXa7XXmQMUB0oNFGuBrNI8d8NLy0LPw= github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kkHAIKE/contextcheck v1.1.3 h1:l4pNvrb8JSwRd51ojtcOxOeHJzHek+MtOyXbaR0uvmw= github.com/kkHAIKE/contextcheck v1.1.3/go.mod h1:PG/cwd6c0705/LM0KTr1acO2gORUxkSVWyLJOFW5qoo= +github.com/kkHAIKE/contextcheck v1.1.4 h1:B6zAaLhOEEcjvUgIYEqystmnFk1Oemn8bvJhbt0GMb8= +github.com/kkHAIKE/contextcheck v1.1.4/go.mod h1:1+i/gWqokIa+dm31mqGLZhZJ7Uh44DJGZVmr6QRBNJg= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= @@ -334,8 +365,12 @@ github.com/maratori/testableexamples v1.0.0 h1:dU5alXRrD8WKSjOUnmJZuzdxWOEQ57+7s github.com/maratori/testableexamples v1.0.0/go.mod h1:4rhjL1n20TUTT4vdh3RDqSizKLyXp7K2u6HgraZCGzE= github.com/maratori/testpackage v1.1.0 h1:GJY4wlzQhuBusMF1oahQCBtUV/AQ/k69IZ68vxaac2Q= github.com/maratori/testpackage v1.1.0/go.mod h1:PeAhzU8qkCwdGEMTEupsHJNlQu2gZopMC6RjbhmHeDc= +github.com/maratori/testpackage v1.1.1 h1:S58XVV5AD7HADMmD0fNnziNHqKvSdDuEKdPD1rNTU04= +github.com/maratori/testpackage v1.1.1/go.mod h1:s4gRK/ym6AMrqpOa/kEbQTV4Q4jb7WeLZzVhVVVOQMc= github.com/matoous/godox v0.0.0-20210227103229-6504466cf951 h1:pWxk9e//NbPwfxat7RXkts09K+dEBJWakUWwICVqYbA= github.com/matoous/godox v0.0.0-20210227103229-6504466cf951/go.mod h1:1BELzlh859Sh1c6+90blK8lbYy0kwQf1bYlBhBysy1s= +github.com/matoous/godox v0.0.0-20230222163458-006bad1f9d26 h1:gWg6ZQ4JhDfJPqlo2srm/LN17lpybq15AryXIRcWYLE= +github.com/matoous/godox v0.0.0-20230222163458-006bad1f9d26/go.mod h1:1BELzlh859Sh1c6+90blK8lbYy0kwQf1bYlBhBysy1s= github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= @@ -350,6 +385,8 @@ github.com/mbilski/exhaustivestruct v1.2.0 h1:wCBmUnSYufAHO6J4AVWY6ff+oxWxsVFrwg github.com/mbilski/exhaustivestruct v1.2.0/go.mod h1:OeTBVxQWoEmB2J2JCHmXWPJ0aksxSUOUy+nvtVEfzXc= github.com/mgechev/revive v1.2.5 h1:UF9AR8pOAuwNmhXj2odp4mxv9Nx2qUIwVz8ZsU+Mbec= github.com/mgechev/revive v1.2.5/go.mod h1:nFOXent79jMTISAfOAasKfy0Z2Ejq0WX7Qn/KAdYopI= +github.com/mgechev/revive v1.3.1 h1:OlQkcH40IB2cGuprTPcjB0iIUddgVZgGmDX3IAMR8D4= +github.com/mgechev/revive v1.3.1/go.mod h1:YlD6TTWl2B8A103R9KWJSPVI9DrEf+oqr15q21Ld+5I= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= @@ -361,6 +398,8 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/moricho/tparallel v0.2.1 h1:95FytivzT6rYzdJLdtfn6m1bfFJylOJK41+lgv/EHf4= github.com/moricho/tparallel v0.2.1/go.mod h1:fXEIZxG2vdfl0ZF8b42f5a78EhjjD5mX8qUplsoSU4k= +github.com/moricho/tparallel v0.3.1 h1:fQKD4U1wRMAYNngDonW5XupoB/ZGJHdpzrWqgyg9krA= +github.com/moricho/tparallel v0.3.1/go.mod h1:leENX2cUv7Sv2qDgdi0D0fCftN8fRC67Bcn8pqzeYNI= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nakabonne/nestif v0.3.1 h1:wm28nZjhQY5HyYPx+weN3Q65k6ilSBxDb8v5S81B81U= @@ -373,6 +412,8 @@ github.com/nishanths/predeclared v0.2.2 h1:V2EPdZPliZymNAn79T8RkNApBjMmVKh5XRpLm github.com/nishanths/predeclared v0.2.2/go.mod h1:RROzoN6TnGQupbC+lqggsOlcgysk3LMK/HI84Mp280c= github.com/nunnatsa/ginkgolinter v0.8.1 h1:/y4o/0hV+ruUHj4xXh89xlFjoaitnI4LnkpuYs02q1c= github.com/nunnatsa/ginkgolinter v0.8.1/go.mod h1:FYYLtszIdmzCH8XMaMPyxPVXZ7VCaIm55bA+gugx+14= +github.com/nunnatsa/ginkgolinter v0.9.0 h1:Sm0zX5QfjJzkeCjEp+t6d3Ha0jwvoDjleP9XCsrEzOA= +github.com/nunnatsa/ginkgolinter v0.9.0/go.mod h1:FHaMLURXP7qImeH6bvxWJUpyH+2tuqe5j4rW1gxJRmI= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw= @@ -393,6 +434,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/polyfloyd/go-errorlint v1.1.0 h1:VKoEFg5yxSgJ2yFPVhxW7oGz+f8/OVcuMeNvcPIi6Eg= github.com/polyfloyd/go-errorlint v1.1.0/go.mod h1:Uss7Bc/izYG0leCMRx3WVlrpqWedSZk7V/FUQW6VJ6U= +github.com/polyfloyd/go-errorlint v1.4.0 h1:b+sQ5HibPIAjEZwtuwU8Wz/u0dMZ7YL+bk+9yWyHVJk= +github.com/polyfloyd/go-errorlint v1.4.0/go.mod h1:qJCkPeBn+0EXkdKTrUCcuFStM2xrDKfxI3MGLXPexUs= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= @@ -421,6 +464,8 @@ github.com/quasilyte/gogrep v0.5.0 h1:eTKODPXbI8ffJMN+W2aE0+oL0z/nh8/5eNdiO34SOA github.com/quasilyte/gogrep v0.5.0/go.mod h1:Cm9lpz9NZjEoL1tgZ2OgeUKPIxL1meE7eo60Z6Sk+Ng= github.com/quasilyte/regex/syntax v0.0.0-20200407221936-30656e2c4a95 h1:L8QM9bvf68pVdQ3bCFZMDmnt9yqcMBro1pC7F+IPYMY= github.com/quasilyte/regex/syntax v0.0.0-20200407221936-30656e2c4a95/go.mod h1:rlzQ04UMyJXu/aOvhd8qT+hvDrFpiwqp8MRXDY9szc0= +github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 h1:TCg2WBOl980XxGFEZSS6KlBGIV0diGdySzxATTWoqaU= +github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727/go.mod h1:rlzQ04UMyJXu/aOvhd8qT+hvDrFpiwqp8MRXDY9szc0= github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 h1:M8mH9eK4OUR4lu7Gd+PU1fV2/qnDNfzT635KRSObncs= github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567/go.mod h1:DWNGW8A4Y+GyBgPuaQJuWiy0XYftx4Xm/y5Jqk9I6VQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= @@ -454,6 +499,8 @@ github.com/sivchari/tenv v1.7.1 h1:PSpuD4bu6fSmtWMxSGWcvqUUgIn7k3yOJhOIzVWn8Ak= github.com/sivchari/tenv v1.7.1/go.mod h1:64yStXKSOxDfX47NlhVwND4dHwfZDdbp2Lyl018Icvg= github.com/sonatard/noctx v0.0.1 h1:VC1Qhl6Oxx9vvWo3UDgrGXYCeKCe3Wbw7qAWL6FrmTY= github.com/sonatard/noctx v0.0.1/go.mod h1:9D2D/EoULe8Yy2joDHJj7bv3sZoq9AaSb8B4lqBjiZI= +github.com/sonatard/noctx v0.0.2 h1:L7Dz4De2zDQhW8S0t+KUjY0MAQJd6SgVwhzNIc4ok00= +github.com/sonatard/noctx v0.0.2/go.mod h1:kzFz+CzWSjQ2OzIm46uJZoXuBpa2+0y3T36U18dWqIo= github.com/sourcegraph/go-diff v0.7.0 h1:9uLlrd5T46OXs5qpp8L/MTltk0zikUGi0sNNyCpA8G0= github.com/sourcegraph/go-diff v0.7.0/go.mod h1:iBszgVvyxdc8SFZ7gm69go2KDdt3ag071iBaWPF6cjs= github.com/spf13/afero v1.8.2 h1:xehSyVa0YnHWsJ49JFljMpg1HX19V6NDZ1fkm1Xznbo= @@ -487,12 +534,16 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/t-yuki/gocover-cobertura v0.0.0-20180217150009-aaee18c8195c h1:+aPplBwWcHBo6q9xrfWdMrT9o4kltkmmvpemgIjep/8= github.com/t-yuki/gocover-cobertura v0.0.0-20180217150009-aaee18c8195c/go.mod h1:SbErYREK7xXdsRiigaQiQkI9McGRzYMvlKYaP3Nimdk= github.com/tdakkota/asciicheck v0.1.1 h1:PKzG7JUTUmVspQTDqtkX9eSiLGossXTybutHwTXuO0A= github.com/tdakkota/asciicheck v0.1.1/go.mod h1:yHp0ai0Z9gUljN3o0xMhYJnH/IcvkdTBOX2fmJ93JEM= +github.com/tdakkota/asciicheck v0.2.0 h1:o8jvnUANo0qXtnslk2d3nMKTFNlOnJjRrNcj0j9qkHM= +github.com/tdakkota/asciicheck v0.2.0/go.mod h1:Qb7Y9EgjCLJGup51gDHFzbI08/gbGhL/UVhYIPWG2rg= github.com/tenntenn/modver v1.0.1/go.mod h1:bePIyQPb7UeioSRkw3Q0XeMhYZSMx9B8ePqg6SAMGH0= github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3/go.mod h1:ON8b8w4BN/kE1EOhwT0o+d62W65a6aPw1nouo9LMgyY= github.com/tetafro/godot v1.4.11 h1:BVoBIqAf/2QdbFmSwAWnaIqDivZdOV0ZRwEm6jivLKw= @@ -501,8 +552,12 @@ github.com/timakin/bodyclose v0.0.0-20221125081123-e39cf3fc478e h1:MV6KaVu/hzByH github.com/timakin/bodyclose v0.0.0-20221125081123-e39cf3fc478e/go.mod h1:27bSVNWSBOHm+qRp1T9qzaIpsWEP6TbUnei/43HK+PQ= github.com/timonwong/loggercheck v0.9.3 h1:ecACo9fNiHxX4/Bc02rW2+kaJIAMAes7qJ7JKxt0EZI= github.com/timonwong/loggercheck v0.9.3/go.mod h1:wUqnk9yAOIKtGA39l1KLE9Iz0QiTocu/YZoOf+OzFdw= +github.com/timonwong/loggercheck v0.9.4 h1:HKKhqrjcVj8sxL7K77beXh0adEm6DLjV/QOGeMXEVi4= +github.com/timonwong/loggercheck v0.9.4/go.mod h1:caz4zlPcgvpEkXgVnAJGowHAMW2NwHaNlpS8xDbVhTg= github.com/tomarrell/wrapcheck/v2 v2.8.0 h1:qDzbir0xmoE+aNxGCPrn+rUSxAX+nG6vREgbbXAR81I= github.com/tomarrell/wrapcheck/v2 v2.8.0/go.mod h1:ao7l5p0aOlUNJKI0qVwB4Yjlqutd0IvAB9Rdwyilxvg= +github.com/tomarrell/wrapcheck/v2 v2.8.1 h1:HxSqDSN0sAt0yJYsrcYVoEeyM4aI9yAm3KQpIXDJRhQ= +github.com/tomarrell/wrapcheck/v2 v2.8.1/go.mod h1:/n2Q3NZ4XFT50ho6Hbxg+RV1uyo2Uow/Vdm9NQcl5SE= github.com/tommy-muehle/go-mnd/v2 v2.5.1 h1:NowYhSdyE/1zwK9QCLeRb6USWdoif80Ie+v+yU8u1Zw= github.com/tommy-muehle/go-mnd/v2 v2.5.1/go.mod h1:WsUAkMJMYww6l/ufffCD3m+P7LEvr8TnZn9lwVDlgzw= github.com/ultraware/funlen v0.0.3 h1:5ylVWm8wsNwH5aWo9438pwvsK0QiqVuUrt9bn7S/iLA= @@ -536,6 +591,8 @@ go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/zap v1.17.0 h1:MTjgFu6ZLKvY6Pvaqk97GlxNBuMpV4Hy/3P6tRGlI2U= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= +go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= +go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -561,6 +618,8 @@ golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2F golang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/exp/typeparams v0.0.0-20230203172020-98cc5a0785f9 h1:6WHiuFL9FNjg8RljAaT7FNUuKDbvMqS1i5cr2OE2sLQ= golang.org/x/exp/typeparams v0.0.0-20230203172020-98cc5a0785f9/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= +golang.org/x/exp/typeparams v0.0.0-20230224173230-c95f2b4c22f2 h1:J74nGeMgeFnYQJN59eFwh06jX/V8g0lB7LWpjSLxtgU= +golang.org/x/exp/typeparams v0.0.0-20230224173230-c95f2b4c22f2/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -592,6 +651,8 @@ golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs= +golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -633,6 +694,7 @@ golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -717,12 +779,15 @@ golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -735,6 +800,8 @@ golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k= golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -815,6 +882,8 @@ golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ= golang.org/x/tools v0.5.0/go.mod h1:N+Kgy78s5I24c24dU8OfWNEotWjutIs8SnJvn5IDq+k= golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4= +golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -939,6 +1008,8 @@ honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9 honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.4.2 h1:6qXr+R5w+ktL5UkwEbPp+fEvfyoMPche6GkOpGHZcLc= honnef.co/go/tools v0.4.2/go.mod h1:36ZgoUOrqOk1GxwHhyryEkq8FQWkUO2xGuSMhUCcdvA= +honnef.co/go/tools v0.4.3 h1:o/n5/K5gXqk8Gozvs2cnL0F2S1/g1vcGCAx2vETjITw= +honnef.co/go/tools v0.4.3/go.mod h1:36ZgoUOrqOk1GxwHhyryEkq8FQWkUO2xGuSMhUCcdvA= mvdan.cc/gofumpt v0.4.0 h1:JVf4NN1mIpHogBj7ABpgOyZc65/UUOkKQFkoURsz4MM= mvdan.cc/gofumpt v0.4.0/go.mod h1:PljLOHDeZqgS8opHRKLzp2It2VBuSdteAgqUfzMTxlQ= mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed h1:WX1yoOaKQfddO/mLzdV4wptyWgoH/6hwLs7QHTixo0I= diff --git a/.bingo/variables.env b/.bingo/variables.env index 394897e5690..d7e32d47a06 100644 --- a/.bingo/variables.env +++ b/.bingo/variables.env @@ -14,7 +14,7 @@ CUE="${GOBIN}/cue-v0.5.0" DRONE="${GOBIN}/drone-v1.5.0" -GOLANGCI_LINT="${GOBIN}/golangci-lint-v1.51.2" +GOLANGCI_LINT="${GOBIN}/golangci-lint-v1.52.2" JB="${GOBIN}/jb-v0.5.1" diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 3fceb0d3545..052944c72bc 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -44,7 +44,7 @@ jobs: name: Set go version uses: actions/setup-go@v3 with: - go-version: '1.19.2' + go-version: '1.20.4' # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.github/workflows/pr-codeql-analysis-go.yml b/.github/workflows/pr-codeql-analysis-go.yml index 16f61aa9e9a..cea7ada3cc1 100644 --- a/.github/workflows/pr-codeql-analysis-go.yml +++ b/.github/workflows/pr-codeql-analysis-go.yml @@ -23,7 +23,7 @@ jobs: - name: Set go version uses: actions/setup-go@v3 with: - go-version: '1.19.2' + go-version: '1.20.4' # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.golangci.toml b/.golangci.toml index 63260853e4b..eaf8ca8acb3 100644 --- a/.golangci.toml +++ b/.golangci.toml @@ -47,7 +47,6 @@ enable = [ "ineffassign", "misspell", "nakedret", - "rowserrcheck", "exportloopref", "staticcheck", "stylecheck", @@ -65,6 +64,7 @@ enable = [ # Disabled linters (might want them later) # "unparam" +# "rowserrcheck" # The linter doesn't detect that both Scan and Close also returns the error message returned by Err. [issues] exclude-use-default = false diff --git a/go.mod b/go.mod index beb2c46400f..7f3b99d2379 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/grafana/grafana -go 1.19 +go 1.20 // Override xorm's outdated go-mssqldb dependency, since we can't upgrade to current xorm (due to breaking changes). // We need a more current go-mssqldb so we get rid of a version of apache/thrift with vulnerabilities. diff --git a/pkg/tsdb/mssql/mssql_test.go b/pkg/tsdb/mssql/mssql_test.go index a0e76ed5f78..83999122a5e 100644 --- a/pkg/tsdb/mssql/mssql_test.go +++ b/pkg/tsdb/mssql/mssql_test.go @@ -399,9 +399,9 @@ func TestMSSQL(t *testing.T) { err = sess.CreateTable(metric_values{}) require.NoError(t, err) - rand.Seed(time.Now().Unix()) + rng := rand.New(rand.NewSource(time.Now().Unix())) rnd := func(min, max int64) int64 { - return rand.Int63n(max-min) + min + return rng.Int63n(max-min) + min } var tInitial time.Time diff --git a/pkg/tsdb/mysql/mysql_test.go b/pkg/tsdb/mysql/mysql_test.go index ef2b61d5ffc..9159d6744dd 100644 --- a/pkg/tsdb/mysql/mysql_test.go +++ b/pkg/tsdb/mysql/mysql_test.go @@ -450,9 +450,9 @@ func TestIntegrationMySQL(t *testing.T) { err = sess.CreateTable(metric_values{}) require.NoError(t, err) - rand.Seed(time.Now().Unix()) + rng := rand.New(rand.NewSource(time.Now().Unix())) rnd := func(min, max int64) int64 { - return rand.Int63n(max-min) + min + return rng.Int63n(max-min) + min } var tInitial time.Time diff --git a/pkg/tsdb/postgres/postgres_test.go b/pkg/tsdb/postgres/postgres_test.go index ecf9405eabf..40d851c890f 100644 --- a/pkg/tsdb/postgres/postgres_test.go +++ b/pkg/tsdb/postgres/postgres_test.go @@ -632,9 +632,9 @@ func TestIntegrationPostgres(t *testing.T) { err := sess.CreateTable(metric_values{}) require.NoError(t, err) - rand.Seed(time.Now().Unix()) + rng := rand.New(rand.NewSource(time.Now().Unix())) rnd := func(min, max int64) int64 { - return rand.Int63n(max-min) + min + return rng.Int63n(max-min) + min } var tInitial time.Time From 62a660c6f6157c6d9339ff09de445042c3ae0545 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Farkas?= Date: Wed, 10 May 2023 13:54:50 +0200 Subject: [PATCH 108/179] devenv:loki faster test-data generation (#68201) --- devenv/docker/blocks/loki/data/data.js | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/devenv/docker/blocks/loki/data/data.js b/devenv/docker/blocks/loki/data/data.js index bc013954726..b08a494ef4e 100644 --- a/devenv/docker/blocks/loki/data/data.js +++ b/devenv/docker/blocks/loki/data/data.js @@ -176,8 +176,8 @@ async function sendOldLogs() { const timestampNs = `${timestampMs}${getRandomNanosecPart()}`; globalCounter += 1; const item = getRandomLogItem(globalCounter) - await lokiSendLogLine(timestampNs, JSON.stringify(item), {place:'moon', ...sharedLabels}); - await lokiSendLogLine(timestampNs, logFmtLine(item), {place:'luna', ...sharedLabels}); + await lokiSendLogLine(timestampNs, JSON.stringify(item), {age:'old', place:'moon', ...sharedLabels}); + await lokiSendLogLine(timestampNs, logFmtLine(item), {age:'old', place:'luna', ...sharedLabels}); }; } @@ -187,18 +187,16 @@ async function sendNewLogs() { const nowMs = new Date().getTime(); const timestampNs = `${nowMs}${getRandomNanosecPart()}`; const item = getRandomLogItem(globalCounter) - await lokiSendLogLine(timestampNs, JSON.stringify(item), {place:'moon', ...sharedLabels}); - await lokiSendLogLine(timestampNs, logFmtLine(item), {place:'luna', ...sharedLabels}); + await lokiSendLogLine(timestampNs, JSON.stringify(item), {age:'new', place:'moon', ...sharedLabels}); + await lokiSendLogLine(timestampNs, logFmtLine(item), {age:'new', place:'luna', ...sharedLabels}); const sleepDuration = 200 + Math.random() * 800; // between 0.2 and 1 seconds await sleep(sleepDuration); } } async function main() { - // first we send logs to build a last-7-days log-data - await sendOldLogs(); - // then keep sending new data forever - await sendNewLogs(); + // we generate both old-logs and new-logs at the same time + await Promise.all([sendOldLogs(), sendNewLogs()]) } // when running in docker, we catch the needed stop-signal, to shutdown fast From 51a5f9f3fdeda19ee19f3724c9ca248521503f91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 10 May 2023 14:07:08 +0200 Subject: [PATCH 109/179] Scenes: Update to 0.7 and update monitoring demo with new form of in-place drilldown (#68123) * Update * Progress * Update * Update --- package.json | 2 +- .../scenes/apps/GrafanaMonitoringApp.tsx | 7 + .../scenes/apps/{scenes.ts => scenes.tsx} | 90 +++------- public/app/features/scenes/apps/traffic.tsx | 163 ++++++++++++++++++ public/app/features/scenes/apps/utils.ts | 44 +++++ yarn.lock | 10 +- 6 files changed, 246 insertions(+), 70 deletions(-) rename public/app/features/scenes/apps/{scenes.ts => scenes.tsx} (87%) create mode 100644 public/app/features/scenes/apps/traffic.tsx diff --git a/package.json b/package.json index ac78d9eb01e..7d04e89fecb 100644 --- a/package.json +++ b/package.json @@ -261,7 +261,7 @@ "@grafana/lezer-logql": "0.1.3", "@grafana/monaco-logql": "^0.0.7", "@grafana/runtime": "workspace:*", - "@grafana/scenes": "^0.6.0", + "@grafana/scenes": "^0.7.0", "@grafana/schema": "workspace:*", "@grafana/ui": "workspace:*", "@kusto/monaco-kusto": "5.3.6", diff --git a/public/app/features/scenes/apps/GrafanaMonitoringApp.tsx b/public/app/features/scenes/apps/GrafanaMonitoringApp.tsx index b1aa3b7471f..a19e41bbdc7 100644 --- a/public/app/features/scenes/apps/GrafanaMonitoringApp.tsx +++ b/public/app/features/scenes/apps/GrafanaMonitoringApp.tsx @@ -12,6 +12,7 @@ import { getHandlerDetailsScene, getHandlerLogsScene, } from './scenes'; +import { getTrafficScene } from './traffic'; export function GrafanaMonitoringApp() { const appScene = useMemo( @@ -58,6 +59,12 @@ export function getMainPageScene() { }, ], }), + new SceneAppPage({ + title: 'Traffic', + url: '/scenes/grafana-monitoring/traffic', + getScene: getTrafficScene, + preserveUrlKeys: ['from', 'to', 'var-instance'], + }), new SceneAppPage({ title: 'Logs', url: '/scenes/grafana-monitoring/logs', diff --git a/public/app/features/scenes/apps/scenes.ts b/public/app/features/scenes/apps/scenes.tsx similarity index 87% rename from public/app/features/scenes/apps/scenes.ts rename to public/app/features/scenes/apps/scenes.tsx index 1f3fbe47808..9e964bc5d61 100644 --- a/public/app/features/scenes/apps/scenes.ts +++ b/public/app/features/scenes/apps/scenes.tsx @@ -1,3 +1,5 @@ +import React from 'react'; + import { FieldColorModeId, getFrameDisplayName } from '@grafana/data'; import { locationService } from '@grafana/runtime'; import { @@ -10,19 +12,17 @@ import { SceneTimeRange, VariableValueSelectors, SceneQueryRunner, - SceneVariableSet, - QueryVariable, SceneControlsSpacer, SceneDataTransformer, SceneRefreshPicker, SceneFlexItem, } from '@grafana/scenes'; -import { PromQuery } from 'app/plugins/datasource/prometheus/types'; +import { LinkButton } from '@grafana/ui'; import { SceneRadioToggle } from './SceneRadioToggle'; import { SceneSearchBox } from './SceneSearchBox'; import { getTableFilterTransform, getTimeSeriesFilterTransform } from './transforms'; -import { getLinkUrlWithAppUrlState } from './utils'; +import { getInstantQuery, getLinkUrlWithAppUrlState, getTimeSeriesQuery, getVariablesDefinitions } from './utils'; export function getHttpHandlerListScene(): EmbeddedScene { const searchBox = new SceneSearchBox({ value: '' }); @@ -49,7 +49,7 @@ export function getHttpHandlerListScene(): EmbeddedScene { const httpHandlersTable = new VizPanel({ $data: httpHandlerQueriesFiltered, pluginId: 'table', - title: '', + title: 'Handlers', options: { footer: { enablePagination: true, @@ -154,14 +154,23 @@ export function getHttpHandlerListScene(): EmbeddedScene { }), body: new SceneFlexLayout({ direction: 'row', + key: `row-${frameIndex}`, children: [ new SceneFlexItem({ + key: `flex1-${frameIndex}`, body: new VizPanel({ + key: `viz1-${frameIndex}`, pluginId: 'timeseries', - // titleLink: { - // path: `/scenes/grafana-monitoring/handlers/${encodeURIComponent(frame.fields[1].labels.handler)}`, - // queryKeys: ['from', 'to', 'var-instance'], - // }, + headerActions: ( + + Details + + ), title: getFrameDisplayName(frame), options: { legend: { showLegend: false }, @@ -170,26 +179,16 @@ export function getHttpHandlerListScene(): EmbeddedScene { }), new SceneFlexItem({ + key: `flex1-${frameIndex}`, width: 200, body: new VizPanel({ + key: `viz3-${frameIndex}`, title: 'Last', pluginId: 'stat', fieldConfig: { defaults: { displayName: 'Last', - links: [ - { - title: 'Go to handler drilldown view', - url: ``, - onBuildUrl: () => { - const params = locationService.getSearchObject(); - return getLinkUrlWithAppUrlState( - '/scenes/grafana-monitoring/handlers/${__field.labels.handler:percentencode}', - params - ); - }, - }, - ], + links: [], }, overrides: [], }, @@ -242,6 +241,11 @@ export function getHttpHandlerListScene(): EmbeddedScene { return scene; } +function getHandlerDrilldownUrl(handler: string) { + const params = locationService.getSearchObject(); + return getLinkUrlWithAppUrlState(`/scenes/grafana-monitoring/handlers/${encodeURIComponent(handler)}`, params); +} + export function getHandlerDetailsScene(handler: string): EmbeddedScene { const reqDurationTimeSeries = getTimeSeriesQuery({ expr: `avg without(job, instance) (rate(grafana_http_request_duration_seconds_sum{handler="${handler}"}[$__rate_interval])) * 1e3`, @@ -290,36 +294,6 @@ export function getHandlerDetailsScene(handler: string): EmbeddedScene { return scene; } -function getInstantQuery(query: Partial): SceneQueryRunner { - return new SceneQueryRunner({ - datasource: { uid: 'gdev-prometheus' }, - queries: [ - { - refId: 'A', - instant: true, - format: 'table', - maxDataPoints: 500, - ...query, - }, - ], - }); -} - -function getTimeSeriesQuery(query: Partial): SceneQueryRunner { - return new SceneQueryRunner({ - datasource: { uid: 'gdev-prometheus' }, - queries: [ - { - refId: 'A', - range: true, - format: 'time_series', - maxDataPoints: 500, - ...query, - }, - ], - }); -} - export function getOverviewScene(): EmbeddedScene { const scene = new EmbeddedScene({ $variables: getVariablesDefinitions(), @@ -429,18 +403,6 @@ export function getOverviewScene(): EmbeddedScene { return scene; } -function getVariablesDefinitions() { - return new SceneVariableSet({ - variables: [ - new QueryVariable({ - name: 'instance', - datasource: { uid: 'gdev-prometheus' }, - query: { query: 'label_values(grafana_http_request_duration_seconds_sum, instance)' }, - }), - ], - }); -} - function getInstantStatPanel(query: string, title: string) { return new VizPanel({ $data: getInstantQuery({ expr: query }), diff --git a/public/app/features/scenes/apps/traffic.tsx b/public/app/features/scenes/apps/traffic.tsx new file mode 100644 index 00000000000..afbf872147a --- /dev/null +++ b/public/app/features/scenes/apps/traffic.tsx @@ -0,0 +1,163 @@ +import React from 'react'; + +import { + SceneFlexLayout, + SceneTimePicker, + VizPanel, + EmbeddedScene, + SceneTimeRange, + VariableValueSelectors, + SceneControlsSpacer, + SceneRefreshPicker, + SceneFlexItem, + SceneObjectState, + SceneObjectBase, + SceneObjectUrlSyncConfig, + SceneObjectUrlValues, +} from '@grafana/scenes'; +import { Button } from '@grafana/ui'; + +import { getInstantQuery, getTimeSeriesQuery, getVariablesDefinitions } from './utils'; + +export function getTrafficScene(): EmbeddedScene { + const httpHandlersTable = new VizPanel({ + $data: getInstantQuery({ + expr: 'sort_desc(avg without(job, instance) (rate(grafana_http_request_duration_seconds_sum[$__rate_interval]) * 1e3)) ', + }), + pluginId: 'table', + title: 'Handlers', + options: { + footer: { + enablePagination: true, + }, + }, + fieldConfig: { + defaults: {}, + overrides: [ + { + matcher: { + id: 'byRegexp', + options: '.*', + }, + properties: [{ id: 'filterable', value: false }], + }, + { + matcher: { + id: 'byName', + options: 'Time', + }, + properties: [{ id: 'custom.hidden', value: true }], + }, + { + matcher: { + id: 'byName', + options: 'Value', + }, + properties: [{ id: 'displayName', value: 'Duration (Avg)' }], + }, + { + matcher: { + id: 'byName', + options: 'handler', + }, + properties: [ + { + id: 'links', + value: [ + { + title: 'Go to handler drilldown view', + url: '/scenes/grafana-monitoring/traffic?handler=${__value.text:percentencode}', + // onBuildUrl: () => { + // const params = locationService.getSearchObject(); + // return getLinkUrlWithAppUrlState('/scenes/grafana-monitoring/traffic', { + // ...params, + // handler: '${__value.text:percentencode}', + // }); + // }, + }, + ], + }, + ], + }, + ], + }, + }); + + const scene = new EmbeddedScene({ + $variables: getVariablesDefinitions(), + $timeRange: new SceneTimeRange({ from: 'now-1h', to: 'now' }), + controls: [ + new VariableValueSelectors({}), + new SceneControlsSpacer(), + new SceneTimePicker({ isOnCanvas: true }), + new SceneRefreshPicker({ isOnCanvas: true }), + ], + body: new SceneFlexLayout({ + $behaviors: [new HandlerDrilldownViewBehavior()], + children: [new SceneFlexItem({ body: httpHandlersTable })], + }), + }); + + return scene; +} + +export interface HandlerDrilldownViewBehaviorState extends SceneObjectState { + handler?: string; +} + +export class HandlerDrilldownViewBehavior extends SceneObjectBase { + protected _urlSync = new SceneObjectUrlSyncConfig(this, { keys: ['handler'] }); + + public constructor() { + super({}); + + this.addActivationHandler(() => { + this._subs.add(this.subscribeToState((state) => this.onHandlerChanged(state.handler))); + this.onHandlerChanged(this.state.handler); + }); + } + + private onHandlerChanged(handler: string | undefined) { + const layout = this.getLayout(); + + if (handler == null) { + layout.setState({ children: layout.state.children.slice(0, 1) }); + } else { + layout.setState({ children: [layout.state.children[0], this.getDrilldownView(handler)] }); + } + } + + private getDrilldownView(handler: string): SceneFlexItem { + return new SceneFlexItem({ + key: 'drilldown-flex', + body: new VizPanel({ + $data: getTimeSeriesQuery({ + expr: `rate(grafana_http_request_duration_seconds_sum{handler="${handler}"}[$__rate_interval]) * 1e3`, + }), + pluginId: 'timeseries', + title: `Handler: ${handler} details`, + headerActions: ( +
    {{ .Name }} - {{ .Value }} - {{ .Value }}
    {{ .Name }} - {{ .Value }} - {{ .Value }}
    - - {{ range $line := (splitList "\n" .Annotations.description) }} - + {{ range $line := (splitList "\n" .Annotations.description) }} {{ $line }}
    - - {{ end }} - + {{ end }}
    - - {{ range $refID, $value := .Values }} - - {{ $refID }}={{ $value }}  - {{ end }} - + {{ range $refID, $value := .Values }} + {{ $refID }}={{ $value }}  {{ end }}
    - - {{ range .Labels.SortedPairs }} - + {{ range .Labels.SortedPairs }} - + - - {{ end }} - + {{ end }}
    {{ .Name }} - {{ .Value }} - {{ .Value }}
    Annotations
    @@ -602,20 +588,16 @@
    - - {{ range .Annotations.SortedPairs }} - + {{ range .Annotations.SortedPairs }} + {{ if and (ne .Name "description") (ne .Name "summary") }} - + - - {{ end }} - + {{ end }} + {{ end }}
    {{ .Name }} - {{ .Value }} - {{ .Value }}
    - - {{ range $line := (splitList "\n" .Annotations.description) }} - + {{ range $line := (splitList "\n" .Annotations.description) }} {{ $line }}
    - - {{ end }} - + {{ end }}
    - - {{ range $refID, $value := .Values }} - - {{ $refID }}={{ $value }}  - {{ end }} - + {{ range $refID, $value := .Values }} + {{ $refID }}={{ $value }}  {{ end }}
    - - {{ range .Labels.SortedPairs }} - + {{ range .Labels.SortedPairs }} - + - - {{ end }} - + {{ end }}
    {{ .Name }} - {{ .Value }} - {{ .Value }}
    Annotations
    @@ -1111,20 +1079,16 @@
    - - {{ range .Annotations.SortedPairs }} - + {{ range .Annotations.SortedPairs }} + {{ if and (ne .Name "description") (ne .Name "summary") }} - + - - {{ end }} - + {{ end }} + {{ end }}
    {{ .Name }} - {{ .Value }} - {{ .Value }}