[v8.3.0-beta1] Manual backports (#42032)
* Alerting: increase timeout for silences tests (#41918) (#41958) (cherry picked from commit81155e2d55) Co-authored-by: Nathan Rodman <nathanrodman@gmail.com> * Fix loop when cannot fetch roles (#41901) (#41945) (cherry picked from commitd66158a042) Co-authored-by: Alexander Zobnin <alexanderzobnin@gmail.com> * ThresholdControls: Use correct scale key when calculating handle position (#41942) (#41971) (cherry picked from commit67f43cd7ab) Co-authored-by: Dominik Prokop <dominik.prokop@grafana.com> * Candlestick: fix volume histogram height by using mapped field name (#41931) (#41934) (cherry picked from commit17c2f52dcf) Co-authored-by: Leon Sorokin <leeoniya@gmail.com> Co-authored-by: Grot (@grafanabot) <43478413+grafanabot@users.noreply.github.com> Co-authored-by: Nathan Rodman <nathanrodman@gmail.com> Co-authored-by: Alexander Zobnin <alexanderzobnin@gmail.com> Co-authored-by: Leon Sorokin <leeoniya@gmail.com>
This commit is contained in:
co-authored by
Nathan Rodman
Alexander Zobnin
Leon Sorokin
Grot
parent
d95a4c6488
commit
71935ec4d7
@@ -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;
|
||||
|
||||
@@ -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<GraphFieldConfig>) {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -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<GraphFieldConfig>) {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
DataFrame,
|
||||
DefaultTimeZone,
|
||||
EventBus,
|
||||
Field,
|
||||
getTimeZoneInfo,
|
||||
GrafanaTheme2,
|
||||
TimeRange,
|
||||
@@ -287,8 +288,8 @@ type UPlotConfigPrepOpts<T extends Record<string, any> = {}> = {
|
||||
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 */
|
||||
|
||||
@@ -60,11 +60,16 @@ export const fetchUserRoles = async (userId: number, orgId?: number): Promise<Ro
|
||||
if (orgId) {
|
||||
userRolesUrl += `?targetOrgId=${orgId}`;
|
||||
}
|
||||
const roles = await getBackendSrv().get(userRolesUrl);
|
||||
if (!roles || !roles.length) {
|
||||
try {
|
||||
const roles = await getBackendSrv().get(userRolesUrl);
|
||||
if (!roles || !roles.length) {
|
||||
return [];
|
||||
}
|
||||
return roles;
|
||||
} catch (error) {
|
||||
error.isHandled = true;
|
||||
return [];
|
||||
}
|
||||
return roles;
|
||||
};
|
||||
|
||||
export const updateUserRoles = (roleUids: string[], userId: number, orgId?: number) => {
|
||||
|
||||
@@ -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
|
||||
);
|
||||
});
|
||||
|
||||
@@ -43,8 +43,8 @@ export const MarketTrendPanel: React.FC<CandlestickPanelProps> = ({
|
||||
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<CandlestickPanelProps> = ({
|
||||
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<CandlestickPanelProps> = ({
|
||||
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];
|
||||
}
|
||||
|
||||
|
||||
@@ -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<ThresholdControlsPluginProps> = (
|
||||
if (!thresholds) {
|
||||
return null;
|
||||
}
|
||||
const scale = fieldConfig.defaults.unit ?? FIXED_UNIT;
|
||||
const scale = buildScaleKey(fieldConfig as FieldConfig<GraphFieldConfig>);
|
||||
|
||||
const decimals = fieldConfig.defaults.decimals;
|
||||
const handles = [];
|
||||
|
||||
|
||||
Reference in New Issue
Block a user