From 1ca5f3c5b78be371da6d6ea06015416eece7e16a Mon Sep 17 00:00:00 2001 From: "Grot (@grafanabot)" <43478413+grafanabot@users.noreply.github.com> Date: Thu, 9 Jun 2022 07:09:54 -0600 Subject: [PATCH 01/63] Updating migration docs to include most relevant breaking changes (#50356) (#50514) (cherry picked from commit cd01eb541a83fc0a1f222dbb917ff1dc25c44c46) Co-authored-by: Timur Olzhabayev --- docs/sources/whatsnew/whats-new-in-v9-0.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/docs/sources/whatsnew/whats-new-in-v9-0.md b/docs/sources/whatsnew/whats-new-in-v9-0.md index cdf3a8cddaf..74c7d2f8661 100644 --- a/docs/sources/whatsnew/whats-new-in-v9-0.md +++ b/docs/sources/whatsnew/whats-new-in-v9-0.md @@ -216,6 +216,25 @@ In the InfluxDB data source, browser access mode was deprecated in grafana 8.0.0 The rename by regex transformation has been improved to allow global patterns of the form `//g`. Depending on the regex match used, this may cause some transformations to behave slightly differently. You can guarantee the same behavior as before by wrapping the match string in forward slashes (/), for example, (._) would become /(._)/. ([Github Issue #48179](https://github.com/grafana/grafana/pull/48179)) +### Clock Panel + +We have updated [clock panel](https://grafana.com/grafana/plugins/grafana-clock-panel/) to version `2.0.0` to make it Compatible with Grafana 9. The previous version `1.3.1` will cause the Grafana 9 to [crash](https://github.com/grafana/clock-panel/issues/106) when being used in a dashboard, we encourage you to update the panel before migrating to Grafana 9. + +### Plugins: Most relevant breaking changes + +- `getColorForTheme` was removed, use `theme.visualization.getColorByName` instead [PR #49519](https://github.com/grafana/grafana/pull/49519) +- `TextDisplayOptions` was removed, use `VizTextDisplayOptions` instead [PR #49705](https://github.com/grafana/grafana/pull/49705) +- We have changed the internals of `backendSrv.fetch()` to throw an error when the response is an incorrect JSON. Make sure to handle possible errors on the callsite where using `backendSrv.fetch()` (or any other `backendSrv` methods) [PR #47493](https://github.com/grafana/grafana/pull/47493) +- We have removed the deprecated `getFormStyles` function from [grafana-ui](https://www.npmjs.com/package/@grafana/ui). Use `GrafanaTheme2` and the `useStyles2` hook instead [PR #49945](https://github.com/grafana/grafana/pull/49945) +- We have removed the deprecated `/api/tsdb/query` metrics endpoint. Use `/api/ds/query` instead [PR #49916](https://github.com/grafana/grafana/pull/49916) + +You can find the complete list of breaking changes in the links below. Please check them out for more details and see if there is anything affecting your plugin + +- https://grafana.com/docs/grafana/next/release-notes/release-notes-9-0-0-beta1/ +- https://grafana.com/docs/grafana/next/release-notes/release-notes-9-0-0-beta2/ +- https://grafana.com/docs/grafana/next/release-notes/release-notes-9-0-0-beta3/ +- https://grafana.com/docs/grafana/next/release-notes/release-notes-9-0-0 + ## A note on Grafana Enterprise licensing When we release Grafana 9.0 on June 14th, Grafana will no longer enforce viewers and editor-admins differently. That means that regardless of whether your Grafana Enterprise license is tiered or combined, instead of seeing this on the Stats & Licensing page: From 06ed6d25f5fb5828e729c17c6d06b7ba4f67c0c6 Mon Sep 17 00:00:00 2001 From: "Grot (@grafanabot)" <43478413+grafanabot@users.noreply.github.com> Date: Thu, 9 Jun 2022 07:43:32 -0600 Subject: [PATCH 02/63] ReleaseNotes: Updated changelog and release notes for 9.0.0-beta2 (#50398) (#50520) Co-authored-by: Timur Olzhabayev (cherry picked from commit a6943cb399afeaac74be81269c1716e0952eb114) --- .../release-notes-9-0-0-beta2.md | 24 +------------------ 1 file changed, 1 insertion(+), 23 deletions(-) diff --git a/docs/sources/release-notes/release-notes-9-0-0-beta2.md b/docs/sources/release-notes/release-notes-9-0-0-beta2.md index 44e9e6b1080..068c6295287 100644 --- a/docs/sources/release-notes/release-notes-9-0-0-beta2.md +++ b/docs/sources/release-notes/release-notes-9-0-0-beta2.md @@ -81,29 +81,7 @@ module.exports.getWebpackConfig = (config, options) => ({ }); ``` -Please refer to the webpack build error messages or the [official migration guide](https://webpack.js.org/migrate/5/) for assistance with fallbacks. - -**Which issue(s) this PR fixes**: - - - -Fixes # - -**Special notes for your reviewer**: - -It does not bump the following dependencies to the very latest due to the latest versions being ES modules: - -- ora -- globby -- execa -- chalk - Issue [#47826](https://github.com/grafana/grafana/issues/47826) +Please refer to the webpack build error messages or the [official migration guide](https://webpack.js.org/migrate/5/) for assistance with fallbacks. Issue [#47826](https://github.com/grafana/grafana/issues/47826) We have changed the internals of `backendSrv.fetch()` to throw an error when the response is an incorrect JSON. From 7a4e3a3588d75051ab265b8b59f66facf43194a3 Mon Sep 17 00:00:00 2001 From: "Grot (@grafanabot)" <43478413+grafanabot@users.noreply.github.com> Date: Thu, 9 Jun 2022 08:27:59 -0600 Subject: [PATCH 03/63] [v9.0.x] Docs: Manual backport of alert list panel docs rewrite (#50516) Co-authored-by: Gilles De Mey --- .../visualizations/alert-list-panel.md | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/docs/sources/visualizations/alert-list-panel.md b/docs/sources/visualizations/alert-list-panel.md index 9d5f1838cae..5e14a1bc695 100644 --- a/docs/sources/visualizations/alert-list-panel.md +++ b/docs/sources/visualizations/alert-list-panel.md @@ -16,25 +16,29 @@ weight: 100 # Alert list -The Alert list allows you to display your dashboards alerts. You can configure the list to show current state or recent state changes. You can read more about alerts in [Alerts overview]({{< relref "../alerting/_index.md" >}}). +Use Alert list to display your alerts. You can configure the list to show the current state or recent state changes. You can read more about alerts in [Grafana Alerting overview]({{< relref "../alerting/" >}}). -{{< figure src="/static/img/docs/v45/alert-list-panel.png" max-width="850px" >}} +{{< figure src="/static/img/docs/alert-list-panel/alert-list-panel.png" max-width="850px" >}} -Use these settings to refine your visualization. +Customize your visualization using the following settings. ## Options -- **Show -** Choose whether the panel should display the current alert state or recent alert state changes. +- **Group mode -** Choose between "Default grouping" to show alert instances grouped by their alert rule, or "Custom grouping" to group alert instances by a custom set of labels. - **Max Items -** Sets the maximum number of alerts to list. - **Sort order -** Select how to order the alerts displayed: - **Alphabetical (asc) -** Alphabetical order. - **Alphabetical (desc) -** Reverse alphabetical order. - **Importance -** By importance according to the following values, with 1 being the highest: - alerting: 1 + - firing: 1 - no_data: 2 - pending: 3 - ok: 4 - paused: 5 + - inactive: 5 + - **Time (asc) -** Newest active alert instances first. + - **Time (desc) -** Oldest active alert instances first. - **Alerts from this dashboard -** Shows alerts only from the dashboard the alert list is in. ## Filter @@ -42,17 +46,16 @@ Use these settings to refine your visualization. These options allow you to limit alerts shown to only those that match the query, folder, or tags you choose. - **Alert name -** Enter an alert name query. -- **Dashboard title -** Enter a dashboard title query. +- **Alert instance label -** Filter alert instances using label querying, ex: `{severity="critical", instance=~"cluster-us-.+"}`. - **Folder -** Select a folder. Only alerts from dashboards in the folder selected will be displayed. -- **Dashboard tags -** Select one or more tags. Only alerts from dashboards with one or more of the tags will be displayed. +- **Datasource -** Filter alerts from the selected data source. ## State filter Choose which alert states to display in this panel. -- Ok -- Paused -- No data -- Execution error -- Alerting +- Alerting / Firing - Pending +- No Data +- Normal +- Error From dd7ea16eb1c7b8577dec1378dde7c12def2da52c Mon Sep 17 00:00:00 2001 From: "Grot (@grafanabot)" <43478413+grafanabot@users.noreply.github.com> Date: Thu, 9 Jun 2022 08:58:04 -0600 Subject: [PATCH 04/63] AzureMonitor: Clean namespace when changing the resource (#50311) (#50342) (cherry picked from commit ae449cc8239fee02172b3180ac1ee79f13d38a85) Co-authored-by: Andres Martinez Gotor --- .../MetricsQueryEditor/setQueryValue.test.ts | 31 +++++++++++++++++++ .../MetricsQueryEditor/setQueryValue.ts | 1 + 2 files changed, 32 insertions(+) create mode 100644 public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/setQueryValue.test.ts diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/setQueryValue.test.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/setQueryValue.test.ts new file mode 100644 index 00000000000..e1edaff0c30 --- /dev/null +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/setQueryValue.test.ts @@ -0,0 +1,31 @@ +import createMockQuery from '../../__mocks__/query'; + +import { setResource } from './setQueryValue'; + +describe('setResource', () => { + it('should set a resource URI', () => { + const q = setResource(createMockQuery(), '/new-uri'); + expect(q.azureMonitor?.resourceUri).toEqual('/new-uri'); + }); + + it('should remove clean up dependent fields', () => { + const q = createMockQuery(); + expect(q.azureMonitor?.metricNamespace).not.toEqual(undefined); + expect(q.azureMonitor?.metricName).not.toEqual(undefined); + expect(q.azureMonitor?.metricDefinition).not.toEqual(undefined); + expect(q.azureMonitor?.aggregation).not.toEqual(undefined); + expect(q.azureMonitor?.metricDefinition).not.toEqual(undefined); + expect(q.azureMonitor?.metricDefinition).not.toEqual(undefined); + expect(q.azureMonitor?.timeGrain).not.toEqual(''); + expect(q.azureMonitor?.timeGrain).not.toEqual([]); + const newQ = setResource(createMockQuery(), '/new-uri'); + expect(newQ.azureMonitor).toMatchObject({ + metricNamespace: undefined, + metricName: undefined, + aggregation: undefined, + metricDefinition: undefined, + timeGrain: '', + dimensionFilters: [], + }); + }); +}); diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/setQueryValue.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/setQueryValue.ts index a83c214380c..165016e33b9 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/setQueryValue.ts +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/setQueryValue.ts @@ -9,6 +9,7 @@ export function setResource(query: AzureMonitorQuery, resourceURI: string | unde metricNamespace: undefined, metricName: undefined, aggregation: undefined, + metricDefinition: undefined, timeGrain: '', dimensionFilters: [], }, From cfe8116791308a1b0a22167cc112f8579addc11d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Farkas?= Date: Thu, 9 Jun 2022 17:04:51 +0200 Subject: [PATCH 05/63] logs: track the usage of certain features (#50325) (#50526) * logs: track the usage of certain features * Add report interaction for logs interactions * mock reportInteraction in test * mock reportInteraction Co-authored-by: Ivana Huckova (cherry picked from commit c412a3b052ac16c23ba809e438ac5350b7d6ee0e) --- .../components/Inspector/InspectContent.tsx | 3 +- .../explore/ExploreQueryInspector.tsx | 3 +- .../app/features/explore/ExploreToolbar.tsx | 36 ++++++++++++------- public/app/features/explore/Logs.tsx | 6 ++++ public/app/features/explore/LogsContainer.tsx | 1 + .../features/explore/LogsNavigation.test.tsx | 6 ++++ .../app/features/explore/LogsNavigation.tsx | 7 ++++ .../features/explore/LogsNavigationPages.tsx | 9 ++++- .../explore/spec/queryHistory.test.tsx | 1 - public/app/features/explore/state/query.ts | 7 +++- .../app/features/inspector/InspectDataTab.tsx | 21 +++++++++-- .../loki/components/LokiCheatSheet.tsx | 8 ++++- .../loki/components/LokiLabelBrowser.test.tsx | 6 ++++ .../loki/components/LokiLabelBrowser.tsx | 16 ++++++++- .../loki/components/LokiQueryField.tsx | 13 +++++++ .../components/LokiQueryBuilderOptions.tsx | 10 ++++-- .../components/LokiQueryCodeEditor.tsx | 1 + .../components/LokiQueryEditorSelector.tsx | 2 +- 18 files changed, 131 insertions(+), 25 deletions(-) diff --git a/public/app/features/dashboard/components/Inspector/InspectContent.tsx b/public/app/features/dashboard/components/Inspector/InspectContent.tsx index daa3608548d..5d3e5b9fc36 100644 --- a/public/app/features/dashboard/components/Inspector/InspectContent.tsx +++ b/public/app/features/dashboard/components/Inspector/InspectContent.tsx @@ -1,6 +1,6 @@ import React, { useState } from 'react'; -import { DataSourceApi, formattedValueToString, getValueFormat, PanelData, PanelPlugin } from '@grafana/data'; +import { CoreApp, DataSourceApi, formattedValueToString, getValueFormat, PanelData, PanelPlugin } from '@grafana/data'; import { getTemplateSrv } from '@grafana/runtime'; import { Drawer, Tab, TabsBar } from '@grafana/ui'; import { InspectDataTab } from 'app/features/inspector/InspectDataTab'; @@ -90,6 +90,7 @@ export const InspectContent: React.FC = ({ options={dataOptions} onOptionsChange={onDataOptionsChange} timeZone={dashboard.timezone} + app={CoreApp.Dashboard} /> )} {data && activeTab === InspectTab.Meta && ( diff --git a/public/app/features/explore/ExploreQueryInspector.tsx b/public/app/features/explore/ExploreQueryInspector.tsx index f231bd9a33d..bc39f5ea0eb 100644 --- a/public/app/features/explore/ExploreQueryInspector.tsx +++ b/public/app/features/explore/ExploreQueryInspector.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { connect, ConnectedProps } from 'react-redux'; -import { TimeZone } from '@grafana/data'; +import { CoreApp, TimeZone } from '@grafana/data'; import { TabbedContainer, TabConfig } from '@grafana/ui'; import { ExploreDrawer } from 'app/features/explore/ExploreDrawer'; import { InspectDataTab } from 'app/features/inspector/InspectDataTab'; @@ -51,6 +51,7 @@ export function ExploreQueryInspector(props: Props) { isLoading={loading} options={{ withTransforms: false, withFieldConfig: false }} timeZone={timeZone} + app={CoreApp.Explore} /> ), }; diff --git a/public/app/features/explore/ExploreToolbar.tsx b/public/app/features/explore/ExploreToolbar.tsx index f4e7b0468b3..f1c164cfc94 100644 --- a/public/app/features/explore/ExploreToolbar.tsx +++ b/public/app/features/explore/ExploreToolbar.tsx @@ -2,7 +2,7 @@ import React, { lazy, PureComponent, RefObject, Suspense } from 'react'; import { connect, ConnectedProps } from 'react-redux'; import { DataSourceInstanceSettings, RawTimeRange } from '@grafana/data'; -import { config, DataSourcePicker } from '@grafana/runtime'; +import { config, DataSourcePicker, reportInteraction } from '@grafana/runtime'; import { defaultIntervals, PageToolbar, @@ -186,17 +186,28 @@ class UnConnectedExploreToolbar extends PureComponent { {hasLiveOption && ( - {(controls) => ( - - )} + {(c) => { + const controls = { + ...c, + start: () => { + reportInteraction('grafana_explore_logs_result_displayed', { + datasourceType: this.props.datasourceType, + }); + c.start(); + }, + }; + return ( + + ); + }} )} @@ -217,6 +228,7 @@ const mapStateToProps = (state: StoreState, { exploreId }: OwnProps) => { return { datasourceMissing, datasourceName: datasourceInstance?.name, + datasourceType: datasourceInstance?.type, loading, range, timeZone: getTimeZone(state.user), diff --git a/public/app/features/explore/Logs.tsx b/public/app/features/explore/Logs.tsx index d1ae1172991..fb1105ca626 100644 --- a/public/app/features/explore/Logs.tsx +++ b/public/app/features/explore/Logs.tsx @@ -21,6 +21,7 @@ import { GrafanaTheme2, LoadingState, } from '@grafana/data'; +import { reportInteraction } from '@grafana/runtime'; import { TooltipDisplayMode } from '@grafana/schema'; import { RadioButtonGroup, @@ -64,6 +65,7 @@ interface Props extends Themeable2 { scanning?: boolean; scanRange?: RawTimeRange; exploreId: ExploreId; + datasourceType?: string; showContextToggle?: (row?: LogRowModel) => boolean; onChangeTime: (range: AbsoluteTimeRange) => void; onClickFilterLabel?: (key: string, value: string) => void; @@ -138,6 +140,10 @@ class UnthemedLogs extends PureComponent { }; onChangeDedup = (dedupStrategy: LogsDedupStrategy) => { + reportInteraction('grafana_explore_logs_deduplication_clicked', { + deduplicationType: dedupStrategy, + datasourceType: this.props.datasourceType, + }); this.setState({ dedupStrategy }); }; diff --git a/public/app/features/explore/LogsContainer.tsx b/public/app/features/explore/LogsContainer.tsx index cc9660f7d82..111f9bc6e48 100644 --- a/public/app/features/explore/LogsContainer.tsx +++ b/public/app/features/explore/LogsContainer.tsx @@ -128,6 +128,7 @@ class LogsContainer extends PureComponent { ({ + ...jest.requireActual('@grafana/runtime'), + reportInteraction: () => null, +})); + type LogsNavigationProps = ComponentProps; const defaultProps: LogsNavigationProps = { absoluteRange: { from: 1637319381811, to: 1637322981811 }, diff --git a/public/app/features/explore/LogsNavigation.tsx b/public/app/features/explore/LogsNavigation.tsx index 5c2cd5a0904..5d495660161 100644 --- a/public/app/features/explore/LogsNavigation.tsx +++ b/public/app/features/explore/LogsNavigation.tsx @@ -3,6 +3,7 @@ import { isEqual } from 'lodash'; import React, { memo, useState, useEffect, useRef } from 'react'; import { LogsSortOrder, AbsoluteTimeRange, TimeZone, DataQuery, GrafanaTheme2 } from '@grafana/data'; +import { reportInteraction } from '@grafana/runtime'; import { Button, Icon, Spinner, useTheme2 } from '@grafana/ui'; import { LogsNavigationPages } from './LogsNavigationPages'; @@ -107,6 +108,9 @@ function LogsNavigation({ variant="secondary" onClick={() => { //If we are not on the last page, use next page's range + reportInteraction('grafana_explore_logs_pagination_clicked', { + pageType: 'olderLogsButton', + }); if (!onLastPage) { const indexChange = oldestLogsFirst ? -1 : 1; changeTime({ @@ -133,6 +137,9 @@ function LogsNavigation({ className={styles.navButton} variant="secondary" onClick={() => { + reportInteraction('grafana_explore_logs_pagination_clicked', { + pageType: 'newerLogsButton', + }); //If we are not on the first page, use previous page's range if (!onFirstPage) { const indexChange = oldestLogsFirst ? 1 : -1; diff --git a/public/app/features/explore/LogsNavigationPages.tsx b/public/app/features/explore/LogsNavigationPages.tsx index a5c978b0d8c..b4ab4153d92 100644 --- a/public/app/features/explore/LogsNavigationPages.tsx +++ b/public/app/features/explore/LogsNavigationPages.tsx @@ -2,6 +2,7 @@ import { css, cx } from '@emotion/css'; import React from 'react'; import { dateTimeFormat, systemDateFormats, TimeZone, AbsoluteTimeRange, GrafanaTheme2 } from '@grafana/data'; +import { reportInteraction } from '@grafana/runtime'; import { CustomScrollbar, Spinner, useTheme2 } from '@grafana/ui'; import { LogsPage } from './LogsNavigation'; @@ -51,7 +52,13 @@ export function LogsNavigationPages({ data-testid={`page${index + 1}`} className={styles.page} key={page.queryRange.to} - onClick={() => !loading && changeTime({ from: page.queryRange.from, to: page.queryRange.to })} + onClick={() => { + reportInteraction('grafana_explore_logs_pagination_clicked', { + pageType: 'page', + pageNumber: index + 1, + }); + !loading && changeTime({ from: page.queryRange.from, to: page.queryRange.to }); + }} >
diff --git a/public/app/features/explore/spec/queryHistory.test.tsx b/public/app/features/explore/spec/queryHistory.test.tsx index f8d99773ac8..86dae67df45 100644 --- a/public/app/features/explore/spec/queryHistory.test.tsx +++ b/public/app/features/explore/spec/queryHistory.test.tsx @@ -118,7 +118,6 @@ describe('Explore: Query History', () => { await openQueryHistory(); await assertQueryHistoryExists(RAW_QUERY); - expect(reportInteractionMock).toBeCalledTimes(2); expect(reportInteractionMock).toBeCalledWith('grafana_explore_query_history_opened', { queryHistoryEnabled: false, }); diff --git a/public/app/features/explore/state/query.ts b/public/app/features/explore/state/query.ts index 6a192bfcda5..137e7e6acc3 100644 --- a/public/app/features/explore/state/query.ts +++ b/public/app/features/explore/state/query.ts @@ -19,7 +19,7 @@ import { QueryFixAction, toLegacyResponseData, } from '@grafana/data'; -import { config } from '@grafana/runtime'; +import { config, reportInteraction } from '@grafana/runtime'; import { buildQueryTransaction, ensureQueries, @@ -442,6 +442,11 @@ export const runQueries = ( ) .subscribe({ next(data) { + if (data.logsResult !== null) { + reportInteraction('grafana_explore_logs_result_displayed', { + datasourceType: datasourceInstance.type, + }); + } dispatch(queryStreamUpdatedAction({ exploreId, response: data })); // Keep scanning for results if this was the last scanning transaction diff --git a/public/app/features/inspector/InspectDataTab.tsx b/public/app/features/inspector/InspectDataTab.tsx index 6fed6dbfdce..d05dbfd5366 100644 --- a/public/app/features/inspector/InspectDataTab.tsx +++ b/public/app/features/inspector/InspectDataTab.tsx @@ -16,8 +16,10 @@ import { toCSV, transformDataFrame, TimeZone, + CoreApp, } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; +import { reportInteraction } from '@grafana/runtime'; import { Button, Spinner, Table } from '@grafana/ui'; import { config } from 'app/core/config'; import { dataFrameToLogsModel } from 'app/core/logs_model'; @@ -34,6 +36,7 @@ interface Props { isLoading: boolean; options: GetDataOptions; timeZone: TimeZone; + app?: CoreApp; data?: DataFrame[]; panel?: PanelModel; onOptionsChange?: (options: GetDataOptions) => void; @@ -107,7 +110,11 @@ export class InspectDataTab extends PureComponent { }; exportLogsAsTxt = () => { - const { data, panel } = this.props; + const { data, panel, app } = this.props; + reportInteraction('grafana_logs_download_logs_clicked', { + app, + format: 'logs', + }); const logsModel = dataFrameToLogsModel(data || [], undefined); let textToDownload = ''; @@ -209,7 +216,7 @@ export class InspectDataTab extends PureComponent { } render() { - const { isLoading, options, data, panel, onOptionsChange } = this.props; + const { isLoading, options, data, panel, onOptionsChange, app } = this.props; const { dataFrameIndex, transformId, transformationOptions, selectedDataFrame, downloadForExcel } = this.state; const styles = getPanelInspectorStyles(); @@ -251,7 +258,15 @@ export class InspectDataTab extends PureComponent { />
); diff --git a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryEditorSelector.tsx b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryEditorSelector.tsx index 1c84d804923..b0f045f1f4c 100644 --- a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryEditorSelector.tsx +++ b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryEditorSelector.tsx @@ -126,7 +126,7 @@ export const LokiQueryEditorSelector = React.memo((props) )} {editorMode === QueryEditorMode.Explain && } {editorMode !== QueryEditorMode.Explain && ( - + )} From 4f011515c6827e88dece69e485e99c044ab19c3d Mon Sep 17 00:00:00 2001 From: "Grot (@grafanabot)" <43478413+grafanabot@users.noreply.github.com> Date: Thu, 9 Jun 2022 09:37:58 -0600 Subject: [PATCH 06/63] AzureMonitor: Remove unused code (#50399) (#50531) (cherry picked from commit 9703c9211e2cb6dd2b941bd14af221105455f9bc) Co-authored-by: Andres Martinez Gotor --- .../MetricsQueryEditor.test.tsx | 491 +++++++++--------- .../MetricsQueryEditor/MetricsQueryEditor.tsx | 303 ++++++----- .../ResourceGroupsField.tsx | 46 -- .../MetricsQueryEditor/ResourceNameField.tsx | 46 -- .../MetricsQueryEditor/ResourceTypeField.tsx | 50 -- .../MetricsQueryEditor/dataHooks.test.ts | 221 +------- .../MetricsQueryEditor/dataHooks.ts | 203 ++------ .../components/MetricsQueryEditor/index.tsx | 1 - .../MetricsQueryEditor/setQueryValue.ts | 89 ---- .../MetricsQueryEditor.test.tsx | 260 ---------- .../MetricsQueryEditor.tsx | 228 -------- .../NewMetricsQueryEditor/dataHooks.test.ts | 235 --------- .../NewMetricsQueryEditor/dataHooks.ts | 152 ------ .../components/QueryEditor/QueryEditor.tsx | 4 +- 14 files changed, 471 insertions(+), 1858 deletions(-) delete mode 100644 public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/ResourceGroupsField.tsx delete mode 100644 public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/ResourceNameField.tsx delete mode 100644 public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/ResourceTypeField.tsx delete mode 100644 public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/index.tsx delete mode 100644 public/app/plugins/datasource/grafana-azure-monitor-datasource/components/NewMetricsQueryEditor/MetricsQueryEditor.test.tsx delete mode 100644 public/app/plugins/datasource/grafana-azure-monitor-datasource/components/NewMetricsQueryEditor/MetricsQueryEditor.tsx delete mode 100644 public/app/plugins/datasource/grafana-azure-monitor-datasource/components/NewMetricsQueryEditor/dataHooks.test.ts delete mode 100644 public/app/plugins/datasource/grafana-azure-monitor-datasource/components/NewMetricsQueryEditor/dataHooks.ts diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/MetricsQueryEditor.test.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/MetricsQueryEditor.test.tsx index f4c729c52b8..0a0f60ebaa2 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/MetricsQueryEditor.test.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/MetricsQueryEditor.test.tsx @@ -1,10 +1,20 @@ -import { render, screen, waitFor } from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import React from 'react'; import { selectOptionInTest } from 'test/helpers/selectOptionInTest'; +import { config } from '@grafana/runtime'; + import createMockDatasource from '../../__mocks__/datasource'; +import { createMockInstanceSetttings } from '../../__mocks__/instanceSettings'; import createMockPanelData from '../../__mocks__/panelData'; import createMockQuery from '../../__mocks__/query'; +import { + createMockResourceGroupsBySubscription, + createMockSubscriptions, + mockResourcesByResourceGroup, +} from '../../__mocks__/resourcePickerRows'; +import ResourcePickerData from '../../resourcePicker/resourcePickerData'; import MetricsQueryEditor from './MetricsQueryEditor'; @@ -13,259 +23,238 @@ const variableOptionGroup = { options: [], }; -describe('Azure Monitor QueryEditor', () => { - const mockPanelData = createMockPanelData(); - it('should render', async () => { - const mockDatasource = createMockDatasource(); - render( - {}} - setError={() => {}} - /> - ); - await waitFor(() => expect(screen.getByTestId('azure-monitor-metrics-query-editor')).toBeInTheDocument()); - }); +const tests = [ + { + id: 'azure-monitor-metrics-query-editor-with-resource-picker', + }, + { + id: 'azure-monitor-metrics-query-editor-with-experimental-ui', + }, +]; - it('should change the subscription ID when selected', async () => { - const mockDatasource = createMockDatasource(); - const onChange = jest.fn(); - const mockQuery = createMockQuery(); - (mockQuery.azureMonitor ?? {}).metricName = undefined; - mockDatasource.azureMonitorDatasource.getSubscriptions = jest.fn().mockResolvedValueOnce([ - { - value: 'abc-123', - text: 'Primary Subscription', - }, - { - value: 'abc-456', - text: 'Another Subscription', - }, - ]); +export function createMockResourcePickerData() { + const mockDatasource = new ResourcePickerData(createMockInstanceSetttings()); - render( - {}} - /> - ); + mockDatasource.getSubscriptions = jest.fn().mockResolvedValue(createMockSubscriptions()); + mockDatasource.getResourceGroupsBySubscriptionId = jest + .fn() + .mockResolvedValue(createMockResourceGroupsBySubscription()); + mockDatasource.getResourcesForResourceGroup = jest.fn().mockResolvedValue(mockResourcesByResourceGroup()); + mockDatasource.getResourceURIFromWorkspace = jest.fn().mockReturnValue(''); + mockDatasource.getResourceURIDisplayProperties = jest.fn().mockResolvedValue({}); - const subscriptions = await screen.findByLabelText('Subscription'); - await selectOptionInTest(subscriptions, 'Another Subscription'); + return mockDatasource; +} - expect(onChange).toHaveBeenCalledWith({ - ...mockQuery, - subscription: 'abc-456', - azureMonitor: { - ...mockQuery.azureMonitor, - resourceGroup: undefined, - metricDefinition: undefined, - metricNamespace: undefined, - resourceName: undefined, - metricName: undefined, - aggregation: undefined, - timeGrain: '', - dimensionFilters: [], - }, +for (const t of tests) { + describe(`MetricsQueryEditor: ${t.id}`, () => { + const originalScrollIntoView = window.HTMLElement.prototype.scrollIntoView; + const mockPanelData = createMockPanelData(); + + beforeEach(() => { + window.HTMLElement.prototype.scrollIntoView = function () {}; + config.featureToggles.azureMonitorExperimentalUI = + t.id === 'azure-monitor-metrics-query-editor-with-experimental-ui'; + }); + afterEach(() => { + window.HTMLElement.prototype.scrollIntoView = originalScrollIntoView; + config.featureToggles.azureMonitorExperimentalUI = false; + }); + + it('should render', async () => { + const mockDatasource = createMockDatasource({ resourcePickerData: createMockResourcePickerData() }); + + render( + {}} + setError={() => {}} + /> + ); + + expect(await screen.findByTestId(t.id)).toBeInTheDocument(); + }); + + it('should change resource when a resource is selected in the ResourcePicker', async () => { + const mockDatasource = createMockDatasource({ resourcePickerData: createMockResourcePickerData() }); + const query = createMockQuery(); + delete query?.azureMonitor?.resourceUri; + const onChange = jest.fn(); + + render( + {}} + /> + ); + + const resourcePickerButton = await screen.findByRole('button', { name: 'Select a resource' }); + expect(resourcePickerButton).toBeInTheDocument(); + expect(screen.queryByRole('button', { name: 'Expand Primary Subscription' })).not.toBeInTheDocument(); + resourcePickerButton.click(); + + const subscriptionButton = await screen.findByRole('button', { name: 'Expand Primary Subscription' }); + expect(subscriptionButton).toBeInTheDocument(); + expect(screen.queryByRole('button', { name: 'Expand A Great Resource Group' })).not.toBeInTheDocument(); + subscriptionButton.click(); + + const resourceGroupButton = await screen.findByRole('button', { name: 'Expand A Great Resource Group' }); + expect(resourceGroupButton).toBeInTheDocument(); + expect(screen.queryByLabelText('web-server')).not.toBeInTheDocument(); + resourceGroupButton.click(); + + const checkbox = await screen.findByLabelText('web-server'); + expect(checkbox).toBeInTheDocument(); + expect(checkbox).not.toBeChecked(); + await userEvent.click(checkbox); + expect(checkbox).toBeChecked(); + await userEvent.click(await screen.findByRole('button', { name: 'Apply' })); + + expect(onChange).toBeCalledTimes(1); + expect(onChange).toBeCalledWith( + expect.objectContaining({ + azureMonitor: expect.objectContaining({ + resourceUri: + '/subscriptions/def-456/resourceGroups/dev-3/providers/Microsoft.Compute/virtualMachines/web-server', + }), + }) + ); + }); + + it('should reset metric namespace, metric name, and aggregation fields after selecting a new resource when a valid query has already been set', async () => { + const mockDatasource = createMockDatasource({ resourcePickerData: createMockResourcePickerData() }); + const query = createMockQuery(); + const onChange = jest.fn(); + + render( + {}} + /> + ); + + const resourcePickerButton = await screen.findByRole('button', { name: /grafana/ }); + + expect(screen.getByText('Microsoft.Compute/virtualMachines')).toBeInTheDocument(); + expect(screen.getByText('Metric A')).toBeInTheDocument(); + expect(screen.getByText('Average')).toBeInTheDocument(); + + expect(resourcePickerButton).toBeInTheDocument(); + expect(screen.queryByRole('button', { name: 'Expand Primary Subscription' })).not.toBeInTheDocument(); + resourcePickerButton.click(); + + const subscriptionButton = await screen.findByRole('button', { name: 'Expand Dev Subscription' }); + expect(subscriptionButton).toBeInTheDocument(); + expect(screen.queryByRole('button', { name: 'Expand Development 3' })).not.toBeInTheDocument(); + subscriptionButton.click(); + + const resourceGroupButton = await screen.findByRole('button', { name: 'Expand Development 3' }); + expect(resourceGroupButton).toBeInTheDocument(); + expect(screen.queryByLabelText('db-server')).not.toBeInTheDocument(); + resourceGroupButton.click(); + + const checkbox = await screen.findByLabelText('db-server'); + expect(checkbox).toBeInTheDocument(); + expect(checkbox).not.toBeChecked(); + await userEvent.click(checkbox); + expect(checkbox).toBeChecked(); + await userEvent.click(await screen.findByRole('button', { name: 'Apply' })); + + expect(onChange).toBeCalledTimes(1); + expect(onChange).toBeCalledWith( + expect.objectContaining({ + azureMonitor: expect.objectContaining({ + resourceUri: + '/subscriptions/def-456/resourceGroups/dev-3/providers/Microsoft.Compute/virtualMachines/db-server', + metricNamespace: undefined, + metricName: undefined, + aggregation: undefined, + timeGrain: '', + dimensionFilters: [], + }), + }) + ); + }); + + it('should change the metric name when selected', async () => { + const mockDatasource = createMockDatasource({ resourcePickerData: createMockResourcePickerData() }); + const onChange = jest.fn(); + const mockQuery = createMockQuery(); + mockDatasource.azureMonitorDatasource.getMetricNames = jest.fn().mockResolvedValue([ + { + value: 'metric-a', + text: 'Metric A', + }, + { + value: 'metric-b', + text: 'Metric B', + }, + ]); + + render( + {}} + /> + ); + + const metrics = await screen.findByLabelText('Metric'); + expect(metrics).toBeInTheDocument(); + await selectOptionInTest(metrics, 'Metric B'); + + expect(onChange).toHaveBeenLastCalledWith({ + ...mockQuery, + azureMonitor: { + ...mockQuery.azureMonitor, + metricName: 'metric-b', + aggregation: undefined, + timeGrain: '', + }, + }); + }); + + it('should change the aggregation type when selected', async () => { + const mockDatasource = createMockDatasource({ resourcePickerData: createMockResourcePickerData() }); + const onChange = jest.fn(); + const mockQuery = createMockQuery(); + + render( + {}} + /> + ); + + const aggregation = await screen.findByLabelText('Aggregation'); + expect(aggregation).toBeInTheDocument(); + await selectOptionInTest(aggregation, 'Maximum'); + + expect(onChange).toHaveBeenLastCalledWith({ + ...mockQuery, + azureMonitor: { + ...mockQuery.azureMonitor, + aggregation: 'Maximum', + }, + }); }); }); - - it('should change the resource group when selected', async () => { - const mockDatasource = createMockDatasource(); - const onChange = jest.fn(); - const mockQuery = createMockQuery(); - mockDatasource.getResourceGroups = jest.fn().mockResolvedValue([ - { text: 'grafanastaging', value: 'grafanastaging' }, - { text: 'Grafana Prod', value: 'grafanaprod' }, - ]); - render( - {}} - /> - ); - await waitFor(() => expect(screen.getByTestId('azure-monitor-metrics-query-editor')).toBeInTheDocument()); - - const resourceGroup = await screen.findByLabelText('Resource group'); - await selectOptionInTest(resourceGroup, 'Grafana Prod'); - - expect(onChange).toHaveBeenLastCalledWith({ - ...mockQuery, - azureMonitor: { - ...mockQuery.azureMonitor, - resourceUri: '', - resourceGroup: 'grafanaprod', - metricDefinition: undefined, - metricNamespace: undefined, - resourceName: undefined, - metricName: undefined, - aggregation: undefined, - timeGrain: '', - dimensionFilters: [], - }, - }); - }); - - it('should change the resource type when selected', async () => { - const mockDatasource = createMockDatasource(); - const onChange = jest.fn(); - const mockQuery = createMockQuery(); - mockDatasource.getMetricDefinitions = jest.fn().mockResolvedValue([ - { text: 'Virtual Machine', value: 'azure/vm' }, - { text: 'Database', value: 'azure/db' }, - ]); - render( - {}} - /> - ); - await waitFor(() => expect(screen.getByTestId('azure-monitor-metrics-query-editor')).toBeInTheDocument()); - - const resourceGroup = await screen.findByLabelText('Resource type'); - await selectOptionInTest(resourceGroup, 'Virtual Machine'); - - expect(onChange).toHaveBeenLastCalledWith({ - ...mockQuery, - azureMonitor: { - ...mockQuery.azureMonitor, - resourceUri: '', - metricDefinition: 'azure/vm', - resourceName: undefined, - metricNamespace: undefined, - metricName: undefined, - aggregation: undefined, - timeGrain: '', - dimensionFilters: [], - }, - }); - }); - - it('should change the resource name when selected', async () => { - const mockDatasource = createMockDatasource(); - const onChange = jest.fn(); - const mockQuery = createMockQuery(); - mockDatasource.getResourceNames = jest.fn().mockResolvedValue([ - { text: 'ResourceName1', value: 'resource-name-1' }, - { text: 'ResourceName2', value: 'resource-name-2' }, - ]); - render( - {}} - /> - ); - await waitFor(() => expect(screen.getByTestId('azure-monitor-metrics-query-editor')).toBeInTheDocument()); - - const resourceGroup = await screen.findByLabelText('Resource name'); - await selectOptionInTest(resourceGroup, 'ResourceName1'); - - expect(onChange).toHaveBeenLastCalledWith({ - ...mockQuery, - azureMonitor: { - ...mockQuery.azureMonitor, - resourceUri: '', - resourceName: 'resource-name-1', - metricNamespace: undefined, - metricName: undefined, - aggregation: undefined, - timeGrain: '', - dimensionFilters: [], - }, - }); - }); - - it('should change the metric name when selected', async () => { - const mockDatasource = createMockDatasource(); - const onChange = jest.fn(); - const mockQuery = createMockQuery(); - mockDatasource.azureMonitorDatasource.getMetricNames = jest.fn().mockResolvedValue([ - { - value: 'metric-a', - text: 'Metric A', - }, - { - value: 'metric-b', - text: 'Metric B', - }, - ]); - render( - {}} - /> - ); - await waitFor(() => expect(screen.getByTestId('azure-monitor-metrics-query-editor')).toBeInTheDocument()); - - const metrics = await screen.findByLabelText('Metric'); - await selectOptionInTest(metrics, 'Metric B'); - - expect(onChange).toHaveBeenLastCalledWith({ - ...mockQuery, - azureMonitor: { - ...mockQuery.azureMonitor, - metricName: 'metric-b', - aggregation: undefined, - timeGrain: '', - }, - }); - }); - - it('should change the aggregation type when selected', async () => { - const mockDatasource = createMockDatasource(); - const onChange = jest.fn(); - const mockQuery = createMockQuery(); - render( - {}} - /> - ); - await waitFor(() => expect(screen.getByTestId('azure-monitor-metrics-query-editor')).toBeInTheDocument()); - - const aggregation = await screen.findByLabelText('Aggregation'); - await selectOptionInTest(aggregation, 'Maximum'); - - expect(onChange).toHaveBeenLastCalledWith({ - ...mockQuery, - azureMonitor: { - ...mockQuery.azureMonitor, - aggregation: 'Maximum', - }, - }); - }); -}); +} diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/MetricsQueryEditor.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/MetricsQueryEditor.tsx index 6f082fe4390..c75e59029f7 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/MetricsQueryEditor.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/MetricsQueryEditor.tsx @@ -1,37 +1,31 @@ +import { css } from '@emotion/css'; import React from 'react'; import { PanelData } from '@grafana/data/src/types'; -import { InlineFieldRow } from '@grafana/ui'; +import { EditorRows, EditorRow, EditorFieldGroup } from '@grafana/experimental'; +import { config } from '@grafana/runtime'; +import { InlineFieldRow, useStyles2 } from '@grafana/ui'; -import Datasource from '../../datasource'; -import { AzureMonitorQuery, AzureMonitorOption, AzureMonitorErrorish } from '../../types'; -import SubscriptionField from '../SubscriptionField'; +import type Datasource from '../../datasource'; +import type { AzureMonitorQuery, AzureMonitorOption, AzureMonitorErrorish } from '../../types'; +import ResourceField from '../ResourceField'; +import { ResourceRowType } from '../ResourcePicker/types'; import AggregationField from './AggregationField'; import DimensionFields from './DimensionFields'; import LegendFormatField from './LegendFormatField'; import MetricNameField from './MetricNameField'; import MetricNamespaceField from './MetricNamespaceField'; -import ResourceGroupsField from './ResourceGroupsField'; -import ResourceNameField from './ResourceNameField'; -import ResourceTypeField from './ResourceTypeField'; +import NewDimensionFields from './NewDimensionFields'; import TimeGrainField from './TimeGrainField'; import TopField from './TopField'; -import { - useMetricNames, - useMetricNamespaces, - useResourceGroups, - useResourceNames, - useResourceTypes, - useSubscriptions, - useMetricMetadata, -} from './dataHooks'; +import { useMetricNames, useMetricNamespaces, useMetricMetadata } from './dataHooks'; +import { setResource } from './setQueryValue'; interface MetricsQueryEditorProps { data: PanelData | undefined; query: AzureMonitorQuery; datasource: Datasource; - subscriptionId?: string; onChange: (newQuery: AzureMonitorQuery) => void; variableOptionGroup: { label: string; options: AzureMonitorOption[] }; setError: (source: string, error: AzureMonitorErrorish | undefined) => void; @@ -41,133 +35,194 @@ const MetricsQueryEditor: React.FC = ({ data, query, datasource, - subscriptionId, variableOptionGroup, onChange, setError, }) => { + const styles = useStyles2(getStyles); + const metricsMetadata = useMetricMetadata(query, datasource, onChange); - const subscriptions = useSubscriptions(query, datasource, onChange, setError); - const resourceGroups = useResourceGroups(query, datasource, onChange, setError); - const resourceTypes = useResourceTypes(query, datasource, onChange, setError); - const resourceNames = useResourceNames(query, datasource, onChange, setError); - const metricNames = useMetricNames(query, datasource, onChange, setError); const metricNamespaces = useMetricNamespaces(query, datasource, onChange, setError); + const metricNames = useMetricNames(query, datasource, onChange, setError); + if (config.featureToggles.azureMonitorExperimentalUI) { + return ( + + + + + + + - return ( -
- - + + + + + + + + + + + + + + + + + + + + + + + + + + ); + } else { + return ( +
+ + + - + + + + + + + + + - - - - - - - - - - - - - - - - - - -
- ); +
+ ); + } }; +const getStyles = () => ({ + row: css({ + rowGap: 0, + }), +}); + export default MetricsQueryEditor; diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/ResourceGroupsField.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/ResourceGroupsField.tsx deleted file mode 100644 index fd9aa3fcfb2..00000000000 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/ResourceGroupsField.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import React, { useCallback, useMemo } from 'react'; - -import { SelectableValue } from '@grafana/data'; -import { Select } from '@grafana/ui'; - -import { AzureQueryEditorFieldProps, AzureMonitorOption } from '../../types'; -import { Field } from '../Field'; - -import { setResourceGroup } from './setQueryValue'; - -interface ResourceGroupsFieldProps extends AzureQueryEditorFieldProps { - resourceGroups: AzureMonitorOption[]; -} - -const ResourceGroupsField: React.FC = ({ - query, - resourceGroups, - variableOptionGroup, - onQueryChange, - setError, -}) => { - const handleChange = useCallback( - (change: SelectableValue) => { - const newQuery = setResourceGroup(query, change.value); - onQueryChange(newQuery); - }, - [onQueryChange, query] - ); - - const options = useMemo(() => [...resourceGroups, variableOptionGroup], [resourceGroups, variableOptionGroup]); - - return ( - - - - ); -}; - -export default ResourceNameField; diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/ResourceTypeField.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/ResourceTypeField.tsx deleted file mode 100644 index 3f1b2b5a69e..00000000000 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/ResourceTypeField.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import React, { useCallback, useMemo } from 'react'; - -import { SelectableValue } from '@grafana/data'; -import { Select } from '@grafana/ui'; - -import { AzureQueryEditorFieldProps, AzureMonitorOption } from '../../types'; -import { Field } from '../Field'; - -import { setResourceType } from './setQueryValue'; - -interface NamespaceFieldProps extends AzureQueryEditorFieldProps { - resourceTypes: AzureMonitorOption[]; -} - -const NamespaceField: React.FC = ({ - resourceTypes, - query, - variableOptionGroup, - onQueryChange, -}) => { - const handleChange = useCallback( - (change: SelectableValue) => { - if (!change.value) { - return; - } - - const newQuery = setResourceType(query, change.value); - onQueryChange(newQuery); - }, - [onQueryChange, query] - ); - - const options = useMemo(() => [...resourceTypes, variableOptionGroup], [resourceTypes, variableOptionGroup]); - - return ( - - {/* It's expected that the label reads Resource type but the property is metricDefinition */} - - - - - - ); - } - render() { const query = { ...defaultQuery, ...this.props.query, }; + const { queryType } = query; + return ( <> + {queryType === GrafanaQueryType.Search && ( + + Using this datasource to call the new search system is experimental, and subject to change at any time + without notice. + + )} setQuery(e.currentTarget.value)} + onKeyDown={handleSearchEnterKey} + onBlur={handleSearchBlur} + spellCheck={false} + /> + + + + + ); +} diff --git a/public/app/plugins/datasource/grafana/types.ts b/public/app/plugins/datasource/grafana/types.ts index 1773d251f97..76d83798fe5 100644 --- a/public/app/plugins/datasource/grafana/types.ts +++ b/public/app/plugins/datasource/grafana/types.ts @@ -1,5 +1,6 @@ import { DataQuery } from '@grafana/data'; import { LiveDataFilter } from '@grafana/runtime'; +import { SearchQuery } from 'app/features/search/service'; //---------------------------------------------- // Query @@ -22,8 +23,8 @@ export interface GrafanaQuery extends DataQuery { filter?: LiveDataFilter; buffer?: number; path?: string; // for list and read - query?: string; // for query endpoint -} // NOTE, query will have more field!!! + search?: SearchQuery; +} export const defaultQuery: GrafanaQuery = { refId: 'A', From 5ca6b7cd2d6cb1099168de559a8f30e0e4976cb4 Mon Sep 17 00:00:00 2001 From: "Grot (@grafanabot)" <43478413+grafanabot@users.noreply.github.com> Date: Fri, 10 Jun 2022 17:36:00 -0400 Subject: [PATCH 26/63] Panels: Fixes issue with showing 'Cannot visualize data' when there issue really was no data (#50485) (#50615) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (cherry picked from commit 56a9994a1348c82228a151596f051ab7d1848897) Co-authored-by: Torkel Ödegaard --- public/app/features/panel/components/PanelDataErrorView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app/features/panel/components/PanelDataErrorView.tsx b/public/app/features/panel/components/PanelDataErrorView.tsx index a58848c432c..a5173f79a9c 100644 --- a/public/app/features/panel/components/PanelDataErrorView.tsx +++ b/public/app/features/panel/components/PanelDataErrorView.tsx @@ -67,7 +67,7 @@ function getMessageFor( } // In some cases there is a data frame but with no fields - if (!data.series || data.series.length === 0 || (data.series.length === 1 && data.series[0].fields.length === 0)) { + if (!data.series || data.series.length === 0 || (data.series.length === 1 && data.series[0].length === 0)) { return fieldConfig?.defaults.noValue ?? 'No data'; } From 342af69edb425ce17b629370721ea7e0f1f2d025 Mon Sep 17 00:00:00 2001 From: "Grot (@grafanabot)" <43478413+grafanabot@users.noreply.github.com> Date: Sat, 11 Jun 2022 15:50:53 -0400 Subject: [PATCH 27/63] Explore: Fix log context scroll to bottom (#50600) (#50645) * Explore: Fix log context scroll to bottom * Update (cherry picked from commit 1231e0dd691e73d3daa5c9cb343c334ce3f3ba14) Co-authored-by: Ivana Huckova <30407135+ivanahuckova@users.noreply.github.com> --- packages/grafana-ui/src/components/Logs/LogMessageAnsi.tsx | 1 + packages/grafana-ui/src/components/Logs/LogRowContext.tsx | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/grafana-ui/src/components/Logs/LogMessageAnsi.tsx b/packages/grafana-ui/src/components/Logs/LogMessageAnsi.tsx index b7633b714b5..918ff631cc3 100644 --- a/packages/grafana-ui/src/components/Logs/LogMessageAnsi.tsx +++ b/packages/grafana-ui/src/components/Logs/LogMessageAnsi.tsx @@ -82,6 +82,7 @@ export class UnThemedLogMessageAnsi extends PureComponent { return chunks.map((chunk, index) => { const chunkText = this.props.highlight?.searchWords ? ( () as React.RefObject; useLayoutEffect(() => { - if (shouldScrollToBottom && listContainerRef.current) { + // We want to scroll to bottom only when we receive first 10 log lines + const shouldScrollRows = rows.length > 0 && rows.length <= 10; + if (shouldScrollToBottom && shouldScrollRows && listContainerRef.current) { setScrollTop(listContainerRef.current.offsetHeight); } - }, [shouldScrollToBottom]); + }, [shouldScrollToBottom, rows]); const headerProps = { row, From 98c540f51e835a368dd0fdaf00679de5dfadc3c6 Mon Sep 17 00:00:00 2001 From: "Grot (@grafanabot)" <43478413+grafanabot@users.noreply.github.com> Date: Sat, 11 Jun 2022 15:53:46 -0400 Subject: [PATCH 28/63] Loki: Fix issues with using query patterns (#50414) (#50646) * WIP * Loki: Fix running of query patterns * Remove console.log * Add test * Update empty line filter test (cherry picked from commit 88279dda1ca419d9886bdb9e056a1033775fa653) Co-authored-by: Ivana Huckova <30407135+ivanahuckova@users.noreply.github.com> --- .../querybuilder/LokiQueryModeller.test.ts | 2 +- .../loki/querybuilder/LokiQueryModeller.ts | 6 ++--- .../loki/querybuilder/operations.ts | 3 --- .../loki/querybuilder/parsing.test.ts | 23 +++++++++++++++++++ .../datasource/loki/querybuilder/parsing.ts | 12 +++++++--- 5 files changed, 36 insertions(+), 10 deletions(-) diff --git a/public/app/plugins/datasource/loki/querybuilder/LokiQueryModeller.test.ts b/public/app/plugins/datasource/loki/querybuilder/LokiQueryModeller.test.ts index da4c68b1134..9135368d0cd 100644 --- a/public/app/plugins/datasource/loki/querybuilder/LokiQueryModeller.test.ts +++ b/public/app/plugins/datasource/loki/querybuilder/LokiQueryModeller.test.ts @@ -73,7 +73,7 @@ describe('LokiQueryModeller', () => { labels: [{ label: 'app', op: '=', value: 'grafana' }], operations: [{ id: LokiOperationId.LineContains, params: [''] }], }) - ).toBe('{app="grafana"}'); + ).toBe('{app="grafana"} |= ``'); }); it('Can query with line filter contains not operation', () => { diff --git a/public/app/plugins/datasource/loki/querybuilder/LokiQueryModeller.ts b/public/app/plugins/datasource/loki/querybuilder/LokiQueryModeller.ts index 461b1688f72..0b68e34a4dc 100644 --- a/public/app/plugins/datasource/loki/querybuilder/LokiQueryModeller.ts +++ b/public/app/plugins/datasource/loki/querybuilder/LokiQueryModeller.ts @@ -94,7 +94,7 @@ export class LokiQueryModeller extends LokiAndPromQueryModellerBase { { id: LokiOperationId.LineContains, params: [''] }, { id: LokiOperationId.Logfmt, params: [] }, { id: LokiOperationId.LabelFilterNoErrors, params: [] }, - { id: LokiOperationId.Unwrap, params: [] }, + { id: LokiOperationId.Unwrap, params: [''] }, { id: LokiOperationId.LabelFilterNoErrors, params: [] }, { id: LokiOperationId.SumOverTime, params: ['$__interval'] }, { id: LokiOperationId.Sum, params: [] }, @@ -106,7 +106,7 @@ export class LokiQueryModeller extends LokiAndPromQueryModellerBase { operations: [ { id: LokiOperationId.LineContains, params: [''] }, { id: LokiOperationId.CountOverTime, params: ['$__interval'] }, - { id: LokiOperationId.Sum, params: ['label'] }, + { id: LokiOperationId.Sum, params: [] }, ], }, { @@ -149,7 +149,7 @@ export class LokiQueryModeller extends LokiAndPromQueryModellerBase { }, { name: 'Metrics query for extracted quantile', - // quantile_over_time(0.99,{} | logfmt | unwrap latency[$__interval]) by () + // quantile_over_time(0.5,{} | logfmt | unwrap latency[$__interval]) by () operations: [ { id: LokiOperationId.Logfmt, params: [] }, { id: LokiOperationId.LabelFilterNoErrors, params: [] }, diff --git a/public/app/plugins/datasource/loki/querybuilder/operations.ts b/public/app/plugins/datasource/loki/querybuilder/operations.ts index 31cf3aca86c..e5418e8e550 100644 --- a/public/app/plugins/datasource/loki/querybuilder/operations.ts +++ b/public/app/plugins/datasource/loki/querybuilder/operations.ts @@ -400,9 +400,6 @@ function operationWithRangeVectorRendererAndParam( function getLineFilterRenderer(operation: string) { return function lineFilterRenderer(model: QueryBuilderOperation, def: QueryBuilderOperationDef, innerExpr: string) { - if (model.params[0] === '') { - return innerExpr; - } return `${innerExpr} ${operation} \`${model.params[0]}\``; }; } diff --git a/public/app/plugins/datasource/loki/querybuilder/parsing.test.ts b/public/app/plugins/datasource/loki/querybuilder/parsing.test.ts index ca0fbaee3c3..d12dcaa5d41 100644 --- a/public/app/plugins/datasource/loki/querybuilder/parsing.test.ts +++ b/public/app/plugins/datasource/loki/querybuilder/parsing.test.ts @@ -374,6 +374,29 @@ describe('buildVisualQueryFromString', () => { ); }); + it('parses metrics query with vector aggregation with number', () => { + expect( + buildVisualQueryFromString('topk(10, sum(count_over_time({app="frontend"} | logfmt | __error__=`` [5m])))') + ).toEqual( + noErrors({ + labels: [ + { + op: '=', + value: 'frontend', + label: 'app', + }, + ], + operations: [ + { id: 'logfmt', params: [] }, + { id: '__label_filter_no_errors', params: [] }, + { id: 'count_over_time', params: ['5m'] }, + { id: 'sum', params: [] }, + { id: 'topk', params: [10] }, + ], + }) + ); + }); + it('parses template variables in strings', () => { expect(buildVisualQueryFromString('{instance="$label_variable"}')).toEqual( noErrors({ diff --git a/public/app/plugins/datasource/loki/querybuilder/parsing.ts b/public/app/plugins/datasource/loki/querybuilder/parsing.ts index f46bb546d4c..f0fff40f33e 100644 --- a/public/app/plugins/datasource/loki/querybuilder/parsing.ts +++ b/public/app/plugins/datasource/loki/querybuilder/parsing.ts @@ -360,7 +360,13 @@ function handleVectorAggregation(expr: string, node: SyntaxNode, context: Contex let funcName = getString(expr, nameNode); const grouping = node.getChild('Grouping'); - const labels: string[] = []; + const params = []; + + const numberNode = node.getChild('Number'); + + if (numberNode) { + params.push(Number(getString(expr, numberNode))); + } if (grouping) { const byModifier = grouping.getChild(`By`); @@ -373,11 +379,11 @@ function handleVectorAggregation(expr: string, node: SyntaxNode, context: Contex funcName = `__${funcName}_without`; } - labels.push(...getAllByType(expr, grouping, 'Identifier')); + params.push(...getAllByType(expr, grouping, 'Identifier')); } const metricExpr = node.getChild('MetricExpr'); - const op: QueryBuilderOperation = { id: funcName, params: labels }; + const op: QueryBuilderOperation = { id: funcName, params }; if (metricExpr) { handleExpression(expr, metricExpr, context); From b4dba8d14eaae251bd3d0b57227236826e7ead7d Mon Sep 17 00:00:00 2001 From: "Grot (@grafanabot)" <43478413+grafanabot@users.noreply.github.com> Date: Mon, 13 Jun 2022 02:50:50 -0400 Subject: [PATCH 29/63] logs: histogram: handle backend-mode queries (#50535) (#50650) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (cherry picked from commit a0e5a4eba28f11552d389aa0c208d307c5037858) Co-authored-by: Gábor Farkas --- public/app/core/logs_model.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/public/app/core/logs_model.ts b/public/app/core/logs_model.ts index 7ab66c6f587..48facc8143f 100644 --- a/public/app/core/logs_model.ts +++ b/public/app/core/logs_model.ts @@ -702,7 +702,17 @@ export function queryLogsVolume( observer.complete(); }, next: (dataQueryResponse: DataQueryResponse) => { - rawLogsVolume = rawLogsVolume.concat(dataQueryResponse.data.map(toDataFrame)); + const { error } = dataQueryResponse; + if (error !== undefined) { + observer.next({ + state: LoadingState.Error, + error, + data: [], + }); + observer.error(error); + } else { + rawLogsVolume = rawLogsVolume.concat(dataQueryResponse.data.map(toDataFrame)); + } }, error: (error) => { observer.next({ From 5390fa57e9a3c384c80884b657b6f9967925f859 Mon Sep 17 00:00:00 2001 From: "Grot (@grafanabot)" <43478413+grafanabot@users.noreply.github.com> Date: Mon, 13 Jun 2022 05:39:56 -0400 Subject: [PATCH 30/63] Expression: Execute hidden expressions (#50636) (#50659) (cherry picked from commit ede7b29db5e467fc20dd37d5883214b1ca9d6895) Co-authored-by: Sriram --- public/app/features/expressions/ExpressionDatasource.ts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/public/app/features/expressions/ExpressionDatasource.ts b/public/app/features/expressions/ExpressionDatasource.ts index 149310a71be..5376e396377 100644 --- a/public/app/features/expressions/ExpressionDatasource.ts +++ b/public/app/features/expressions/ExpressionDatasource.ts @@ -35,13 +35,6 @@ export class ExpressionDatasourceApi extends DataSourceWithBackend): Observable { let targets = request.targets.map(async (query: ExpressionQuery): Promise => { const ds = await getDataSourceSrv().get(query.datasource); From bc2f94100480e63913e1c3f0b98fcb13ee683ef0 Mon Sep 17 00:00:00 2001 From: Dan Cech Date: Mon, 13 Jun 2022 07:12:00 -0400 Subject: [PATCH 31/63] clean up duplicated user creation code (#50178) (#50327) * clean up duplicated user creation code * remove unused duplicate getOrCreateOrg function * fix up tests (cherry picked from commit 9350ab781c65349c2c4d28c56e1af07b074bd13b) --- pkg/api/org_users_test.go | 5 +- .../dashboards/database/database_test.go | 7 +- .../database/permissions/database_acl_test.go | 14 +- pkg/services/serviceaccounts/api/api_test.go | 6 +- pkg/services/sqlstore/org.go | 47 ------ pkg/services/sqlstore/org_test.go | 15 +- pkg/services/sqlstore/sqlstore.go | 4 +- pkg/services/sqlstore/user.go | 151 ++---------------- pkg/services/sqlstore/user_test.go | 7 +- .../api/alerting/api_alertmanager_test.go | 3 + .../api/dashboards/api_dashboards_test.go | 4 + pkg/tests/api/plugins/api_plugins_test.go | 3 + 12 files changed, 49 insertions(+), 217 deletions(-) diff --git a/pkg/api/org_users_test.go b/pkg/api/org_users_test.go index f2bc6297baa..3634879e3f0 100644 --- a/pkg/api/org_users_test.go +++ b/pkg/api/org_users_test.go @@ -19,13 +19,12 @@ import ( "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/services/sqlstore/mockstore" - "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/util" ) func setUpGetOrgUsersDB(t *testing.T, sqlStore *sqlstore.SQLStore) { - setting.AutoAssignOrg = true - setting.AutoAssignOrgId = int(testOrgID) + sqlStore.Cfg.AutoAssignOrg = true + sqlStore.Cfg.AutoAssignOrgId = int(testOrgID) _, err := sqlStore.CreateUser(context.Background(), models.CreateUserCommand{Email: "testUser@grafana.com", Login: testUserLogin}) require.NoError(t, err) diff --git a/pkg/services/dashboards/database/database_test.go b/pkg/services/dashboards/database/database_test.go index c568f646b7c..600cbf8a9a7 100644 --- a/pkg/services/dashboards/database/database_test.go +++ b/pkg/services/dashboards/database/database_test.go @@ -21,7 +21,6 @@ import ( "github.com/grafana/grafana/pkg/services/sqlstore/searchstore" "github.com/grafana/grafana/pkg/services/star" "github.com/grafana/grafana/pkg/services/star/starimpl" - "github.com/grafana/grafana/pkg/setting" ) func TestIntegrationDashboardDataAccess(t *testing.T) { @@ -661,9 +660,9 @@ func insertTestRule(t *testing.T, sqlStore *sqlstore.SQLStore, foderOrgID int64, func CreateUser(t *testing.T, sqlStore *sqlstore.SQLStore, name string, role string, isAdmin bool) models.User { t.Helper() - setting.AutoAssignOrg = true - setting.AutoAssignOrgId = 1 - setting.AutoAssignOrgRole = role + sqlStore.Cfg.AutoAssignOrg = true + sqlStore.Cfg.AutoAssignOrgId = 1 + sqlStore.Cfg.AutoAssignOrgRole = role currentUserCmd := models.CreateUserCommand{Login: name, Email: name + "@test.com", Name: "a " + name, IsAdmin: isAdmin} currentUser, err := sqlStore.CreateUser(context.Background(), currentUserCmd) require.NoError(t, err) diff --git a/pkg/services/dashboards/database/permissions/database_acl_test.go b/pkg/services/dashboards/database/permissions/database_acl_test.go index c4e36ef3541..aca261db397 100644 --- a/pkg/services/dashboards/database/permissions/database_acl_test.go +++ b/pkg/services/dashboards/database/permissions/database_acl_test.go @@ -5,15 +5,13 @@ package permissions import ( "context" - "github.com/grafana/grafana/pkg/components/simplejson" - "github.com/grafana/grafana/pkg/services/dashboards/database" - "github.com/grafana/grafana/pkg/services/sqlstore" - "github.com/grafana/grafana/pkg/setting" "testing" "time" + "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/models" - + "github.com/grafana/grafana/pkg/services/dashboards/database" + "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/stretchr/testify/require" ) @@ -256,9 +254,9 @@ func TestIntegrationDashboardAclDataAccess(t *testing.T) { func createUser(t *testing.T, sqlStore *sqlstore.SQLStore, name string, role string, isAdmin bool) models.User { t.Helper() - setting.AutoAssignOrg = true - setting.AutoAssignOrgId = 1 - setting.AutoAssignOrgRole = role + sqlStore.Cfg.AutoAssignOrg = true + sqlStore.Cfg.AutoAssignOrgId = 1 + sqlStore.Cfg.AutoAssignOrgRole = role currentUserCmd := models.CreateUserCommand{Login: name, Email: name + "@test.com", Name: "a " + name, IsAdmin: isAdmin} currentUser, err := sqlStore.CreateUser(context.Background(), currentUserCmd) require.NoError(t, err) diff --git a/pkg/services/serviceaccounts/api/api_test.go b/pkg/services/serviceaccounts/api/api_test.go index c6db4b27b23..06a2200daca 100644 --- a/pkg/services/serviceaccounts/api/api_test.go +++ b/pkg/services/serviceaccounts/api/api_test.go @@ -36,10 +36,10 @@ func TestServiceAccountsAPI_CreateServiceAccount(t *testing.T) { store := sqlstore.InitTestDB(t) svcmock := tests.ServiceAccountMock{} - autoAssignOrg := setting.AutoAssignOrg - setting.AutoAssignOrg = true + autoAssignOrg := store.Cfg.AutoAssignOrg + store.Cfg.AutoAssignOrg = true defer func() { - setting.AutoAssignOrg = autoAssignOrg + store.Cfg.AutoAssignOrg = autoAssignOrg }() orgCmd := &models.CreateOrgCommand{Name: "Some Test Org"} diff --git a/pkg/services/sqlstore/org.go b/pkg/services/sqlstore/org.go index af65873fb47..182c5ff12ff 100644 --- a/pkg/services/sqlstore/org.go +++ b/pkg/services/sqlstore/org.go @@ -7,7 +7,6 @@ import ( "github.com/grafana/grafana/pkg/events" "github.com/grafana/grafana/pkg/models" - "github.com/grafana/grafana/pkg/setting" "xorm.io/xorm" ) @@ -317,49 +316,3 @@ func (ss *SQLStore) getOrCreateOrg(sess *DBSession, orgName string) (int64, erro return org.Id, nil } - -func getOrCreateOrg(sess *DBSession, orgName string) (int64, error) { - var org models.Org - if setting.AutoAssignOrg { - has, err := sess.Where("id=?", setting.AutoAssignOrgId).Get(&org) - if err != nil { - return 0, err - } - if has { - return org.Id, nil - } - - if setting.AutoAssignOrgId != 1 { - sqlog.Error("Could not create user: organization ID does not exist", "orgID", - setting.AutoAssignOrgId) - return 0, fmt.Errorf("could not create user: organization ID %d does not exist", - setting.AutoAssignOrgId) - } - - org.Name = MainOrgName - org.Id = int64(setting.AutoAssignOrgId) - } else { - org.Name = orgName - } - - org.Created = time.Now() - org.Updated = time.Now() - - if org.Id != 0 { - if _, err := sess.InsertId(&org); err != nil { - return 0, err - } - } else { - if _, err := sess.InsertOne(&org); err != nil { - return 0, err - } - } - - sess.publishAfterCommit(&events.OrgCreated{ - Timestamp: org.Created, - Id: org.Id, - Name: org.Name, - }) - - return org.Id, nil -} diff --git a/pkg/services/sqlstore/org_test.go b/pkg/services/sqlstore/org_test.go index ca9e6b14754..ebbd6f1d548 100644 --- a/pkg/services/sqlstore/org_test.go +++ b/pkg/services/sqlstore/org_test.go @@ -12,7 +12,6 @@ import ( "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/accesscontrol" - "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/util" "github.com/stretchr/testify/require" ) @@ -80,9 +79,9 @@ func TestIntegrationAccountDataAccess(t *testing.T) { }) t.Run("Given single org mode", func(t *testing.T) { - setting.AutoAssignOrg = true - setting.AutoAssignOrgId = 1 - setting.AutoAssignOrgRole = "Viewer" + sqlStore.Cfg.AutoAssignOrg = true + sqlStore.Cfg.AutoAssignOrgId = 1 + sqlStore.Cfg.AutoAssignOrgRole = "Viewer" t.Run("Users should be added to default organization", func(t *testing.T) { ac1cmd := models.CreateUserCommand{Login: "ac1", Email: "ac1@test.com", Name: "ac1 name"} @@ -107,9 +106,9 @@ func TestIntegrationAccountDataAccess(t *testing.T) { t.Run("Given single org and 2 users inserted", func(t *testing.T) { sqlStore = InitTestDB(t) - setting.AutoAssignOrg = true - setting.AutoAssignOrgId = 1 - setting.AutoAssignOrgRole = "Viewer" + sqlStore.Cfg.AutoAssignOrg = true + sqlStore.Cfg.AutoAssignOrgId = 1 + sqlStore.Cfg.AutoAssignOrgRole = "Viewer" ac1cmd := models.CreateUserCommand{Login: "ac1", Email: "ac1@test.com", Name: "ac1 name"} ac2cmd := models.CreateUserCommand{Login: "ac2", Email: "ac2@test.com", Name: "ac2 name"} @@ -148,7 +147,7 @@ func TestIntegrationAccountDataAccess(t *testing.T) { t.Run("Given two saved users", func(t *testing.T) { sqlStore = InitTestDB(t) - setting.AutoAssignOrg = false + sqlStore.Cfg.AutoAssignOrg = false ac1cmd := models.CreateUserCommand{Login: "ac1", Email: "ac1@test.com", Name: "ac1 name"} ac2cmd := models.CreateUserCommand{Login: "ac2", Email: "ac2@test.com", Name: "ac2 name", IsAdmin: true} diff --git a/pkg/services/sqlstore/sqlstore.go b/pkg/services/sqlstore/sqlstore.go index 6eb5ae14dfd..e4aa22b44f9 100644 --- a/pkg/services/sqlstore/sqlstore.go +++ b/pkg/services/sqlstore/sqlstore.go @@ -173,12 +173,12 @@ func (ss *SQLStore) ensureMainOrgAndAdminUser() error { // ensure admin user if !ss.Cfg.DisableInitAdminCreation { ss.log.Debug("Creating default admin user") - if _, err := ss.createUser(ctx, sess, userCreationArgs{ + if _, err := ss.createUser(ctx, sess, models.CreateUserCommand{ Login: ss.Cfg.AdminUser, Email: ss.Cfg.AdminUser + "@localhost", Password: ss.Cfg.AdminPassword, IsAdmin: true, - }, false); err != nil { + }); err != nil { return fmt.Errorf("failed to create admin user: %s", err) } diff --git a/pkg/services/sqlstore/user.go b/pkg/services/sqlstore/user.go index 51f68366bfb..9cd80da3967 100644 --- a/pkg/services/sqlstore/user.go +++ b/pkg/services/sqlstore/user.go @@ -11,51 +11,15 @@ import ( "github.com/grafana/grafana/pkg/events" "github.com/grafana/grafana/pkg/models" ac "github.com/grafana/grafana/pkg/services/accesscontrol" - "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/util" ) -func getOrgIdForNewUser(sess *DBSession, cmd models.CreateUserCommand) (int64, error) { - if cmd.SkipOrgSetup { - return -1, nil - } - - if setting.AutoAssignOrg && cmd.OrgId != 0 { - err := verifyExistingOrg(sess, cmd.OrgId) - if err != nil { +func (ss *SQLStore) getOrgIDForNewUser(sess *DBSession, args models.CreateUserCommand) (int64, error) { + if ss.Cfg.AutoAssignOrg && args.OrgId != 0 { + if err := verifyExistingOrg(sess, args.OrgId); err != nil { return -1, err } - return cmd.OrgId, nil - } - - orgName := cmd.OrgName - if len(orgName) == 0 { - orgName = util.StringsFallback2(cmd.Email, cmd.Login) - } - - return getOrCreateOrg(sess, orgName) -} - -type userCreationArgs struct { - Login string - Email string - Name string - Company string - Password string - IsAdmin bool - IsDisabled bool - EmailVerified bool - OrgID int64 - OrgName string - DefaultOrgRole string -} - -func (ss *SQLStore) getOrgIDForNewUser(sess *DBSession, args userCreationArgs) (int64, error) { - if ss.Cfg.AutoAssignOrg && args.OrgID != 0 { - if err := verifyExistingOrg(sess, args.OrgID); err != nil { - return -1, err - } - return args.OrgID, nil + return args.OrgId, nil } orgName := args.OrgName @@ -67,10 +31,10 @@ func (ss *SQLStore) getOrgIDForNewUser(sess *DBSession, args userCreationArgs) ( } // createUser creates a user in the database -func (ss *SQLStore) createUser(ctx context.Context, sess *DBSession, args userCreationArgs, skipOrgSetup bool) (models.User, error) { +func (ss *SQLStore) createUser(ctx context.Context, sess *DBSession, args models.CreateUserCommand) (models.User, error) { var user models.User var orgID int64 = -1 - if !skipOrgSetup { + if !args.SkipOrgSetup { var err error orgID, err = ss.getOrgIDForNewUser(sess, args) if err != nil { @@ -103,7 +67,7 @@ func (ss *SQLStore) createUser(ctx context.Context, sess *DBSession, args userCr Created: time.Now(), Updated: time.Now(), LastSeenAt: time.Now().AddDate(-10, 0, 0), - IsServiceAccount: false, + IsServiceAccount: args.IsServiceAccount, } salt, err := util.GetRandomString(10) @@ -140,7 +104,7 @@ func (ss *SQLStore) createUser(ctx context.Context, sess *DBSession, args userCr }) // create org user link - if !skipOrgSetup { + if !args.SkipOrgSetup { orgUser := models.OrgUser{ OrgId: orgID, UserId: user.Id, @@ -166,101 +130,12 @@ func (ss *SQLStore) createUser(ctx context.Context, sess *DBSession, args userCr } func (ss *SQLStore) CreateUser(ctx context.Context, cmd models.CreateUserCommand) (*models.User, error) { - var user *models.User - err := ss.WithTransactionalDbSession(ctx, func(sess *DBSession) error { - orgId, err := getOrgIdForNewUser(sess, cmd) - if err != nil { - return err - } - - if cmd.Email == "" { - cmd.Email = cmd.Login - } - - exists, err := sess.Where("email=? OR login=?", cmd.Email, cmd.Login).Get(&models.User{}) - if err != nil { - return err - } - if exists { - return models.ErrUserAlreadyExists - } - - // create user - user = &models.User{ - Email: cmd.Email, - Name: cmd.Name, - Login: cmd.Login, - Company: cmd.Company, - IsAdmin: cmd.IsAdmin, - IsDisabled: cmd.IsDisabled, - OrgId: orgId, - EmailVerified: cmd.EmailVerified, - Created: time.Now(), - Updated: time.Now(), - LastSeenAt: time.Now().AddDate(-10, 0, 0), - IsServiceAccount: cmd.IsServiceAccount, - } - - salt, err := util.GetRandomString(10) - if err != nil { - return err - } - user.Salt = salt - rands, err := util.GetRandomString(10) - if err != nil { - return err - } - user.Rands = rands - - if len(cmd.Password) > 0 { - encodedPassword, err := util.EncodePassword(cmd.Password, user.Salt) - if err != nil { - return err - } - user.Password = encodedPassword - } - - sess.UseBool("is_admin") - - if _, err := sess.Insert(user); err != nil { - return err - } - - sess.publishAfterCommit(&events.UserCreated{ - Timestamp: user.Created, - Id: user.Id, - Name: user.Name, - Login: user.Login, - Email: user.Email, - }) - - // create org user link - if !cmd.SkipOrgSetup { - orgUser := models.OrgUser{ - OrgId: orgId, - UserId: user.Id, - Role: models.ROLE_ADMIN, - Created: time.Now(), - Updated: time.Now(), - } - - if setting.AutoAssignOrg && !user.IsAdmin { - if len(cmd.DefaultOrgRole) > 0 { - orgUser.Role = models.RoleType(cmd.DefaultOrgRole) - } else { - orgUser.Role = models.RoleType(setting.AutoAssignOrgRole) - } - } - - if _, err = sess.Insert(&orgUser); err != nil { - return err - } - } - - return nil + var user models.User + createErr := ss.WithTransactionalDbSession(ctx, func(sess *DBSession) (err error) { + user, err = ss.createUser(ctx, sess, cmd) + return }) - - return user, err + return &user, createErr } func notServiceAccountFilter(ss *SQLStore) string { diff --git a/pkg/services/sqlstore/user_test.go b/pkg/services/sqlstore/user_test.go index 62f02da4ccc..d3803fa0c97 100644 --- a/pkg/services/sqlstore/user_test.go +++ b/pkg/services/sqlstore/user_test.go @@ -9,7 +9,6 @@ import ( "testing" "github.com/grafana/grafana/pkg/models" - "github.com/grafana/grafana/pkg/setting" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -78,10 +77,10 @@ func TestIntegrationUserDataAccess(t *testing.T) { t.Run("Testing DB - create user assigned to other organization", func(t *testing.T) { ss = InitTestDB(t) - autoAssignOrg := setting.AutoAssignOrg - setting.AutoAssignOrg = true + autoAssignOrg := ss.Cfg.AutoAssignOrg + ss.Cfg.AutoAssignOrg = true defer func() { - setting.AutoAssignOrg = autoAssignOrg + ss.Cfg.AutoAssignOrg = autoAssignOrg }() orgCmd := &models.CreateOrgCommand{Name: "Some Test Org"} diff --git a/pkg/tests/api/alerting/api_alertmanager_test.go b/pkg/tests/api/alerting/api_alertmanager_test.go index 705f882d9a9..4f3adaff6da 100644 --- a/pkg/tests/api/alerting/api_alertmanager_test.go +++ b/pkg/tests/api/alerting/api_alertmanager_test.go @@ -2743,6 +2743,9 @@ func rulesNamespaceWithoutVariableValues(t *testing.T, b []byte) (string, map[st func createUser(t *testing.T, store *sqlstore.SQLStore, cmd models.CreateUserCommand) int64 { t.Helper() + store.Cfg.AutoAssignOrg = true + store.Cfg.AutoAssignOrgId = 1 + u, err := store.CreateUser(context.Background(), cmd) require.NoError(t, err) return u.Id diff --git a/pkg/tests/api/dashboards/api_dashboards_test.go b/pkg/tests/api/dashboards/api_dashboards_test.go index 9292002ee36..421a1023e76 100644 --- a/pkg/tests/api/dashboards/api_dashboards_test.go +++ b/pkg/tests/api/dashboards/api_dashboards_test.go @@ -92,6 +92,10 @@ func TestDashboardQuota(t *testing.T) { func createUser(t *testing.T, store *sqlstore.SQLStore, cmd models.CreateUserCommand) int64 { t.Helper() + + store.Cfg.AutoAssignOrg = true + store.Cfg.AutoAssignOrgId = 1 + u, err := store.CreateUser(context.Background(), cmd) require.NoError(t, err) return u.Id diff --git a/pkg/tests/api/plugins/api_plugins_test.go b/pkg/tests/api/plugins/api_plugins_test.go index bf44d6202f4..a63281d4b0f 100644 --- a/pkg/tests/api/plugins/api_plugins_test.go +++ b/pkg/tests/api/plugins/api_plugins_test.go @@ -107,6 +107,9 @@ func TestPlugins(t *testing.T) { func createUser(t *testing.T, store *sqlstore.SQLStore, cmd models.CreateUserCommand) { t.Helper() + store.Cfg.AutoAssignOrg = true + store.Cfg.AutoAssignOrgId = 1 + _, err := store.CreateUser(context.Background(), cmd) require.NoError(t, err) } From 4d1ee06b1b07fd1ebdd365df2c6ad87ebc781b8b Mon Sep 17 00:00:00 2001 From: "Grot (@grafanabot)" <43478413+grafanabot@users.noreply.github.com> Date: Mon, 13 Jun 2022 08:26:04 -0400 Subject: [PATCH 32/63] Prometheus: Don't show undefined for step in collapsed options in query editor when value is "auto" (#50511) (#50658) (cherry picked from commit bd04b776b53915e30adab57de62660ba54526d0b) Co-authored-by: Andrej Ocenas --- .../querybuilder/components/PromQueryBuilderOptions.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.tsx b/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.tsx index a071fe5230f..f2d8ab27671 100644 --- a/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.tsx +++ b/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.tsx @@ -127,7 +127,7 @@ function getCollapsedInfo(query: PromQuery, formatOption: string, queryType: str items.push(`Legend: ${getLegendModeLabel(query.legendFormat)}`); items.push(`Format: ${formatOption}`); - items.push(`Step ${query.interval}`); + items.push(`Step: ${query.interval ?? 'auto'}`); items.push(`Type: ${queryType}`); if (query.exemplar) { From 4eeb6750f0cb134070d9599c08e94b31d0cab2da Mon Sep 17 00:00:00 2001 From: "Grot (@grafanabot)" <43478413+grafanabot@users.noreply.github.com> Date: Mon, 13 Jun 2022 08:27:45 -0400 Subject: [PATCH 33/63] add missing return (#50667) (#50675) (cherry picked from commit 8da9b4f2b5d23d486b198b4b9288670346459978) Co-authored-by: Will Browne --- pkg/services/datasourceproxy/datasourceproxy.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/services/datasourceproxy/datasourceproxy.go b/pkg/services/datasourceproxy/datasourceproxy.go index 9a8a003b2a7..6a1f196c62b 100644 --- a/pkg/services/datasourceproxy/datasourceproxy.go +++ b/pkg/services/datasourceproxy/datasourceproxy.go @@ -86,6 +86,7 @@ func (p *DataSourceProxyService) ProxyDatasourceRequestWithID(c *models.ReqConte ds, err := p.DataSourceCache.GetDatasource(c.Req.Context(), dsID, c.SignedInUser, c.SkipCache) if err != nil { toAPIError(c, err) + return } p.proxyDatasourceRequest(c, ds) } From 001f3709aec64917af66d6437a7f7618cda801b6 Mon Sep 17 00:00:00 2001 From: Carl Bergquist Date: Mon, 13 Jun 2022 15:19:07 +0200 Subject: [PATCH 34/63] Instrumentation: Add feature toggle for logging requests instrumented as unknown (#50566) (#50670) Signed-off-by: bergquist (cherry picked from commit 2d6e69226be20a7f001c7792bda8df68d90287c6) --- .../src/types/featureToggles.gen.ts | 1 + pkg/middleware/request_metrics.go | 18 ++++++++++++------ pkg/services/featuremgmt/registry.go | 5 +++++ pkg/services/featuremgmt/toggles_gen.go | 4 ++++ 4 files changed, 22 insertions(+), 6 deletions(-) diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index 50fd56fa9ff..217ff17ceb9 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -58,4 +58,5 @@ export interface FeatureToggles { prometheusStreamingJSONParser?: boolean; validateDashboardsOnSave?: boolean; prometheusWideSeries?: boolean; + logRequestsInstrumentedAsUnknown?: boolean; } diff --git a/pkg/middleware/request_metrics.go b/pkg/middleware/request_metrics.go index a91b6849f1b..b60d9c75868 100644 --- a/pkg/middleware/request_metrics.go +++ b/pkg/middleware/request_metrics.go @@ -6,6 +6,7 @@ import ( "strings" "time" + "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/metrics" "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/services/featuremgmt" @@ -46,6 +47,8 @@ func init() { // RequestMetrics is a middleware handler that instruments the request. func RequestMetrics(features featuremgmt.FeatureToggles) web.Handler { + log := log.New("middleware.request-metrics") + return func(res http.ResponseWriter, req *http.Request, c *web.Context) { if strings.HasPrefix(c.Req.URL.Path, "/public/") || c.Req.URL.Path == "robots.txt" || c.Req.URL.Path == "/metrics" { c.Next() @@ -59,15 +62,18 @@ func RequestMetrics(features featuremgmt.FeatureToggles) web.Handler { c.Map(c.Req) c.Next() - handler := "unknown" - - if routeOperation, exists := RouteOperationNameFromContext(c.Req.Context()); exists { - handler = routeOperation - } - status := rw.Status() code := sanitizeCode(status) + handler := "unknown" + if routeOperation, exists := RouteOperationNameFromContext(c.Req.Context()); exists { + handler = routeOperation + } else { + if features.IsEnabled(featuremgmt.FlagLogRequestsInstrumentedAsUnknown) { + log.Warn("request instrumented as unknown", "path", c.Req.URL.Path, "status_code", status) + } + } + // avoiding the sanitize functions for in the new instrumentation // since they dont make much sense. We should remove them later. histogram := httpRequestDurationHistogram. diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index 69506473013..8ff5c56bb49 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -237,5 +237,10 @@ var ( Description: "Enable wide series responses in the Prometheus datasource", State: FeatureStateAlpha, }, + { + Name: "logRequestsInstrumentedAsUnknown", + Description: "Logs the path for requests that are instrumented as unknown", + State: FeatureStateAlpha, + }, } ) diff --git a/pkg/services/featuremgmt/toggles_gen.go b/pkg/services/featuremgmt/toggles_gen.go index 95d0cc121fe..0bdbedddfa9 100644 --- a/pkg/services/featuremgmt/toggles_gen.go +++ b/pkg/services/featuremgmt/toggles_gen.go @@ -174,4 +174,8 @@ const ( // FlagPrometheusWideSeries // Enable wide series responses in the Prometheus datasource FlagPrometheusWideSeries = "prometheusWideSeries" + + // FlagLogRequestsInstrumentedAsUnknown + // Logs the path for requests that are instrumented as unknown + FlagLogRequestsInstrumentedAsUnknown = "logRequestsInstrumentedAsUnknown" ) From da76c87bfe914e1d30fc55fd0bfeaa80ed3f1082 Mon Sep 17 00:00:00 2001 From: "Grot (@grafanabot)" <43478413+grafanabot@users.noreply.github.com> Date: Mon, 13 Jun 2022 09:47:36 -0400 Subject: [PATCH 35/63] Loki: Fix showing of duplicated label values in dropdown in query builder (#50680) (#50688) * Prometheus,Loki: Fix showing of duplicated values in dropdown * Use different more readable solution * Update (cherry picked from commit 77bdbe1deac3153859d2173b7a3ff029a76a64c3) Co-authored-by: Ivana Huckova <30407135+ivanahuckova@users.noreply.github.com> --- .../prometheus/querybuilder/shared/LabelFilterItem.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/public/app/plugins/datasource/prometheus/querybuilder/shared/LabelFilterItem.tsx b/public/app/plugins/datasource/prometheus/querybuilder/shared/LabelFilterItem.tsx index f3dd25134af..44800ebb69b 100644 --- a/public/app/plugins/datasource/prometheus/querybuilder/shared/LabelFilterItem.tsx +++ b/public/app/plugins/datasource/prometheus/querybuilder/shared/LabelFilterItem.tsx @@ -1,3 +1,4 @@ +import { uniqBy } from 'lodash'; import React, { useState } from 'react'; import { SelectableValue, toOption } from '@grafana/data'; @@ -38,7 +39,11 @@ export function LabelFilterItem({ item, defaultOp, onChange, onDelete, onGetLabe }; const getOptions = (): SelectableValue[] => { - return [...getSelectOptionsFromString(item?.value).map(toOption), ...(state.labelValues ?? [])]; + const labelValues = state.labelValues ? [...state.labelValues] : []; + const selectedOptions = getSelectOptionsFromString(item?.value).map(toOption); + + // Remove possible duplicated values + return uniqBy([...selectedOptions, ...labelValues], 'value'); }; return ( From 95b545908fcbcd48ce639e617bfc375b9d5ce0b7 Mon Sep 17 00:00:00 2001 From: Ivana Huckova <30407135+ivanahuckova@users.noreply.github.com> Date: Mon, 13 Jun 2022 15:47:54 +0200 Subject: [PATCH 36/63] Loki, Prometheus: Show Query patterns in all editor modes (#50263) (#50653) --- .../components/LokiQueryEditorSelector.tsx | 34 ++++++++--------- .../components/PromQueryEditorSelector.tsx | 38 +++++++++---------- 2 files changed, 32 insertions(+), 40 deletions(-) diff --git a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryEditorSelector.tsx b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryEditorSelector.tsx index b0f045f1f4c..71f50c90722 100644 --- a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryEditorSelector.tsx +++ b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryEditorSelector.tsx @@ -79,25 +79,21 @@ export const LokiQueryEditorSelector = React.memo((props) onDismiss={() => setParseModalOpen(false)} /> - {editorMode === QueryEditorMode.Builder && ( - <> - { - const result = buildVisualQueryFromString(query.expr || ''); - result.query.operations = value?.operations!; - onChange({ - ...query, - expr: lokiQueryModeller.renderQuery(result.query), - }); - }} - options={lokiQueryModeller.getQueryPatterns().map((x) => ({ label: x.name, value: x }))} - /> - - - )} + { + const result = buildVisualQueryFromString(query.expr || ''); + result.query.operations = value?.operations!; + onChange({ + ...query, + expr: lokiQueryModeller.renderQuery(result.query), + }); + }} + options={lokiQueryModeller.getQueryPatterns().map((x) => ({ label: x.name, value: x }))} + /> + {app !== CoreApp.Explore && ( {contextSrv.isEditor && ( <> - + Edit playlist
-
+
); }; @@ -317,6 +319,7 @@ interface RolePickerSubMenuProps { disabledOptions?: Role[]; onSelect: (option: Role) => void; onClear?: () => void; + showOnLeft?: boolean; } export const RolePickerSubMenu = ({ @@ -325,6 +328,7 @@ export const RolePickerSubMenu = ({ disabledOptions, onSelect, onClear, + showOnLeft, }: RolePickerSubMenuProps): JSX.Element => { const theme = useTheme2(); const styles = getSelectStyles(theme); @@ -337,7 +341,10 @@ export const RolePickerSubMenu = ({ }; return ( -
+
{options.map((option, i) => ( @@ -506,7 +513,7 @@ export const RoleMenuGroupOption = React.forwardRef
{data.displayName || data.name} - +
{root && children && ( @@ -552,19 +559,25 @@ export const getStyles = (theme: GrafanaTheme2) => { padding-top: ${theme.spacing(1)}; } `, + menuLeft: css` + right: 0; + flex-direction: row-reverse; + `, subMenu: css` height: 100%; min-width: 260px; display: flex; flex-direction: column; - border-left-style: solid; - border-left-width: 1px; - border-left-color: ${theme.components.input.borderColor}; + border-left: 1px solid ${theme.components.input.borderColor}; & > div { padding-top: ${theme.spacing(1)}; } `, + subMenuLeft: css` + border-right: 1px solid ${theme.components.input.borderColor}; + border-left: unset; + `, groupHeader: css` padding: ${theme.spacing(0, 4)}; display: flex;