diff --git a/packages/grafana-ui/src/components/GraphNG/GraphNG.tsx b/packages/grafana-ui/src/components/GraphNG/GraphNG.tsx index a168ada1457..e33de639ef2 100755 --- a/packages/grafana-ui/src/components/GraphNG/GraphNG.tsx +++ b/packages/grafana-ui/src/components/GraphNG/GraphNG.tsx @@ -6,6 +6,7 @@ import { DataFrame, DataHoverClearEvent, DataHoverEvent, + Field, FieldMatcherID, fieldMatchers, LegacyGraphHoverEvent, @@ -44,8 +45,8 @@ export interface GraphNGProps extends Themeable2 { legend: VizLegendOptions; fields?: XYFieldMatchers; // default will assume timeseries data renderers?: Renderers; - tweakScale?: (opts: ScaleProps) => ScaleProps; - tweakAxis?: (opts: AxisProps) => AxisProps; + tweakScale?: (opts: ScaleProps, forField: Field) => ScaleProps; + tweakAxis?: (opts: AxisProps, forField: Field) => AxisProps; onLegendClick?: (event: GraphNGLegendEvent) => void; children?: (builder: UPlotConfigBuilder, alignedFrame: DataFrame) => React.ReactNode; prepConfig: (alignedFrame: DataFrame, allFrames: DataFrame[], getTimeRange: () => TimeRange) => UPlotConfigBuilder; diff --git a/packages/grafana-ui/src/components/GraphNG/utils.ts b/packages/grafana-ui/src/components/GraphNG/utils.ts index dd07370a82c..fe733543086 100644 --- a/packages/grafana-ui/src/components/GraphNG/utils.ts +++ b/packages/grafana-ui/src/components/GraphNG/utils.ts @@ -1,6 +1,8 @@ import { XYFieldMatchers } from './types'; -import { ArrayVector, DataFrame, FieldType, outerJoinDataFrames } from '@grafana/data'; +import { ArrayVector, DataFrame, FieldConfig, FieldType, outerJoinDataFrames } from '@grafana/data'; import { nullToUndefThreshold } from './nullToUndefThreshold'; +import { AxisPlacement, GraphFieldConfig, ScaleDistribution, ScaleDistributionConfig } from '@grafana/schema'; +import { FIXED_UNIT } from './GraphNG'; // will mutate the DataFrame's fields' values function applySpanNullsThresholds(frame: DataFrame) { @@ -38,3 +40,36 @@ export function preparePlotFrame(frames: DataFrame[], dimFields: XYFieldMatchers return alignedFrame && applySpanNullsThresholds(alignedFrame); } + +export function buildScaleKey(config: FieldConfig) { + const defaultPart = 'na'; + + const scaleRange = `${config.min !== undefined ? config.min : defaultPart}-${ + config.max !== undefined ? config.max : defaultPart + }`; + + const scaleSoftRange = `${config.custom?.axisSoftMin !== undefined ? config.custom.axisSoftMin : defaultPart}-${ + config.custom?.axisSoftMax !== undefined ? config.custom.axisSoftMax : defaultPart + }`; + + const scalePlacement = `${ + config.custom?.axisPlacement !== undefined ? config.custom?.axisPlacement : AxisPlacement.Auto + }`; + + const scaleUnit = config.unit ?? FIXED_UNIT; + + const scaleDistribution = config.custom?.scaleDistribution + ? getScaleDistributionPart(config.custom.scaleDistribution) + : ScaleDistribution.Linear; + + const scaleLabel = Boolean(config.custom?.axisLabel) ? config.custom!.axisLabel : defaultPart; + + return `${scaleUnit}/${scaleRange}/${scaleSoftRange}/${scalePlacement}/${scaleDistribution}/${scaleLabel}`; +} + +function getScaleDistributionPart(config: ScaleDistributionConfig) { + if (config.type === ScaleDistribution.Log) { + return `${config.type}${config.log}`; + } + return config.type; +} diff --git a/packages/grafana-ui/src/components/TimeSeries/utils.ts b/packages/grafana-ui/src/components/TimeSeries/utils.ts index aa7fba626af..33f5381ee8e 100644 --- a/packages/grafana-ui/src/components/TimeSeries/utils.ts +++ b/packages/grafana-ui/src/components/TimeSeries/utils.ts @@ -14,7 +14,6 @@ import { } from '@grafana/data'; import { UPlotConfigBuilder, UPlotConfigPrepFn } from '../uPlot/config/UPlotConfigBuilder'; -import { FIXED_UNIT } from '../GraphNG/GraphNG'; import { AxisPlacement, GraphDrawStyle, @@ -24,11 +23,10 @@ import { ScaleDirection, ScaleOrientation, VizLegendOptions, - ScaleDistributionConfig, - ScaleDistribution, } from '@grafana/schema'; import { collectStackingGroups, orderIdsByCalcs, preparePlotData } from '../uPlot/utils'; import uPlot from 'uplot'; +import { buildScaleKey } from '../GraphNG/utils'; const defaultFormatter = (v: any) => (v == null ? '-' : v.toFixed(1)); @@ -146,17 +144,20 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn<{ sync: DashboardCursor // The builder will manage unique scaleKeys and combine where appropriate builder.addScale( - tweakScale({ - scaleKey, - orientation: ScaleOrientation.Vertical, - direction: ScaleDirection.Up, - distribution: customConfig.scaleDistribution?.type, - log: customConfig.scaleDistribution?.log, - min: field.config.min, - max: field.config.max, - softMin: customConfig.axisSoftMin, - softMax: customConfig.axisSoftMax, - }) + tweakScale( + { + scaleKey, + orientation: ScaleOrientation.Vertical, + direction: ScaleDirection.Up, + distribution: customConfig.scaleDistribution?.type, + log: customConfig.scaleDistribution?.log, + min: field.config.min, + max: field.config.max, + softMin: customConfig.axisSoftMin, + softMax: customConfig.axisSoftMax, + }, + field + ) ); if (!yScaleKey) { @@ -165,15 +166,18 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn<{ sync: DashboardCursor if (customConfig.axisPlacement !== AxisPlacement.Hidden) { builder.addAxis( - tweakAxis({ - scaleKey, - label: customConfig.axisLabel, - size: customConfig.axisWidth, - placement: customConfig.axisPlacement ?? AxisPlacement.Auto, - formatValue: (v) => formattedValueToString(fmt(v)), - theme, - grid: { show: customConfig.axisGridShow }, - }) + tweakAxis( + { + scaleKey, + label: customConfig.axisLabel, + size: customConfig.axisWidth, + placement: customConfig.axisPlacement ?? AxisPlacement.Auto, + formatValue: (v) => formattedValueToString(fmt(v)), + theme, + grid: { show: customConfig.axisGridShow }, + }, + field + ) ); } @@ -435,34 +439,3 @@ export function getNamesToFieldIndex(frame: DataFrame, allFrames: DataFrame[]): }); return originNames; } - -function buildScaleKey(config: FieldConfig) { - const defaultPart = 'na'; - - const scaleRange = `${config.min !== undefined ? config.min : defaultPart}-${ - config.max !== undefined ? config.max : defaultPart - }`; - - const scaleSoftRange = `${config.custom?.axisSoftMin !== undefined ? config.custom.axisSoftMin : defaultPart}-${ - config.custom?.axisSoftMax !== undefined ? config.custom.axisSoftMax : defaultPart - }`; - - const scalePlacement = `${config.custom?.axisPlacement !== undefined ? config.custom?.axisPlacement : defaultPart}`; - - const scaleUnit = config.unit ?? FIXED_UNIT; - - const scaleDistribution = config.custom?.scaleDistribution - ? getScaleDistributionPart(config.custom.scaleDistribution) - : ScaleDistribution.Linear; - - const scaleLabel = Boolean(config.custom?.axisLabel) ? config.custom!.axisLabel : defaultPart; - - return `${scaleUnit}/${scaleRange}/${scaleSoftRange}/${scalePlacement}/${scaleDistribution}/${scaleLabel}`; -} - -function getScaleDistributionPart(config: ScaleDistributionConfig) { - if (config.type === ScaleDistribution.Log) { - return `${config.type}${config.log}`; - } - return config.type; -} diff --git a/packages/grafana-ui/src/components/index.ts b/packages/grafana-ui/src/components/index.ts index 2aa052b4a89..c63ebb88ab5 100644 --- a/packages/grafana-ui/src/components/index.ts +++ b/packages/grafana-ui/src/components/index.ts @@ -265,7 +265,7 @@ export { UPlotConfigPrepFn } from './uPlot/config/UPlotConfigBuilder'; export { GraphNG, GraphNGProps, FIXED_UNIT } from './GraphNG/GraphNG'; export { TimeSeries } from './TimeSeries/TimeSeries'; export { useGraphNGContext } from './GraphNG/hooks'; -export { preparePlotFrame } from './GraphNG/utils'; +export { preparePlotFrame, buildScaleKey } from './GraphNG/utils'; export { GraphNGLegendEvent } from './GraphNG/types'; export * from './PanelChrome/types'; export { EmotionPerfTest } from './ThemeDemos/EmotionPerfTest'; diff --git a/packages/grafana-ui/src/components/uPlot/config/UPlotConfigBuilder.ts b/packages/grafana-ui/src/components/uPlot/config/UPlotConfigBuilder.ts index 00d11bebeff..f4b1d1432c6 100644 --- a/packages/grafana-ui/src/components/uPlot/config/UPlotConfigBuilder.ts +++ b/packages/grafana-ui/src/components/uPlot/config/UPlotConfigBuilder.ts @@ -4,6 +4,7 @@ import { DataFrame, DefaultTimeZone, EventBus, + Field, getTimeZoneInfo, GrafanaTheme2, TimeRange, @@ -287,8 +288,8 @@ type UPlotConfigPrepOpts = {}> = { eventBus: EventBus; allFrames: DataFrame[]; renderers?: Renderers; - tweakScale?: (opts: ScaleProps) => ScaleProps; - tweakAxis?: (opts: AxisProps) => AxisProps; + tweakScale?: (opts: ScaleProps, forField: Field) => ScaleProps; + tweakAxis?: (opts: AxisProps, forField: Field) => AxisProps; } & T; /** @alpha */ diff --git a/public/app/core/components/RolePicker/UserRolePicker.tsx b/public/app/core/components/RolePicker/UserRolePicker.tsx index ffb152e414b..175e6c4dd16 100644 --- a/public/app/core/components/RolePicker/UserRolePicker.tsx +++ b/public/app/core/components/RolePicker/UserRolePicker.tsx @@ -60,11 +60,16 @@ export const fetchUserRoles = async (userId: number, orgId?: number): Promise { diff --git a/public/app/features/alerting/unified/Silences.test.tsx b/public/app/features/alerting/unified/Silences.test.tsx index ad4da643f66..3b468dd0ed7 100644 --- a/public/app/features/alerting/unified/Silences.test.tsx +++ b/public/app/features/alerting/unified/Silences.test.tsx @@ -17,6 +17,8 @@ import userEvent from '@testing-library/user-event'; jest.mock('./api/alertmanager'); +const TEST_TIMEOUT = 60000; + const mocks = { api: { fetchSilences: typeAsJestMock(fetchSilences), @@ -99,53 +101,65 @@ describe('Silences', () => { setDataSourceSrv(new MockDataSourceSrv(dataSources)); }); - it('loads and shows silences', async () => { - renderSilences(); - await waitFor(() => expect(mocks.api.fetchSilences).toHaveBeenCalled()); - await waitFor(() => expect(mocks.api.fetchAlerts).toHaveBeenCalled()); + it( + 'loads and shows silences', + async () => { + renderSilences(); + await waitFor(() => expect(mocks.api.fetchSilences).toHaveBeenCalled()); + await waitFor(() => expect(mocks.api.fetchAlerts).toHaveBeenCalled()); - expect(ui.silencesTable.query()).not.toBeNull(); + expect(ui.silencesTable.query()).not.toBeNull(); - const silences = ui.silenceRow.queryAll(); - expect(silences).toHaveLength(2); - expect(silences[0]).toHaveTextContent('foo=bar'); - expect(silences[1]).toHaveTextContent('foo!=bar'); - }); + const silences = ui.silenceRow.queryAll(); + expect(silences).toHaveLength(2); + expect(silences[0]).toHaveTextContent('foo=bar'); + expect(silences[1]).toHaveTextContent('foo!=bar'); + }, + TEST_TIMEOUT + ); - it('shows the correct number of silenced alerts', async () => { - mocks.api.fetchAlerts.mockImplementation(() => { - return Promise.resolve([ - mockAlertmanagerAlert({ - labels: { foo: 'bar', buzz: 'bazz' }, - status: { state: AlertState.Suppressed, silencedBy: ['12345'], inhibitedBy: [] }, - }), - mockAlertmanagerAlert({ - labels: { foo: 'bar', buzz: 'bazz' }, - status: { state: AlertState.Suppressed, silencedBy: ['12345'], inhibitedBy: [] }, - }), - ]); - }); + it( + 'shows the correct number of silenced alerts', + async () => { + mocks.api.fetchAlerts.mockImplementation(() => { + return Promise.resolve([ + mockAlertmanagerAlert({ + labels: { foo: 'bar', buzz: 'bazz' }, + status: { state: AlertState.Suppressed, silencedBy: ['12345'], inhibitedBy: [] }, + }), + mockAlertmanagerAlert({ + labels: { foo: 'bar', buzz: 'bazz' }, + status: { state: AlertState.Suppressed, silencedBy: ['12345'], inhibitedBy: [] }, + }), + ]); + }); - renderSilences(); - await waitFor(() => expect(mocks.api.fetchSilences).toHaveBeenCalled()); - await waitFor(() => expect(mocks.api.fetchAlerts).toHaveBeenCalled()); + renderSilences(); + await waitFor(() => expect(mocks.api.fetchSilences).toHaveBeenCalled()); + await waitFor(() => expect(mocks.api.fetchAlerts).toHaveBeenCalled()); - const silencedAlertRows = ui.silencedAlertCell.getAll(ui.silencesTable.get()); - expect(silencedAlertRows).toHaveLength(2); - expect(silencedAlertRows[0]).toHaveTextContent('2'); - expect(silencedAlertRows[1]).toHaveTextContent('0'); - }); + const silencedAlertRows = ui.silencedAlertCell.getAll(ui.silencesTable.get()); + expect(silencedAlertRows).toHaveLength(2); + expect(silencedAlertRows[0]).toHaveTextContent('2'); + expect(silencedAlertRows[1]).toHaveTextContent('0'); + }, + TEST_TIMEOUT + ); - it('filters silences by matchers', async () => { - renderSilences(); - await waitFor(() => expect(mocks.api.fetchSilences).toHaveBeenCalled()); - await waitFor(() => expect(mocks.api.fetchAlerts).toHaveBeenCalled()); + it( + 'filters silences by matchers', + async () => { + renderSilences(); + await waitFor(() => expect(mocks.api.fetchSilences).toHaveBeenCalled()); + await waitFor(() => expect(mocks.api.fetchAlerts).toHaveBeenCalled()); - const queryBar = ui.queryBar.get(); - userEvent.paste(queryBar, 'foo=bar'); + const queryBar = ui.queryBar.get(); + userEvent.paste(queryBar, 'foo=bar'); - await waitFor(() => expect(ui.silenceRow.getAll()).toHaveLength(1)); - }); + await waitFor(() => expect(ui.silenceRow.getAll()).toHaveLength(1)); + }, + TEST_TIMEOUT + ); }); describe('Silence edit', () => { @@ -157,94 +171,102 @@ describe('Silence edit', () => { setDataSourceSrv(new MockDataSourceSrv(dataSources)); }); - it('prefills the matchers field with matchers params', async () => { - renderSilences( - `${baseUrlPath}?matchers=${encodeURIComponent('foo=bar,bar=~ba.+,hello!=world,cluster!~us-central.*')}` - ); - await waitFor(() => expect(ui.editor.durationField.query()).not.toBeNull()); + it( + 'prefills the matchers field with matchers params', + async () => { + renderSilences( + `${baseUrlPath}?matchers=${encodeURIComponent('foo=bar,bar=~ba.+,hello!=world,cluster!~us-central.*')}` + ); + await waitFor(() => expect(ui.editor.durationField.query()).not.toBeNull()); - const matchers = ui.editor.matchersField.queryAll(); - expect(matchers).toHaveLength(4); + const matchers = ui.editor.matchersField.queryAll(); + expect(matchers).toHaveLength(4); - expect(ui.editor.matcherName.query(matchers[0])).toHaveValue('foo'); - expect(ui.editor.matcherOperator(MatcherOperator.equal).query(matchers[0])).not.toBeNull(); - expect(ui.editor.matcherValue.query(matchers[0])).toHaveValue('bar'); + expect(ui.editor.matcherName.query(matchers[0])).toHaveValue('foo'); + expect(ui.editor.matcherOperator(MatcherOperator.equal).query(matchers[0])).not.toBeNull(); + expect(ui.editor.matcherValue.query(matchers[0])).toHaveValue('bar'); - expect(ui.editor.matcherName.query(matchers[1])).toHaveValue('bar'); - expect(ui.editor.matcherOperator(MatcherOperator.regex).query(matchers[1])).not.toBeNull(); - expect(ui.editor.matcherValue.query(matchers[1])).toHaveValue('ba.+'); + expect(ui.editor.matcherName.query(matchers[1])).toHaveValue('bar'); + expect(ui.editor.matcherOperator(MatcherOperator.regex).query(matchers[1])).not.toBeNull(); + expect(ui.editor.matcherValue.query(matchers[1])).toHaveValue('ba.+'); - expect(ui.editor.matcherName.query(matchers[2])).toHaveValue('hello'); - expect(ui.editor.matcherOperator(MatcherOperator.notEqual).query(matchers[2])).not.toBeNull(); - expect(ui.editor.matcherValue.query(matchers[2])).toHaveValue('world'); + expect(ui.editor.matcherName.query(matchers[2])).toHaveValue('hello'); + expect(ui.editor.matcherOperator(MatcherOperator.notEqual).query(matchers[2])).not.toBeNull(); + expect(ui.editor.matcherValue.query(matchers[2])).toHaveValue('world'); - expect(ui.editor.matcherName.query(matchers[3])).toHaveValue('cluster'); - expect(ui.editor.matcherOperator(MatcherOperator.notRegex).query(matchers[3])).not.toBeNull(); - expect(ui.editor.matcherValue.query(matchers[3])).toHaveValue('us-central.*'); - }); + expect(ui.editor.matcherName.query(matchers[3])).toHaveValue('cluster'); + expect(ui.editor.matcherOperator(MatcherOperator.notRegex).query(matchers[3])).not.toBeNull(); + expect(ui.editor.matcherValue.query(matchers[3])).toHaveValue('us-central.*'); + }, + TEST_TIMEOUT + ); - it('creates a new silence', async () => { - renderSilences(baseUrlPath); - await waitFor(() => expect(ui.editor.durationField.query()).not.toBeNull()); + it( + 'creates a new silence', + async () => { + renderSilences(baseUrlPath); + await waitFor(() => expect(ui.editor.durationField.query()).not.toBeNull()); - const start = new Date(); - const end = new Date(start.getTime() + 24 * 60 * 60 * 1000); + const start = new Date(); + const end = new Date(start.getTime() + 24 * 60 * 60 * 1000); - const startDateString = dateTime(start).format('YYYY-MM-DD'); - const endDateString = dateTime(end).format('YYYY-MM-DD'); + const startDateString = dateTime(start).format('YYYY-MM-DD'); + const endDateString = dateTime(end).format('YYYY-MM-DD'); - userEvent.clear(ui.editor.durationInput.get()); - userEvent.type(ui.editor.durationInput.get(), '1d'); + userEvent.clear(ui.editor.durationInput.get()); + userEvent.type(ui.editor.durationInput.get(), '1d'); - await waitFor(() => expect(ui.editor.durationInput.query()).toHaveValue('1d')); - await waitFor(() => expect(ui.editor.timeRange.get()).toHaveTextContent(startDateString)); - await waitFor(() => expect(ui.editor.timeRange.get()).toHaveTextContent(endDateString)); + await waitFor(() => expect(ui.editor.durationInput.query()).toHaveValue('1d')); + await waitFor(() => expect(ui.editor.timeRange.get()).toHaveTextContent(startDateString)); + await waitFor(() => expect(ui.editor.timeRange.get()).toHaveTextContent(endDateString)); - userEvent.type(ui.editor.matcherName.get(), 'foo'); - userEvent.type(ui.editor.matcherOperatorSelect.get(), '='); - userEvent.tab(); - userEvent.type(ui.editor.matcherValue.get(), 'bar'); + userEvent.type(ui.editor.matcherName.get(), 'foo'); + userEvent.type(ui.editor.matcherOperatorSelect.get(), '='); + userEvent.tab(); + userEvent.type(ui.editor.matcherValue.get(), 'bar'); - // TODO remove skipPointerEventsCheck once https://github.com/jsdom/jsdom/issues/3232 is fixed - userEvent.click(ui.editor.addMatcherButton.get(), undefined, { skipPointerEventsCheck: true }); - userEvent.type(ui.editor.matcherName.getAll()[1], 'bar'); - userEvent.type(ui.editor.matcherOperatorSelect.getAll()[1], '!='); - userEvent.tab(); - userEvent.type(ui.editor.matcherValue.getAll()[1], 'buzz'); + // TODO remove skipPointerEventsCheck once https://github.com/jsdom/jsdom/issues/3232 is fixed + userEvent.click(ui.editor.addMatcherButton.get(), undefined, { skipPointerEventsCheck: true }); + userEvent.type(ui.editor.matcherName.getAll()[1], 'bar'); + userEvent.type(ui.editor.matcherOperatorSelect.getAll()[1], '!='); + userEvent.tab(); + userEvent.type(ui.editor.matcherValue.getAll()[1], 'buzz'); - // TODO remove skipPointerEventsCheck once https://github.com/jsdom/jsdom/issues/3232 is fixed - userEvent.click(ui.editor.addMatcherButton.get(), undefined, { skipPointerEventsCheck: true }); - userEvent.type(ui.editor.matcherName.getAll()[2], 'region'); - userEvent.type(ui.editor.matcherOperatorSelect.getAll()[2], '=~'); - userEvent.tab(); - userEvent.type(ui.editor.matcherValue.getAll()[2], 'us-west-.*'); + // TODO remove skipPointerEventsCheck once https://github.com/jsdom/jsdom/issues/3232 is fixed + userEvent.click(ui.editor.addMatcherButton.get(), undefined, { skipPointerEventsCheck: true }); + userEvent.type(ui.editor.matcherName.getAll()[2], 'region'); + userEvent.type(ui.editor.matcherOperatorSelect.getAll()[2], '=~'); + userEvent.tab(); + userEvent.type(ui.editor.matcherValue.getAll()[2], 'us-west-.*'); - // TODO remove skipPointerEventsCheck once https://github.com/jsdom/jsdom/issues/3232 is fixed - userEvent.click(ui.editor.addMatcherButton.get(), undefined, { skipPointerEventsCheck: true }); - userEvent.type(ui.editor.matcherName.getAll()[3], 'env'); - userEvent.type(ui.editor.matcherOperatorSelect.getAll()[3], '!~'); - userEvent.tab(); - userEvent.type(ui.editor.matcherValue.getAll()[3], 'dev|staging'); + // TODO remove skipPointerEventsCheck once https://github.com/jsdom/jsdom/issues/3232 is fixed + userEvent.click(ui.editor.addMatcherButton.get(), undefined, { skipPointerEventsCheck: true }); + userEvent.type(ui.editor.matcherName.getAll()[3], 'env'); + userEvent.type(ui.editor.matcherOperatorSelect.getAll()[3], '!~'); + userEvent.tab(); + userEvent.type(ui.editor.matcherValue.getAll()[3], 'dev|staging'); - userEvent.type(ui.editor.comment.get(), 'Test'); - userEvent.type(ui.editor.createdBy.get(), 'Homer Simpson'); + userEvent.type(ui.editor.comment.get(), 'Test'); + userEvent.type(ui.editor.createdBy.get(), 'Homer Simpson'); - userEvent.click(ui.editor.submit.get()); + userEvent.click(ui.editor.submit.get()); - await waitFor(() => - expect(mocks.api.createOrUpdateSilence).toHaveBeenCalledWith( - 'grafana', - expect.objectContaining({ - comment: 'Test', - createdBy: 'Homer Simpson', - matchers: [ - { isEqual: true, isRegex: false, name: 'foo', value: 'bar' }, - { isEqual: false, isRegex: false, name: 'bar', value: 'buzz' }, - { isEqual: true, isRegex: true, name: 'region', value: 'us-west-.*' }, - { isEqual: false, isRegex: true, name: 'env', value: 'dev|staging' }, - ], - }) - ) - ); - }); + await waitFor(() => + expect(mocks.api.createOrUpdateSilence).toHaveBeenCalledWith( + 'grafana', + expect.objectContaining({ + comment: 'Test', + createdBy: 'Homer Simpson', + matchers: [ + { isEqual: true, isRegex: false, name: 'foo', value: 'bar' }, + { isEqual: false, isRegex: false, name: 'bar', value: 'buzz' }, + { isEqual: true, isRegex: true, name: 'region', value: 'us-west-.*' }, + { isEqual: false, isRegex: true, name: 'env', value: 'dev|staging' }, + ], + }) + ) + ); + }, + TEST_TIMEOUT + ); }); diff --git a/public/app/plugins/panel/candlestick/CandlestickPanel.tsx b/public/app/plugins/panel/candlestick/CandlestickPanel.tsx index f3164f238c2..35ec51ba98c 100644 --- a/public/app/plugins/panel/candlestick/CandlestickPanel.tsx +++ b/public/app/plugins/panel/candlestick/CandlestickPanel.tsx @@ -43,8 +43,8 @@ export const MarketTrendPanel: React.FC = ({ const info = useMemo(() => prepareCandlestickFields(data?.series, options, theme), [data, options, theme]); const { renderers, tweakScale, tweakAxis } = useMemo(() => { - let tweakScale = (opts: ScaleProps) => opts; - let tweakAxis = (opts: AxisProps) => opts; + let tweakScale = (opts: ScaleProps, forField: Field) => opts; + let tweakAxis = (opts: AxisProps, forField: Field) => opts; let doNothing = { renderers: [], @@ -97,8 +97,9 @@ export const MarketTrendPanel: React.FC = ({ theme: config.theme2, }); - tweakAxis = (opts: AxisProps) => { - if (opts.scaleKey === 'short') { + tweakAxis = (opts: AxisProps, forField: Field) => { + // we can't do forField === info.volume because of copies :( + if (forField.name === info.volume?.name) { let filter = (u: uPlot, splits: number[]) => { let _splits = []; let max = u.series[volumeIdx].max as number; @@ -122,8 +123,9 @@ export const MarketTrendPanel: React.FC = ({ return opts; }; - tweakScale = (opts: ScaleProps) => { - if (opts.scaleKey === 'short') { + tweakScale = (opts: ScaleProps, forField: Field) => { + // we can't do forField === info.volume because of copies :( + if (forField.name === info.volume?.name) { opts.range = (u: uPlot, min: number, max: number) => [0, max * 7]; } diff --git a/public/app/plugins/panel/timeseries/plugins/ThresholdControlsPlugin.tsx b/public/app/plugins/panel/timeseries/plugins/ThresholdControlsPlugin.tsx index ce113cc592d..3be5e750016 100644 --- a/public/app/plugins/panel/timeseries/plugins/ThresholdControlsPlugin.tsx +++ b/public/app/plugins/panel/timeseries/plugins/ThresholdControlsPlugin.tsx @@ -1,6 +1,6 @@ import React, { useState, useLayoutEffect, useMemo, useRef } from 'react'; -import { FieldConfigSource, ThresholdsConfig, getValueFormat } from '@grafana/data'; -import { UPlotConfigBuilder, FIXED_UNIT } from '@grafana/ui'; +import { FieldConfigSource, ThresholdsConfig, getValueFormat, FieldConfig } from '@grafana/data'; +import { UPlotConfigBuilder, buildScaleKey, GraphFieldConfig } from '@grafana/ui'; import { ThresholdDragHandle } from './ThresholdDragHandle'; import uPlot from 'uplot'; @@ -42,7 +42,8 @@ export const ThresholdControlsPlugin: React.FC = ( if (!thresholds) { return null; } - const scale = fieldConfig.defaults.unit ?? FIXED_UNIT; + const scale = buildScaleKey(fieldConfig as FieldConfig); + const decimals = fieldConfig.defaults.decimals; const handles = [];