From 4cf097c4e5a9d210f8d4046e44ccff0ef842ff80 Mon Sep 17 00:00:00 2001 From: Ivana Huckova <30407135+ivanahuckova@users.noreply.github.com> Date: Wed, 11 Aug 2021 03:56:23 -0400 Subject: [PATCH 01/46] Small UX updates (#37694) --- .../loki/components/LokiCheatSheet.tsx | 17 +++++++++++------ .../datasource/loki/language_provider.ts | 6 +++++- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/public/app/plugins/datasource/loki/components/LokiCheatSheet.tsx b/public/app/plugins/datasource/loki/components/LokiCheatSheet.tsx index aaaf45a95b3..924f12b52e9 100644 --- a/public/app/plugins/datasource/loki/components/LokiCheatSheet.tsx +++ b/public/app/plugins/datasource/loki/components/LokiCheatSheet.tsx @@ -36,7 +36,7 @@ const LOGQL_EXAMPLES = [ export default class LokiCheatSheet extends PureComponent, { userExamples: string[] }> { declare userLabelTimer: NodeJS.Timeout; state = { - userExamples: DEFAULT_EXAMPLES, + userExamples: [], }; componentDidMount() { @@ -81,23 +81,28 @@ export default class LokiCheatSheet extends PureComponent 0; return (

Loki Cheat Sheet

See your logs
-
Start by selecting a log stream from the Log labels selector.
- Alternatively, you can write a stream selector into the query field: + Start by selecting a log stream from the Log browser, or alternatively you can write a stream selector into + the query field.
- {this.renderExpression('{job="default/prometheus"}')} - {userExamples !== DEFAULT_EXAMPLES && userExamples.length > 0 ? ( + {hasUserExamples ? (
Here are some example streams from your logs:
{userExamples.map((example) => this.renderExpression(example))}
- ) : null} + ) : ( +
+
Here is an example of a log stream:
+ {this.renderExpression(DEFAULT_EXAMPLES[0])} +
+ )}
Combine stream selectors
diff --git a/public/app/plugins/datasource/loki/language_provider.ts b/public/app/plugins/datasource/loki/language_provider.ts index 9e275687ea0..a5fd487f3f7 100644 --- a/public/app/plugins/datasource/loki/language_provider.ts +++ b/public/app/plugins/datasource/loki/language_provider.ts @@ -424,7 +424,11 @@ export default class LokiLanguageProvider extends LanguageProvider { const res = await this.request(url, timeRange); if (Array.isArray(res)) { - this.labelKeys = res.slice().sort(); + const labels = res + .slice() + .sort() + .filter((label) => label !== '__name__'); + this.labelKeys = labels; } return []; From 181d6a78ed49257a96410247a83772b3ecf692c2 Mon Sep 17 00:00:00 2001 From: Ashley Harrison Date: Wed, 11 Aug 2021 09:10:41 +0100 Subject: [PATCH 02/46] Chore: Fix some more typescript strict errors (#37750) --- .../components/ImportDashboardOverview.tsx | 53 +++++++------------ .../plugins/admin/hooks/usePlugins.tsx | 2 +- .../components/LogsQueryField.test.tsx | 4 +- .../plugins/datasource/cloudwatch/types.ts | 4 +- .../components/InsightsConfig.tsx | 2 +- .../testdata/components/USAQueryEditor.tsx | 8 ++- scripts/ci-check-strict.sh | 2 +- 7 files changed, 31 insertions(+), 44 deletions(-) diff --git a/public/app/features/manage-dashboards/components/ImportDashboardOverview.tsx b/public/app/features/manage-dashboards/components/ImportDashboardOverview.tsx index 26fc2afa0df..92b2335140d 100644 --- a/public/app/features/manage-dashboards/components/ImportDashboardOverview.tsx +++ b/public/app/features/manage-dashboards/components/ImportDashboardOverview.tsx @@ -1,29 +1,33 @@ import React, { PureComponent } from 'react'; import { dateTimeFormat } from '@grafana/data'; import { Legend, Form } from '@grafana/ui'; -import { connect, MapDispatchToProps, MapStateToProps } from 'react-redux'; +import { connect, ConnectedProps } from 'react-redux'; import { ImportDashboardForm } from './ImportDashboardForm'; import { clearLoadedDashboard, importDashboard } from '../state/actions'; -import { DashboardInputs, DashboardSource, ImportDashboardDTO } from '../state/reducers'; +import { DashboardSource, ImportDashboardDTO } from '../state/reducers'; import { StoreState } from 'app/types'; import { locationService } from '@grafana/runtime'; -interface OwnProps {} +const mapStateToProps = (state: StoreState) => { + const searchObj = locationService.getSearchObject(); -interface ConnectedProps { - dashboard: ImportDashboardDTO; - inputs: DashboardInputs; - source: DashboardSource; - meta?: any; - folder: { id: number; title?: string }; -} + return { + dashboard: state.importDashboard.dashboard, + meta: state.importDashboard.meta, + source: state.importDashboard.source, + inputs: state.importDashboard.inputs, + folder: searchObj.folderId ? { id: Number(searchObj.folderId) } : { id: 0 }, + }; +}; -interface DispatchProps { - clearLoadedDashboard: typeof clearLoadedDashboard; - importDashboard: typeof importDashboard; -} +const mapDispatchToProps = { + clearLoadedDashboard, + importDashboard, +}; -type Props = OwnProps & ConnectedProps & DispatchProps; +const connector = connect(mapStateToProps, mapDispatchToProps); + +type Props = ConnectedProps; interface State { uidReset: boolean; @@ -109,22 +113,5 @@ class ImportDashboardOverviewUnConnected extends PureComponent { } } -const mapStateToProps: MapStateToProps = (state: StoreState) => { - const searchObj = locationService.getSearchObject(); - - return { - dashboard: state.importDashboard.dashboard, - meta: state.importDashboard.meta, - source: state.importDashboard.source, - inputs: state.importDashboard.inputs, - folder: searchObj.folderId ? { id: Number(searchObj.folderId) } : { id: 0 }, - }; -}; - -const mapDispatchToProps: MapDispatchToProps = { - clearLoadedDashboard, - importDashboard, -}; - -export const ImportDashboardOverview = connect(mapStateToProps, mapDispatchToProps)(ImportDashboardOverviewUnConnected); +export const ImportDashboardOverview = connector(ImportDashboardOverviewUnConnected); ImportDashboardOverview.displayName = 'ImportDashboardOverview'; diff --git a/public/app/features/plugins/admin/hooks/usePlugins.tsx b/public/app/features/plugins/admin/hooks/usePlugins.tsx index 4bfc908a5c7..3713462b2ee 100644 --- a/public/app/features/plugins/admin/hooks/usePlugins.tsx +++ b/public/app/features/plugins/admin/hooks/usePlugins.tsx @@ -65,7 +65,7 @@ export const usePluginsByFilter = (queries: PluginsByFilterType): FilteredPlugin const { loading, error, plugins } = usePlugins(); const filteredPlugins = plugins.filter((plugin) => - Object.keys(queries).every((query: keyof PluginsByFilterType) => + (Object.keys(queries) as Array).every((query) => typeof URLFilterHandlers[query] === 'function' ? URLFilterHandlers[query](plugin, queries[query]) : true ) ); diff --git a/public/app/plugins/datasource/cloudwatch/components/LogsQueryField.test.tsx b/public/app/plugins/datasource/cloudwatch/components/LogsQueryField.test.tsx index da1a4825069..633988f48d2 100644 --- a/public/app/plugins/datasource/cloudwatch/components/LogsQueryField.test.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/LogsQueryField.test.tsx @@ -7,7 +7,9 @@ import { SelectableValue } from '@grafana/data'; // eslint-disable-next-line lodash/import-scope import _, { Cancelable } from 'lodash'; -jest.spyOn(_, 'debounce').mockImplementation((func: ((...args: any) => any) & Cancelable, wait: number) => func); +jest + .spyOn(_, 'debounce') + .mockImplementation((func: (...args: any) => any, wait?: number) => func as typeof func & Cancelable); describe('CloudWatchLogsQueryField', () => { it('updates upstream query log groups on region change', async () => { diff --git a/public/app/plugins/datasource/cloudwatch/types.ts b/public/app/plugins/datasource/cloudwatch/types.ts index 2804e33ca56..9d3b78cf291 100644 --- a/public/app/plugins/datasource/cloudwatch/types.ts +++ b/public/app/plugins/datasource/cloudwatch/types.ts @@ -65,8 +65,8 @@ export interface CloudWatchJsonData extends AwsAuthDataSourceJsonData { } export interface CloudWatchSecureJsonData extends AwsAuthDataSourceSecureJsonData { - accessKey: string; - secretKey: string; + accessKey?: string; + secretKey?: string; } export interface GetQueryResultsRequest { diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/InsightsConfig.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/InsightsConfig.tsx index 1caf5b3015e..1e57a9b18a3 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/InsightsConfig.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/InsightsConfig.tsx @@ -11,7 +11,7 @@ export interface Props { onUpdateSecureJsonDataOption: ( key: keyof AzureDataSourceSecureJsonData ) => (event: React.SyntheticEvent) => void; - onResetOptionKey: (key: string) => void; + onResetOptionKey: (key: keyof AzureDataSourceSecureJsonData) => void; } export class InsightsConfig extends PureComponent { onAppInsightsResetApiKey = () => { diff --git a/public/app/plugins/datasource/testdata/components/USAQueryEditor.tsx b/public/app/plugins/datasource/testdata/components/USAQueryEditor.tsx index db733072c5e..1e9338dd930 100644 --- a/public/app/plugins/datasource/testdata/components/USAQueryEditor.tsx +++ b/public/app/plugins/datasource/testdata/components/USAQueryEditor.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { InlineFieldRow, InlineField, Select, Input } from '@grafana/ui'; +import { InlineFieldRow, InlineField, Select, MultiSelect, Input } from '@grafana/ui'; import { SelectableValue } from '@grafana/data'; import { USAQuery } from '../types'; @@ -35,26 +35,24 @@ export function USAQueryEditor({ query, onChange }: Props) { - { onChange({ ...query, states: vals.map((v) => v.value) }); }} - isMulti={true} placeholder="all" value={query.states} /> diff --git a/scripts/ci-check-strict.sh b/scripts/ci-check-strict.sh index f18ffc6d8d1..01c25971960 100755 --- a/scripts/ci-check-strict.sh +++ b/scripts/ci-check-strict.sh @@ -3,7 +3,7 @@ set -e echo -e "Collecting code stats (typescript errors & more)" -ERROR_COUNT_LIMIT=61 +ERROR_COUNT_LIMIT=50 ERROR_COUNT="$(./node_modules/.bin/tsc --project tsconfig.json --noEmit --strict true | grep -oP 'Found \K(\d+)')" if [ "$ERROR_COUNT" -gt $ERROR_COUNT_LIMIT ]; then From 32054502100a5621ad28e35f4114ede18de24109 Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Wed, 11 Aug 2021 01:25:33 -0700 Subject: [PATCH 03/46] Transformations: support a synchronous transformation pattern (#37780) --- .../transformers/ensureColumns.ts | 35 +++++++-------- .../transformations/transformers/histogram.ts | 33 ++++++-------- .../transformers/seriesToColumns.ts | 36 ++++++++------- .../grafana-data/src/types/transformations.ts | 15 +++++++ .../prepareTimeSeries.test.ts | 14 +++--- .../prepareTimeSeries/prepareTimeSeries.ts | 45 +++++++++---------- 6 files changed, 93 insertions(+), 85 deletions(-) diff --git a/packages/grafana-data/src/transformations/transformers/ensureColumns.ts b/packages/grafana-data/src/transformations/transformers/ensureColumns.ts index b8068ce3094..5b7f20511de 100644 --- a/packages/grafana-data/src/transformations/transformers/ensureColumns.ts +++ b/packages/grafana-data/src/transformations/transformers/ensureColumns.ts @@ -1,33 +1,28 @@ -import { of } from 'rxjs'; - import { seriesToColumnsTransformer } from './seriesToColumns'; import { DataFrame } from '../../types/dataFrame'; import { getTimeField } from '../../dataframe/processDataFrame'; -import { DataTransformerInfo } from '../../types/transformations'; +import { SynchronousDataTransformerInfo } from '../../types/transformations'; import { DataTransformerID } from './ids'; -import { mergeMap } from 'rxjs/operators'; +import { map } from 'rxjs/operators'; -export const ensureColumnsTransformer: DataTransformerInfo = { +export const ensureColumnsTransformer: SynchronousDataTransformerInfo = { id: DataTransformerID.ensureColumns, name: 'Ensure Columns Transformer', description: 'Will check if current data frames is series or columns. If in series it will convert to columns.', - operator: (options = {}) => (source) => - source.pipe( - mergeMap((data) => { - // Assume timeseries should first be joined by time - const timeFieldName = findConsistentTimeFieldName(data); - if (data.length > 1 && timeFieldName) { - return of(data).pipe( - seriesToColumnsTransformer.operator({ - byField: timeFieldName, - }) - ); - } + operator: (options) => (source) => source.pipe(map((data) => ensureColumnsTransformer.transformer(options)(data))), - return of(data); - }) - ), + transformer: (options: any) => (frames: DataFrame[]) => { + // Assume timeseries should first be joined by time + const timeFieldName = findConsistentTimeFieldName(frames); + + if (frames.length > 1 && timeFieldName) { + return seriesToColumnsTransformer.transformer({ + byField: timeFieldName, + })(frames); + } + return frames; + }, }; /** diff --git a/packages/grafana-data/src/transformations/transformers/histogram.ts b/packages/grafana-data/src/transformations/transformers/histogram.ts index a28aeaf88f9..0e8b2c1da88 100644 --- a/packages/grafana-data/src/transformations/transformers/histogram.ts +++ b/packages/grafana-data/src/transformations/transformers/histogram.ts @@ -1,4 +1,4 @@ -import { DataTransformerInfo } from '../../types'; +import { SynchronousDataTransformerInfo } from '../../types'; import { map } from 'rxjs/operators'; import { DataTransformerID } from './ids'; @@ -73,7 +73,7 @@ export const histogramFieldInfo = { /** * @alpha */ -export const histogramTransformer: DataTransformerInfo = { +export const histogramTransformer: SynchronousDataTransformerInfo = { id: DataTransformerID.histogram, name: 'Histogram', description: 'Calculate a histogram from input data', @@ -81,23 +81,18 @@ export const histogramTransformer: DataTransformerInfo (source) => - source.pipe( - map((data) => { - if (!Array.isArray(data) || data.length === 0) { - return data; - } - const hist = buildHistogram(data, options); - if (hist == null) { - return []; - } - return [histogramFieldsToFrame(hist)]; - }) - ), + operator: (options) => (source) => source.pipe(map((data) => histogramTransformer.transformer(options)(data))), + + transformer: (options: HistogramTransformerOptions) => (data: DataFrame[]) => { + if (!Array.isArray(data) || data.length === 0) { + return data; + } + const hist = buildHistogram(data, options); + if (hist == null) { + return []; + } + return [histogramFieldsToFrame(hist)]; + }, }; /** diff --git a/packages/grafana-data/src/transformations/transformers/seriesToColumns.ts b/packages/grafana-data/src/transformations/transformers/seriesToColumns.ts index 07acaf87a2f..08170d1192b 100644 --- a/packages/grafana-data/src/transformations/transformers/seriesToColumns.ts +++ b/packages/grafana-data/src/transformations/transformers/seriesToColumns.ts @@ -1,6 +1,6 @@ import { map } from 'rxjs/operators'; -import { DataTransformerInfo, FieldMatcher } from '../../types'; +import { DataFrame, SynchronousDataTransformerInfo, FieldMatcher } from '../../types'; import { DataTransformerID } from './ids'; import { outerJoinDataFrames } from './joinDataFrames'; import { fieldMatchers } from '../matchers'; @@ -10,27 +10,29 @@ export interface SeriesToColumnsOptions { byField?: string; // empty will pick the field automatically } -export const seriesToColumnsTransformer: DataTransformerInfo = { +export const seriesToColumnsTransformer: SynchronousDataTransformerInfo = { id: DataTransformerID.seriesToColumns, name: 'Series as columns', // Called 'Outer join' in the UI! description: 'Groups series by field and returns values as columns', defaultOptions: { byField: undefined, // DEFAULT_KEY_FIELD, }, - operator: (options) => (source) => - source.pipe( - map((data) => { - if (data.length > 1) { - let joinBy: FieldMatcher | undefined = undefined; - if (options.byField) { - joinBy = fieldMatchers.get(FieldMatcherID.byName).get(options.byField); - } - const joined = outerJoinDataFrames({ frames: data, joinBy }); - if (joined) { - return [joined]; - } + + operator: (options) => (source) => source.pipe(map((data) => seriesToColumnsTransformer.transformer(options)(data))), + + transformer: (options: SeriesToColumnsOptions) => { + let joinBy: FieldMatcher | undefined = undefined; + return (data: DataFrame[]) => { + if (data.length > 1) { + if (options.byField && !joinBy) { + joinBy = fieldMatchers.get(FieldMatcherID.byName).get(options.byField); } - return data; - }) - ), + const joined = outerJoinDataFrames({ frames: data, joinBy }); + if (joined) { + return [joined]; + } + } + return data; + }; + }, }; diff --git a/packages/grafana-data/src/types/transformations.ts b/packages/grafana-data/src/types/transformations.ts index c1ad5995f61..b7a018093d7 100644 --- a/packages/grafana-data/src/types/transformations.ts +++ b/packages/grafana-data/src/types/transformations.ts @@ -5,6 +5,8 @@ import { RegistryItemWithOptions } from '../utils/Registry'; /** * Function that transform data frames (AKA transformer) + * + * @public */ export interface DataTransformerInfo extends RegistryItemWithOptions { /** @@ -14,6 +16,19 @@ export interface DataTransformerInfo extends RegistryItemWithOpt operator: (options: TOptions) => MonoTypeOperatorFunction; } +/** + * Many transformations can be called with a simple synchronous function. + * When a transformer is defined, it should have identical behavior to using the operator + * + * @public + */ +export interface SynchronousDataTransformerInfo extends DataTransformerInfo { + transformer: (options: TOptions) => (frames: DataFrame[]) => DataFrame[]; +} + +/** + * @public + */ export interface DataTransformerConfig { /** * Unique identifier of transformer diff --git a/public/app/core/components/TransformersUI/prepareTimeSeries/prepareTimeSeries.test.ts b/public/app/core/components/TransformersUI/prepareTimeSeries/prepareTimeSeries.test.ts index cc043766fd4..b9cae546f84 100644 --- a/public/app/core/components/TransformersUI/prepareTimeSeries/prepareTimeSeries.test.ts +++ b/public/app/core/components/TransformersUI/prepareTimeSeries/prepareTimeSeries.test.ts @@ -1,5 +1,5 @@ import { toDataFrame, ArrayVector, DataFrame, FieldType, toDataFrameDTO, DataFrameDTO } from '@grafana/data'; -import { prepareTimeSeries, PrepareTimeSeriesOptions, timeSeriesFormat } from './prepareTimeSeries'; +import { prepareTimeSeriesTransformer, PrepareTimeSeriesOptions, timeSeriesFormat } from './prepareTimeSeries'; describe('Prepair time series transformer', () => { it('should transform wide to many', () => { @@ -19,7 +19,7 @@ describe('Prepair time series transformer', () => { format: timeSeriesFormat.TimeSeriesMany, }; - expect(prepareTimeSeries(source, config)).toEqual([ + expect(prepareTimeSeriesTransformer.transformer(config)(source)).toEqual([ toEquableDataFrame({ name: 'wide', refId: 'A', @@ -59,7 +59,7 @@ describe('Prepair time series transformer', () => { format: timeSeriesFormat.TimeSeriesMany, }; - expect(prepareTimeSeries(source, config)).toEqual([ + expect(prepareTimeSeriesTransformer.transformer(config)(source)).toEqual([ toEquableDataFrame({ name: 'wide', refId: 'A', @@ -107,7 +107,7 @@ describe('Prepair time series transformer', () => { format: timeSeriesFormat.TimeSeriesMany, }; - expect(prepareTimeSeries(source, config)).toEqual([ + expect(prepareTimeSeriesTransformer.transformer(config)(source)).toEqual([ toEquableDataFrame({ name: 'wide', refId: 'A', @@ -162,7 +162,9 @@ describe('Prepair time series transformer', () => { format: timeSeriesFormat.TimeSeriesMany, }; - expect(toEquableDataFrames(prepareTimeSeries(source, config))).toEqual(toEquableDataFrames(source)); + expect(toEquableDataFrames(prepareTimeSeriesTransformer.transformer(config)(source))).toEqual( + toEquableDataFrames(source) + ); }); it('should return empty array when no timeseries exist', () => { @@ -191,7 +193,7 @@ describe('Prepair time series transformer', () => { format: timeSeriesFormat.TimeSeriesMany, }; - expect(prepareTimeSeries(source, config)).toEqual([]); + expect(prepareTimeSeriesTransformer.transformer(config)(source)).toEqual([]); }); }); diff --git a/public/app/core/components/TransformersUI/prepareTimeSeries/prepareTimeSeries.ts b/public/app/core/components/TransformersUI/prepareTimeSeries/prepareTimeSeries.ts index 197dc77c131..842c16cf5f6 100644 --- a/public/app/core/components/TransformersUI/prepareTimeSeries/prepareTimeSeries.ts +++ b/public/app/core/components/TransformersUI/prepareTimeSeries/prepareTimeSeries.ts @@ -1,5 +1,5 @@ import { - DataTransformerInfo, + SynchronousDataTransformerInfo, DataFrame, FieldType, DataTransformerID, @@ -63,31 +63,30 @@ export function toTimeSeriesMany(data: DataFrame[]): DataFrame[] { return result; } -export function prepareTimeSeries(data: DataFrame[], options: PrepareTimeSeriesOptions): DataFrame[] { - const format = options?.format ?? timeSeriesFormat.TimeSeriesWide; - if (format === timeSeriesFormat.TimeSeriesMany) { - return toTimeSeriesMany(data); - } - - // Join by the first frame - const frame = outerJoinDataFrames({ - frames: data, - joinBy: fieldMatchers.get(FieldMatcherID.firstTimeField).get({}), - enforceSort: true, - keepOriginIndices: true, - }); - return frame ? [frame] : []; -} - -export const prepareTimeSeriesTransformer: DataTransformerInfo = { +export const prepareTimeSeriesTransformer: SynchronousDataTransformerInfo = { id: DataTransformerID.prepareTimeSeries, name: 'Prepare time series', description: `Will stretch data frames from the wide format into the long format. This is really helpful to be able to keep backwards compatability for panels not supporting the new wide format.`, defaultOptions: {}, - /** - * Return a modified copy of the series. If the transform is not or should not - * be applied, just return the input series - */ - operator: (options) => (source) => source.pipe(map((data) => prepareTimeSeries(data, options))), + operator: (options) => (source) => + source.pipe(map((data) => prepareTimeSeriesTransformer.transformer(options)(data))), + + transformer: (options: PrepareTimeSeriesOptions) => { + const format = options?.format ?? timeSeriesFormat.TimeSeriesWide; + if (format === timeSeriesFormat.TimeSeriesMany) { + return toTimeSeriesMany; + } + + return (data: DataFrame[]) => { + // Join by the first frame + const frame = outerJoinDataFrames({ + frames: data, + joinBy: fieldMatchers.get(FieldMatcherID.firstTimeField).get({}), + enforceSort: true, + keepOriginIndices: true, + }); + return frame ? [frame] : []; + }; + }, }; From 94b58b87ea8fbc810e117b1bd61d8538ea0902b3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 11 Aug 2021 09:56:20 +0100 Subject: [PATCH 04/46] Bump prismjs from 1.23.0 to 1.24.0 (#36236) Bumps [prismjs](https://github.com/PrismJS/prism) from 1.23.0 to 1.24.0. - [Release notes](https://github.com/PrismJS/prism/releases) - [Changelog](https://github.com/PrismJS/prism/blob/master/CHANGELOG.md) - [Commits](https://github.com/PrismJS/prism/compare/v1.23.0...v1.24.0) --- updated-dependencies: - dependency-name: prismjs dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 95c0efaa940..ade8fe5adcd 100644 --- a/package.json +++ b/package.json @@ -270,7 +270,7 @@ "@opentelemetry/semantic-conventions": "0.23.0", "papaparse": "5.3.0", "pluralize": "^8.0.0", - "prismjs": "1.23.0", + "prismjs": "1.24.0", "prop-types": "15.7.2", "rc-cascader": "1.0.1", "re-resizable": "^6.2.0", diff --git a/yarn.lock b/yarn.lock index f6c4eab1306..f2b72b79009 100644 --- a/yarn.lock +++ b/yarn.lock @@ -18952,7 +18952,12 @@ pretty-hrtime@^1.0.3: resolved "https://registry.yarnpkg.com/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1" integrity sha1-t+PqQkNaTJsnWdmeDyAesZWALuE= -prismjs@1.23.0, prismjs@^1.21.0, prismjs@~1.23.0: +prismjs@1.24.0, prismjs@^1.21.0: + version "1.24.0" + resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.24.0.tgz#0409c30068a6c52c89ef7f1089b3ca4de56be2ac" + integrity sha512-SqV5GRsNqnzCL8k5dfAjCNhUrF3pR0A9lTDSCUZeh/LIshheXJEaP0hwLz2t4XHivd2J/v2HR+gRnigzeKe3cQ== + +prismjs@~1.23.0: version "1.23.0" resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.23.0.tgz#d3b3967f7d72440690497652a9d40ff046067f33" integrity sha512-c29LVsqOaLbBHuIbsTxaKENh1N2EQBOHaWv7gkHN4dgRbxSREqDnDbtFJYdpPauS4YCplMSNCABQ6Eeor69bAA== From a058a09b9bd28d633d2177fed1ccf66e202bb54e Mon Sep 17 00:00:00 2001 From: Ursula Kallio <73951760+osg-grafana@users.noreply.github.com> Date: Wed, 11 Aug 2021 11:30:40 +0200 Subject: [PATCH 05/46] Include voice and tone summary (#37742) Fix https://github.com/grafana/technical-documentation/issues/35. --- contribute/style-guides/documentation-style-guide.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/contribute/style-guides/documentation-style-guide.md b/contribute/style-guides/documentation-style-guide.md index b8f6035d60f..4d07e8d3e2b 100644 --- a/contribute/style-guides/documentation-style-guide.md +++ b/contribute/style-guides/documentation-style-guide.md @@ -50,6 +50,10 @@ The following guidelines are specific to Grafana documentation. For the most par ### General +Per the [Voice and tone](https://developers.google.com/style/tone) section of the Google developer documentation style guide: + +> In your documents, aim for a voice and tone that's conversational, friendly, and respectful without being overly colloquial or frivolous; a voice that's casual and natural and approachable, not pedantic or pushy. Try to sound like a knowledgeable friend who understands what the developer wants to do. + - Use active voice: - Active: Grafana displays the heatmap visualization. - Passive: The heatmap visualization is displayed. From e2227c38a31393f9153b27adfc8d9c69b90cce51 Mon Sep 17 00:00:00 2001 From: Serge Zaitsev Date: Wed, 11 Aug 2021 14:04:08 +0200 Subject: [PATCH 06/46] Remove debug log from gzipper (#37790) --- pkg/middleware/gziper.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/middleware/gziper.go b/pkg/middleware/gziper.go index d0248b345b8..ad61bd38b7d 100644 --- a/pkg/middleware/gziper.go +++ b/pkg/middleware/gziper.go @@ -58,7 +58,6 @@ func Gziper() func(http.Handler) http.Handler { for _, pathMatcher := range gzipIgnoredPaths { if pathMatcher(requestPath) { - fmt.Println("skip path", requestPath) next.ServeHTTP(rw, req) return } From 45754edfbcb550dc5d995f2d9d58fdbc51aa8eb1 Mon Sep 17 00:00:00 2001 From: Will Browne Date: Wed, 11 Aug 2021 16:23:07 +0200 Subject: [PATCH 07/46] bump plugin-sdk-go to fix tls verify field issue (#37797) --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 21860ef28b9..c5ba8cd5d47 100644 --- a/go.mod +++ b/go.mod @@ -52,7 +52,7 @@ require ( github.com/gorilla/websocket v1.4.2 github.com/gosimple/slug v1.9.0 github.com/grafana/grafana-aws-sdk v0.7.0 - github.com/grafana/grafana-plugin-sdk-go v0.111.0 + github.com/grafana/grafana-plugin-sdk-go v0.112.0 github.com/grafana/loki v1.6.2-0.20210520072447-15d417efe103 github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 github.com/hashicorp/go-hclog v0.16.1 diff --git a/go.sum b/go.sum index 796a32b4d37..6f755b5a460 100644 --- a/go.sum +++ b/go.sum @@ -995,6 +995,8 @@ github.com/grafana/grafana-aws-sdk v0.7.0/go.mod h1:+pPo5U+pX0zWimR7YBc7ASeSQfbR github.com/grafana/grafana-plugin-sdk-go v0.79.0/go.mod h1:NvxLzGkVhnoBKwzkst6CFfpMFKwAdIUZ1q8ssuLeF60= github.com/grafana/grafana-plugin-sdk-go v0.111.0 h1:7eqLgAdDYeELVokRCeDEobYQJy5/E5C43tZkb/xH66Q= github.com/grafana/grafana-plugin-sdk-go v0.111.0/go.mod h1:D7x3ah+1d4phNXpbnOaxa/osSaZlwh9/ZUnGGzegRbk= +github.com/grafana/grafana-plugin-sdk-go v0.112.0 h1:c+3kKJQ1wVZ5pMSF40eUizB5YzGPAbO+2QG0CJ3d4bI= +github.com/grafana/grafana-plugin-sdk-go v0.112.0/go.mod h1:D7x3ah+1d4phNXpbnOaxa/osSaZlwh9/ZUnGGzegRbk= github.com/grafana/loki v1.6.2-0.20210520072447-15d417efe103 h1:qCmofFVwQR9QnsinstVqI1NPLMVl33jNCnOCXEAVn6E= github.com/grafana/loki v1.6.2-0.20210520072447-15d417efe103/go.mod h1:GHIsn+EohCChsdu5YouNZewqLeV9L2FNw4DEJU3P9qE= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= From 4afa74232f23afe1559d5df324e6c84ed37bbdf6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Farkas?= Date: Wed, 11 Aug 2021 20:34:51 +0200 Subject: [PATCH 08/46] prometheus: alerting: respect datasource.scrapeInterval (#37799) --- pkg/tsdb/prometheus/prometheus.go | 15 +++++++++---- pkg/tsdb/prometheus/prometheus_test.go | 31 ++++++++++++++++++-------- 2 files changed, 33 insertions(+), 13 deletions(-) diff --git a/pkg/tsdb/prometheus/prometheus.go b/pkg/tsdb/prometheus/prometheus.go index 5ab3edf2185..14ede6f57d6 100644 --- a/pkg/tsdb/prometheus/prometheus.go +++ b/pkg/tsdb/prometheus/prometheus.go @@ -38,6 +38,7 @@ type DatasourceInfo struct { HTTPClientOpts sdkhttpclient.Options URL string HTTPMethod string + TimeInterval string } func init() { @@ -84,11 +85,17 @@ func newInstanceSettings() datasource.InstanceFactoryFunc { return nil, errors.New("no http method provided") } + timeInterval, ok := jsonData["timeInterval"].(string) + if !ok { + return nil, errors.New("invalid time-interval provided") + } + mdl := DatasourceInfo{ ID: settings.ID, URL: settings.URL, HTTPClientOpts: httpCliOpts, HTTPMethod: httpMethod, + TimeInterval: timeInterval, } return mdl, nil } @@ -123,7 +130,7 @@ func (s *Service) QueryData(ctx context.Context, req *backend.QueryDataRequest) Responses: backend.Responses{}, } - queries, err := s.parseQuery(req.Queries) + queries, err := s.parseQuery(req.Queries, dsInfo) if err != nil { return &result, err } @@ -217,7 +224,7 @@ func formatLegend(metric model.Metric, query *PrometheusQuery) string { return string(result) } -func (s *Service) parseQuery(queries []backend.DataQuery) ( +func (s *Service) parseQuery(queries []backend.DataQuery, dsInfo *DatasourceInfo) ( []*PrometheusQuery, error) { var intervalMode string var adjustedInterval time.Duration @@ -239,7 +246,7 @@ func (s *Service) parseQuery(queries []backend.DataQuery) ( end := queryModel.TimeRange.To queryInterval := jsonModel.Get("interval").MustString("") - dsInterval, err := tsdb.GetIntervalFrom(queryInterval, "", 0, 15*time.Second) + foundInterval, err := tsdb.GetIntervalFrom(dsInfo.TimeInterval, queryInterval, 0, 15*time.Second) hasQueryInterval := queryInterval != "" // Only use stepMode if we have interval in query, otherwise use "min" if hasQueryInterval { @@ -253,7 +260,7 @@ func (s *Service) parseQuery(queries []backend.DataQuery) ( return nil, err } - calculatedInterval, err := s.intervalCalculator.Calculate(queries[0].TimeRange, dsInterval, tsdb.IntervalMode(intervalMode)) + calculatedInterval, err := s.intervalCalculator.Calculate(queries[0].TimeRange, foundInterval, tsdb.IntervalMode(intervalMode)) if err != nil { return nil, err } diff --git a/pkg/tsdb/prometheus/prometheus_test.go b/pkg/tsdb/prometheus/prometheus_test.go index e1fb259f0ed..2518e9c8c2c 100644 --- a/pkg/tsdb/prometheus/prometheus_test.go +++ b/pkg/tsdb/prometheus/prometheus_test.go @@ -56,7 +56,7 @@ func TestPrometheus(t *testing.T) { To: now.Add(12 * time.Hour), } query.TimeRange = timeRange - models, err := service.parseQuery([]backend.DataQuery{query}) + models, err := service.parseQuery([]backend.DataQuery{query}, &DatasourceInfo{}) require.NoError(t, err) require.Equal(t, time.Second*30, models[0].Step) }) @@ -74,7 +74,7 @@ func TestPrometheus(t *testing.T) { To: now.Add(12 * time.Hour), } query.TimeRange = timeRange - models, err := service.parseQuery([]backend.DataQuery{query}) + models, err := service.parseQuery([]backend.DataQuery{query}, &DatasourceInfo{}) require.NoError(t, err) require.Equal(t, time.Second*7, models[0].Step) }) @@ -92,7 +92,7 @@ func TestPrometheus(t *testing.T) { To: now.Add(12 * time.Hour), } query.TimeRange = timeRange - models, err := service.parseQuery([]backend.DataQuery{query}) + models, err := service.parseQuery([]backend.DataQuery{query}, &DatasourceInfo{}) require.NoError(t, err) require.Equal(t, time.Second*6, models[0].Step) }) @@ -110,7 +110,7 @@ func TestPrometheus(t *testing.T) { To: now.Add(12 * time.Hour), } query.TimeRange = timeRange - models, err := service.parseQuery([]backend.DataQuery{query}) + models, err := service.parseQuery([]backend.DataQuery{query}, &DatasourceInfo{}) require.NoError(t, err) require.Equal(t, time.Second*30, models[0].Step) }) @@ -128,7 +128,7 @@ func TestPrometheus(t *testing.T) { To: now.Add(12 * time.Hour), } query.TimeRange = timeRange - models, err := service.parseQuery([]backend.DataQuery{query}) + models, err := service.parseQuery([]backend.DataQuery{query}, &DatasourceInfo{}) require.NoError(t, err) require.Equal(t, time.Second*5, models[0].Step) }) @@ -140,7 +140,7 @@ func TestPrometheus(t *testing.T) { "intervalFactor": 1, "refId": "A" }`) - models, err := service.parseQuery([]backend.DataQuery{query}) + models, err := service.parseQuery([]backend.DataQuery{query}, &DatasourceInfo{}) require.NoError(t, err) require.Equal(t, time.Minute*2, models[0].Step) @@ -149,7 +149,7 @@ func TestPrometheus(t *testing.T) { To: now.Add(1 * time.Hour), } query.TimeRange = timeRange - models, err = service.parseQuery([]backend.DataQuery{query}) + models, err = service.parseQuery([]backend.DataQuery{query}, &DatasourceInfo{}) require.NoError(t, err) require.Equal(t, time.Second*15, models[0].Step) }) @@ -160,7 +160,7 @@ func TestPrometheus(t *testing.T) { "format": "time_series", "intervalFactor": 10, "refId": "A" - }`)}) + }`)}, &DatasourceInfo{}) require.NoError(t, err) require.Equal(t, time.Minute*20, models[0].Step) }) @@ -171,10 +171,23 @@ func TestPrometheus(t *testing.T) { "format": "time_series", "intervalFactor": 1, "refId": "A" - }`)}) + }`)}, &DatasourceInfo{}) require.NoError(t, err) require.Equal(t, time.Minute*2, models[0].Step) }) + + t.Run("parsing query model specified scrape-interval in the data source", func(t *testing.T) { + models, err := service.parseQuery([]backend.DataQuery{queryContext(`{ + "expr": "go_goroutines", + "format": "time_series", + "intervalFactor": 1, + "refId": "A" + }`)}, &DatasourceInfo{ + TimeInterval: "240s", + }) + require.NoError(t, err) + require.Equal(t, time.Minute*4, models[0].Step) + }) } func queryContext(json string) backend.DataQuery { From 28229fc3440c62cc9c15eae8961839ac15d62245 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Farkas?= Date: Thu, 12 Aug 2021 08:36:50 +0200 Subject: [PATCH 09/46] devenv: influxdb: simplified versioning for old-version influxdb (#37726) --- devenv/docker/blocks/influxdb1/docker-compose.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devenv/docker/blocks/influxdb1/docker-compose.yaml b/devenv/docker/blocks/influxdb1/docker-compose.yaml index 39737522937..5a680117a2f 100644 --- a/devenv/docker/blocks/influxdb1/docker-compose.yaml +++ b/devenv/docker/blocks/influxdb1/docker-compose.yaml @@ -1,5 +1,5 @@ influxdb1: - image: influxdb:1.8.6 + image: influxdb:1.8 container_name: influxdb1 ports: - '2004:2004' From e76848acee2635fd99a07bfdc25d57106bcc08dd Mon Sep 17 00:00:00 2001 From: Domas Date: Thu, 12 Aug 2021 10:57:52 +0300 Subject: [PATCH 10/46] Alerting: improve rule list error message (#37734) --- .../alerting/unified/RuleList.test.tsx | 7 +- .../features/alerting/unified/RuleList.tsx | 66 ++----------- .../components/rules/RuleListErrors.tsx | 94 +++++++++++++++++++ 3 files changed, 110 insertions(+), 57 deletions(-) create mode 100644 public/app/features/alerting/unified/components/rules/RuleListErrors.tsx diff --git a/public/app/features/alerting/unified/RuleList.test.tsx b/public/app/features/alerting/unified/RuleList.test.tsx index 22554bc065d..6b8e7e3bf35 100644 --- a/public/app/features/alerting/unified/RuleList.test.tsx +++ b/public/app/features/alerting/unified/RuleList.test.tsx @@ -3,7 +3,7 @@ import { render, waitFor } from '@testing-library/react'; import { configureStore } from 'app/store/configureStore'; import { Provider } from 'react-redux'; import { RuleList } from './RuleList'; -import { byTestId, byText } from 'testing-library-selector'; +import { byRole, byTestId, byText } from 'testing-library-selector'; import { typeAsJestMock } from 'test/helpers/typeAsJestMock'; import { getAllDataSources } from './utils/config'; import { fetchRules } from './api/prometheus'; @@ -77,6 +77,7 @@ const ui = { ruleRow: byTestId('row'), expandedContent: byTestId('expanded-content'), rulesFilterInput: byTestId('search-query-input'), + moreErrorsButton: byRole('button', { name: /more errors/ }), }; describe('RuleList', () => { @@ -164,6 +165,10 @@ describe('RuleList', () => { const errors = await ui.cloudRulesSourceErrors.find(); + expect(errors).not.toHaveTextContent( + 'Failed to load rules state from Prometheus-broken: this datasource is broken' + ); + userEvent.click(ui.moreErrorsButton.get()); expect(errors).toHaveTextContent('Failed to load rules state from Prometheus-broken: this datasource is broken'); }); diff --git a/public/app/features/alerting/unified/RuleList.tsx b/public/app/features/alerting/unified/RuleList.tsx index 14d5f535605..dae426394e6 100644 --- a/public/app/features/alerting/unified/RuleList.tsx +++ b/public/app/features/alerting/unified/RuleList.tsx @@ -1,6 +1,5 @@ -import { DataSourceInstanceSettings, GrafanaTheme, urlUtil } from '@grafana/data'; -import { useStyles, Alert, LinkButton, withErrorBoundary } from '@grafana/ui'; -import { SerializedError } from '@reduxjs/toolkit'; +import { GrafanaTheme2, urlUtil } from '@grafana/data'; +import { useStyles2, LinkButton, withErrorBoundary } from '@grafana/ui'; import React, { useEffect, useMemo } from 'react'; import { useDispatch } from 'react-redux'; import { AlertingPageWrapper } from './components/AlertingPageWrapper'; @@ -8,11 +7,10 @@ import { NoRulesSplash } from './components/rules/NoRulesCTA'; import { useUnifiedAlertingSelector } from './hooks/useUnifiedAlertingSelector'; import { useFilteredRules } from './hooks/useFilteredRules'; import { fetchAllPromAndRulerRulesAction } from './state/actions'; -import { getAllRulesSourceNames, getRulesDataSources, GRAFANA_RULES_SOURCE_NAME } from './utils/datasource'; +import { getAllRulesSourceNames } from './utils/datasource'; import { css } from '@emotion/css'; import { useCombinedRuleNamespaces } from './hooks/useCombinedRuleNamespaces'; import { RULE_LIST_POLL_INTERVAL_MS } from './utils/constants'; -import { isRulerNotSupportedResponse } from './utils/rules'; import RulesFilter from './components/rules/RulesFilter'; import { RuleListGroupView } from './components/rules/RuleListGroupView'; import { RuleListStateView } from './components/rules/RuleListStateView'; @@ -20,6 +18,7 @@ import { useQueryParams } from 'app/core/hooks/useQueryParams'; import { useLocation } from 'react-router-dom'; import { contextSrv } from 'app/core/services/context_srv'; import { RuleStats } from './components/rules/RuleStats'; +import { RuleListErrors } from './components/rules/RuleListErrors'; const VIEWS = { groups: RuleListGroupView, @@ -29,7 +28,7 @@ const VIEWS = { export const RuleList = withErrorBoundary( () => { const dispatch = useDispatch(); - const styles = useStyles(getStyles); + const styles = useStyles2(getStyles); const rulesDataSourceNames = useMemo(getAllRulesSourceNames, []); const location = useLocation(); @@ -65,54 +64,13 @@ export const RuleList = withErrorBoundary( (Object.keys(rulerRuleRequests[name]?.result || {}).length && !rulerRuleRequests[name]?.error) ); - const [promReqeustErrors, rulerRequestErrors] = useMemo( - () => - [promRuleRequests, rulerRuleRequests].map((requests) => - getRulesDataSources().reduce>( - (result, dataSource) => { - const error = requests[dataSource.name]?.error; - if (requests[dataSource.name] && error && !isRulerNotSupportedResponse(requests[dataSource.name])) { - return [...result, { dataSource, error }]; - } - return result; - }, - [] - ) - ), - [promRuleRequests, rulerRuleRequests] - ); - - const grafanaPromError = promRuleRequests[GRAFANA_RULES_SOURCE_NAME]?.error; - const grafanaRulerError = rulerRuleRequests[GRAFANA_RULES_SOURCE_NAME]?.error; - const showNewAlertSplash = dispatched && !loading && !haveResults; const combinedNamespaces = useCombinedRuleNamespaces(); const filteredNamespaces = useFilteredRules(combinedNamespaces); return ( - {(promReqeustErrors.length || rulerRequestErrors.length || grafanaPromError) && ( - - {grafanaPromError && ( -
Failed to load Grafana rules state: {grafanaPromError.message || 'Unknown error.'}
- )} - {grafanaRulerError && ( -
Failed to load Grafana rules config: {grafanaRulerError.message || 'Unknown error.'}
- )} - {promReqeustErrors.map(({ dataSource, error }) => ( -
- Failed to load rules state from {dataSource.name}:{' '} - {error.message || 'Unknown error.'} -
- ))} - {rulerRequestErrors.map(({ dataSource, error }) => ( -
- Failed to load rules config from {dataSource.name}:{' '} - {error.message || 'Unknown error.'} -
- ))} -
- )} + {!showNewAlertSplash && ( <> @@ -139,19 +97,15 @@ export const RuleList = withErrorBoundary( { style: 'page' } ); -const getStyles = (theme: GrafanaTheme) => ({ +const getStyles = (theme: GrafanaTheme2) => ({ break: css` width: 100%; height: 0; - margin-bottom: ${theme.spacing.md}; - border-bottom: solid 1px ${theme.colors.border2}; - `, - iconError: css` - color: ${theme.palette.red}; - margin-right: ${theme.spacing.md}; + margin-bottom: ${theme.spacing(2)}; + border-bottom: solid 1px ${theme.colors.border.medium}; `, buttonsContainer: css` - margin-bottom: ${theme.spacing.md}; + margin-bottom: ${theme.spacing(2)}; display: flex; justify-content: space-between; `, diff --git a/public/app/features/alerting/unified/components/rules/RuleListErrors.tsx b/public/app/features/alerting/unified/components/rules/RuleListErrors.tsx new file mode 100644 index 00000000000..1c26499d838 --- /dev/null +++ b/public/app/features/alerting/unified/components/rules/RuleListErrors.tsx @@ -0,0 +1,94 @@ +import { css } from '@emotion/css'; +import { DataSourceInstanceSettings, GrafanaTheme2 } from '@grafana/data'; +import { Alert, Button, useStyles2 } from '@grafana/ui'; +import { SerializedError } from '@reduxjs/toolkit'; +import pluralize from 'pluralize'; +import React, { useMemo, ReactElement, useState } from 'react'; +import { useUnifiedAlertingSelector } from '../../hooks/useUnifiedAlertingSelector'; +import { getRulesDataSources, GRAFANA_RULES_SOURCE_NAME } from '../../utils/datasource'; +import { isRulerNotSupportedResponse } from '../../utils/rules'; + +export function RuleListErrors(): ReactElement { + const [expanded, setExpanded] = useState(false); + const [closed, setClosed] = useState(false); + const promRuleRequests = useUnifiedAlertingSelector((state) => state.promRules); + const rulerRuleRequests = useUnifiedAlertingSelector((state) => state.rulerRules); + const styles = useStyles2(getStyles); + + const errors = useMemo((): JSX.Element[] => { + const [promRequestErrors, rulerRequestErrors] = [promRuleRequests, rulerRuleRequests].map((requests) => + getRulesDataSources().reduce>( + (result, dataSource) => { + const error = requests[dataSource.name]?.error; + if (requests[dataSource.name] && error && !isRulerNotSupportedResponse(requests[dataSource.name])) { + return [...result, { dataSource, error }]; + } + return result; + }, + [] + ) + ); + const grafanaPromError = promRuleRequests[GRAFANA_RULES_SOURCE_NAME]?.error; + const grafanaRulerError = rulerRuleRequests[GRAFANA_RULES_SOURCE_NAME]?.error; + + const result: JSX.Element[] = []; + + if (grafanaPromError) { + result.push(<>Failed to load Grafana rules state: {grafanaPromError.message || 'Unknown error.'}); + } + if (grafanaRulerError) { + result.push(<>Failed to load Grafana rules config: {grafanaRulerError.message || 'Unknown error.'}); + } + + promRequestErrors.forEach(({ dataSource, error }) => + result.push( + <> + Failed to load rules state from {dataSource.name}:{' '} + {error.message || 'Unknown error.'} + + ) + ); + + rulerRequestErrors.forEach(({ dataSource, error }) => + result.push( + <> + Failed to load rules config from {dataSource.name}:{' '} + {error.message || 'Unknown error.'} + + ) + ); + + return result; + }, [promRuleRequests, rulerRuleRequests]); + + return ( + <> + {errors.length && !closed && ( + setClosed(true)} + > + {expanded && errors.map((item, idx) =>
{item}
)} + {!expanded && ( + <> +
{errors[0]}
+ {errors.length >= 2 && ( + + )} + + )} +
+ )} + + ); +} + +const getStyles = (theme: GrafanaTheme2) => ({ + moreButton: css` + padding: 0; + `, +}); From b5f7df3e6235d65df3ebb6a0cd2088730f9aec68 Mon Sep 17 00:00:00 2001 From: Andrej Ocenas Date: Thu, 12 Aug 2021 10:36:06 +0200 Subject: [PATCH 11/46] CloudWatch/Logs: Fix crash when time column has nil values (#37708) --- pkg/tsdb/cloudwatch/sort_frame.go | 8 ++ pkg/tsdb/cloudwatch/sort_frame_test.go | 107 ++++++++++++++++--------- 2 files changed, 77 insertions(+), 38 deletions(-) diff --git a/pkg/tsdb/cloudwatch/sort_frame.go b/pkg/tsdb/cloudwatch/sort_frame.go index 757f55ef802..94fdc59dd70 100644 --- a/pkg/tsdb/cloudwatch/sort_frame.go +++ b/pkg/tsdb/cloudwatch/sort_frame.go @@ -37,5 +37,13 @@ func (a ByTime) Less(i, j int) bool { return false } + if timeField.At(i).(*time.Time) == nil { + return false + } + + if timeField.At(j).(*time.Time) == nil { + return true + } + return (timeField.At(i).(*time.Time)).Before(*timeField.At(j).(*time.Time)) } diff --git a/pkg/tsdb/cloudwatch/sort_frame_test.go b/pkg/tsdb/cloudwatch/sort_frame_test.go index 076059c25be..cc7408d9c67 100644 --- a/pkg/tsdb/cloudwatch/sort_frame_test.go +++ b/pkg/tsdb/cloudwatch/sort_frame_test.go @@ -12,46 +12,77 @@ import ( ) func TestFrameSort(t *testing.T) { - timeA, _ := time.Parse("2006-01-02 15:04:05.000", "2020-03-02 17:04:05.000") - timeB, _ := time.Parse("2006-01-02 15:04:05.000", "2020-03-02 16:04:05.000") - timeC, _ := time.Parse("2006-01-02 15:04:05.000", "2020-03-02 15:04:05.000") - timeVals := []*time.Time{ - &timeA, &timeB, &timeC, - } - timeField := data.NewField("@timestamp", nil, timeVals) + t.Run("sort simple frame", func(t *testing.T) { + timeA, _ := time.Parse("2006-01-02 15:04:05.000", "2020-03-02 17:04:05.000") + timeB, _ := time.Parse("2006-01-02 15:04:05.000", "2020-03-02 16:04:05.000") + timeC, _ := time.Parse("2006-01-02 15:04:05.000", "2020-03-02 15:04:05.000") + timeVals := []*time.Time{ + &timeA, &timeB, &timeC, + } + timeField := data.NewField("@timestamp", nil, timeVals) - stringField := data.NewField("line", nil, []*string{ - aws.String("test message 1"), - aws.String("test message 2"), - aws.String("test message 3"), + stringField := data.NewField("line", nil, []*string{ + aws.String("test message 1"), + aws.String("test message 2"), + aws.String("test message 3"), + }) + + numberField := data.NewField("nums", nil, []*float64{ + aws.Float64(20.0), + aws.Float64(50.0), + aws.Float64(17.0), + }) + + expectedDataframe := &data.Frame{ + Name: "CloudWatchLogsResponse", + Fields: []*data.Field{ + timeField, + stringField, + numberField, + }, + } + + sort.Sort(ByTime(*expectedDataframe)) + + for i := 1; i < timeField.Len(); i++ { + assert.True(t, timeField.At(i).(*time.Time).After(*(timeField.At(i - 1).(*time.Time)))) + } + + assert.Equal(t, *stringField.At(0).(*string), "test message 3") + assert.Equal(t, *stringField.At(1).(*string), "test message 2") + assert.Equal(t, *stringField.At(2).(*string), "test message 1") + + assert.Equal(t, *numberField.At(0).(*float64), 17.0) + assert.Equal(t, *numberField.At(1).(*float64), 50.0) + assert.Equal(t, *numberField.At(2).(*float64), 20.0) }) - numberField := data.NewField("nums", nil, []*float64{ - aws.Float64(20.0), - aws.Float64(50.0), - aws.Float64(17.0), + t.Run("sort with nil", func(t *testing.T) { + timeA, _ := time.Parse("2006-01-02 15:04:05.000", "2020-03-02 17:04:05.000") + timeB, _ := time.Parse("2006-01-02 15:04:05.000", "2020-03-02 16:04:05.000") + timeVals := []*time.Time{ + &timeA, &timeB, nil, + } + timeField := data.NewField("@timestamp", nil, timeVals) + + stringField := data.NewField("line", nil, []*string{ + aws.String("test message 1"), + aws.String("test message 2"), + aws.String("test message 3"), + }) + + frame := &data.Frame{ + Name: "CloudWatchLogsResponse", + Fields: []*data.Field{ + timeField, + stringField, + }, + } + + sort.Sort(ByTime(*frame)) + + assert.Equal(t, *stringField.At(0).(*string), "test message 2") + assert.Equal(t, *stringField.At(1).(*string), "test message 1") + assert.Equal(t, *stringField.At(2).(*string), "test message 3") }) - - expectedDataframe := &data.Frame{ - Name: "CloudWatchLogsResponse", - Fields: []*data.Field{ - timeField, - stringField, - numberField, - }, - } - - sort.Sort(ByTime(*expectedDataframe)) - - for i := 1; i < timeField.Len(); i++ { - assert.True(t, timeField.At(i).(*time.Time).After(*(timeField.At(i - 1).(*time.Time)))) - } - - assert.Equal(t, *stringField.At(0).(*string), "test message 3") - assert.Equal(t, *stringField.At(1).(*string), "test message 2") - assert.Equal(t, *stringField.At(2).(*string), "test message 1") - - assert.Equal(t, *numberField.At(0).(*float64), 17.0) - assert.Equal(t, *numberField.At(1).(*float64), 50.0) - assert.Equal(t, *numberField.At(2).(*float64), 20.0) } From e4779cfb4f65628b95104c2d04a26f41c2a78aef Mon Sep 17 00:00:00 2001 From: Dimitris Sotirakis Date: Thu, 12 Aug 2021 11:39:09 +0300 Subject: [PATCH 12/46] Remove pkg-archive from Makefile (#37821) --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 79cbbca710f..dbfe41812e7 100644 --- a/Makefile +++ b/Makefile @@ -91,7 +91,7 @@ shellcheck: $(SH_FILES) ## Run checks for shell scripts. build-docker-dev: ## Build Docker image for development (fast). @echo "build development container" @echo "\033[92mInfo:\033[0m the frontend code is expected to be built already." - $(GO) run build.go -goos linux -pkg-arch amd64 ${OPT} build pkg-archive latest + $(GO) run build.go -goos linux -pkg-arch amd64 ${OPT} build latest cp dist/grafana-latest.linux-x64.tar.gz packaging/docker cd packaging/docker && docker build --tag grafana/grafana:dev . @@ -141,7 +141,7 @@ clean: ## Clean up intermediate build artifacts. rm -rf public/build # This repository's configuration is protected (https://readme.drone.io/signature/). -# Use this make target to regenerate the configuration YAML files when +# Use this make target to regenerate the configuration YAML files when # you modify starlark files. drone: drone starlark From 059ef4ee9a1a789cb9d107aad5c01adebc632223 Mon Sep 17 00:00:00 2001 From: Alex Khomenko Date: Thu, 12 Aug 2021 11:46:33 +0300 Subject: [PATCH 13/46] PasswordField: Fix button role (#37816) * Fix button role * Fix tests --- .../core/components/PasswordField/PasswordField.test.tsx | 6 +++--- public/app/core/components/PasswordField/PasswordField.tsx | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/public/app/core/components/PasswordField/PasswordField.test.tsx b/public/app/core/components/PasswordField/PasswordField.test.tsx index 0f85ae02901..98af3c901ba 100644 --- a/public/app/core/components/PasswordField/PasswordField.test.tsx +++ b/public/app/core/components/PasswordField/PasswordField.test.tsx @@ -8,15 +8,15 @@ describe('PasswordField', () => { placeholder: 'enter password', 'data-testid': 'password-field', }; - it('should renders correctly', () => { + it('should render correctly', () => { render(); expect(screen.getByTestId('password-field')).toBeInTheDocument(); - expect(screen.getByRole('button')).toBeInTheDocument(); + expect(screen.getByRole('switch', { name: 'Show password' })).toBeInTheDocument(); }); it('should able to show password value if clicked on password-reveal icon', () => { render(); expect(screen.getByTestId('password-field')).toHaveProperty('type', 'password'); - fireEvent.click(screen.getByRole('button')); + fireEvent.click(screen.getByRole('switch', { name: 'Show password' })); expect(screen.getByTestId('password-field')).toHaveProperty('type', 'text'); }); }); diff --git a/public/app/core/components/PasswordField/PasswordField.tsx b/public/app/core/components/PasswordField/PasswordField.tsx index 3ba84eb1ba4..ec32963c364 100644 --- a/public/app/core/components/PasswordField/PasswordField.tsx +++ b/public/app/core/components/PasswordField/PasswordField.tsx @@ -29,7 +29,7 @@ export const PasswordField: FC = React.forwardRef { From b6ede8d96db820e025dbe63d0d123956f1f00a11 Mon Sep 17 00:00:00 2001 From: Tobias Skarhed <1438972+tskarhed@users.noreply.github.com> Date: Thu, 12 Aug 2021 11:13:03 +0200 Subject: [PATCH 14/46] Chore: Low hanging a11y fruits (#37783) * aria-label disallowed on header element * A11y issues for Getting Started and panel header * Add unique identifier to search navigation * Add dashboard header markup * Fix news panel low hanging fruits --- packages/grafana-e2e-selectors/src/selectors/components.ts | 2 +- .../grafana-ui/src/components/PageLayout/PageToolbar.tsx | 2 +- .../dashboard/dashgrid/PanelHeader/PanelHeader.tsx | 2 +- .../dashboard/dashgrid/PanelHeader/PanelHeaderCorner.tsx | 6 +++--- public/app/plugins/panel/gettingstarted/GettingStarted.tsx | 4 ++-- .../plugins/panel/gettingstarted/components/DocsCard.tsx | 7 +++++-- public/app/plugins/panel/news/NewsPanel.tsx | 2 ++ 7 files changed, 15 insertions(+), 10 deletions(-) diff --git a/packages/grafana-e2e-selectors/src/selectors/components.ts b/packages/grafana-e2e-selectors/src/selectors/components.ts index d032d9c5b74..a0cbd81cfd0 100644 --- a/packages/grafana-e2e-selectors/src/selectors/components.ts +++ b/packages/grafana-e2e-selectors/src/selectors/components.ts @@ -44,7 +44,7 @@ export const Components = { }, Panels: { Panel: { - title: (title: string) => `Panel header ${title}`, + title: (title: string) => `data-testid Panel header ${title}`, headerItems: (item: string) => `Panel header item ${item}`, containerByTitle: (title: string) => `${title} panel`, headerCornerInfo: (mode: string) => `Panel header ${mode}`, diff --git a/packages/grafana-ui/src/components/PageLayout/PageToolbar.tsx b/packages/grafana-ui/src/components/PageLayout/PageToolbar.tsx index 86c41af1c10..ddb1212f7be 100644 --- a/packages/grafana-ui/src/components/PageLayout/PageToolbar.tsx +++ b/packages/grafana-ui/src/components/PageLayout/PageToolbar.tsx @@ -63,7 +63,7 @@ export const PageToolbar: FC = React.memo( />
)} -