mirror of
https://github.com/grafana/grafana.git
synced 2026-02-03 20:49:50 -05:00
* Gauge: Delete radialbar plugin to avoid migrations * fix frontend unit test * update from CI issues
628 lines
21 KiB
TypeScript
628 lines
21 KiB
TypeScript
import {
|
|
AppEvents,
|
|
DataFrame,
|
|
FieldType,
|
|
getDefaultTimeRange,
|
|
getPanelDataSummary,
|
|
LoadingState,
|
|
PanelData,
|
|
PanelPluginVisualizationSuggestion,
|
|
PluginType,
|
|
toDataFrame,
|
|
VisualizationSuggestionScore,
|
|
} from '@grafana/data';
|
|
import { config } from '@grafana/runtime';
|
|
import {
|
|
BarGaugeDisplayMode,
|
|
BigValueColorMode,
|
|
GraphFieldConfig,
|
|
ReduceDataOptions,
|
|
StackingMode,
|
|
VizOrientation,
|
|
} from '@grafana/schema';
|
|
import { appEvents } from 'app/core/app_events';
|
|
import { clearPanelPluginCache } from 'app/features/plugins/importPanelPlugin';
|
|
import { pluginImporter } from 'app/features/plugins/importer/pluginImporter';
|
|
|
|
import { panelsToCheckFirst } from './consts';
|
|
import { getAllSuggestions, loadPlugins, sortSuggestions } from './getAllSuggestions';
|
|
|
|
jest.mock('app/core/app_events', () => ({
|
|
appEvents: {
|
|
subscribe: jest.fn(() => ({ unsubscribe: jest.fn() })),
|
|
publish: jest.fn(),
|
|
},
|
|
}));
|
|
|
|
config.featureToggles.externalVizSuggestions = true;
|
|
|
|
let idx = 0;
|
|
for (const pluginId of panelsToCheckFirst) {
|
|
if (pluginId === 'geomap') {
|
|
continue;
|
|
}
|
|
config.panels[pluginId] = {
|
|
id: pluginId,
|
|
module: `core:plugin/${pluginId}`,
|
|
sort: idx++,
|
|
name: pluginId,
|
|
type: PluginType.panel,
|
|
baseUrl: 'public/app/plugins/panel',
|
|
suggestions: true,
|
|
info: {
|
|
version: '1.0.0',
|
|
updated: '2025-01-01',
|
|
links: [],
|
|
screenshots: [],
|
|
author: {
|
|
name: 'Grafana Labs',
|
|
},
|
|
description: pluginId,
|
|
logos: { small: 'small/logo', large: 'large/logo' },
|
|
},
|
|
};
|
|
}
|
|
|
|
jest.mock('../state/util', () => {
|
|
const originalModule = jest.requireActual('../state/util');
|
|
return {
|
|
...originalModule,
|
|
getAllPanelPluginMeta: jest.fn().mockImplementation(() => [...Object.values(config.panels)]),
|
|
};
|
|
});
|
|
|
|
const SCALAR_PLUGINS = ['gauge', 'stat', 'bargauge', 'piechart'];
|
|
|
|
class ScenarioContext {
|
|
data: DataFrame[] = [];
|
|
suggestions: Array<PanelPluginVisualizationSuggestion<{ reduceOptions?: ReduceDataOptions }, GraphFieldConfig>> = [];
|
|
|
|
setData(scenarioData: DataFrame[]) {
|
|
this.data = scenarioData;
|
|
|
|
beforeAll(async () => {
|
|
await this.run();
|
|
});
|
|
}
|
|
|
|
async run() {
|
|
const panelData: PanelData = {
|
|
series: this.data,
|
|
state: LoadingState.Done,
|
|
timeRange: getDefaultTimeRange(),
|
|
};
|
|
|
|
const result = await getAllSuggestions(panelData);
|
|
this.suggestions = result.suggestions;
|
|
}
|
|
|
|
names() {
|
|
return this.suggestions.map((x) => x.name);
|
|
}
|
|
}
|
|
|
|
function scenario(name: string, setup: (ctx: ScenarioContext) => void) {
|
|
describe(name, () => {
|
|
const ctx = new ScenarioContext();
|
|
setup(ctx);
|
|
});
|
|
}
|
|
|
|
scenario('No series', (ctx) => {
|
|
ctx.setData([]);
|
|
|
|
it('should return correct suggestions', () => {
|
|
expect(ctx.suggestions).toEqual([
|
|
expect.objectContaining({ pluginId: 'table' }),
|
|
expect.objectContaining({ pluginId: 'text' }),
|
|
]);
|
|
});
|
|
});
|
|
|
|
scenario('No rows', (ctx) => {
|
|
ctx.setData([
|
|
toDataFrame({
|
|
fields: [
|
|
{ name: 'Time', type: FieldType.time, values: [] },
|
|
{ name: 'Max', type: FieldType.number, values: [] },
|
|
],
|
|
}),
|
|
]);
|
|
|
|
it('should return correct suggestions', () => {
|
|
expect(ctx.suggestions).toEqual([expect.objectContaining({ pluginId: 'table' })]);
|
|
});
|
|
});
|
|
|
|
scenario('Single frame with time and number field', (ctx) => {
|
|
ctx.setData([
|
|
toDataFrame({
|
|
fields: [
|
|
{ name: 'Time', type: FieldType.time, values: [1, 2, 3, 4, 5] },
|
|
{ name: 'Max', type: FieldType.number, values: [1, 10, 50, 2, 5] },
|
|
],
|
|
}),
|
|
]);
|
|
|
|
it('should return correct suggestions', () => {
|
|
expect(ctx.suggestions).toEqual([
|
|
expect.objectContaining({ pluginId: 'timeseries', name: 'Line chart' }),
|
|
expect.objectContaining({ pluginId: 'timeseries', name: 'Line chart - smooth' }),
|
|
expect.objectContaining({ pluginId: 'timeseries', name: 'Area chart' }),
|
|
expect.objectContaining({ pluginId: 'timeseries', name: 'Bar chart' }),
|
|
expect.objectContaining({ pluginId: 'gauge' }),
|
|
expect.objectContaining({ pluginId: 'gauge', options: expect.objectContaining({ showThresholdMarkers: false }) }),
|
|
expect.objectContaining({ pluginId: 'stat' }),
|
|
expect.objectContaining({
|
|
pluginId: 'stat',
|
|
options: expect.objectContaining({ colorMode: BigValueColorMode.Background }),
|
|
}),
|
|
expect.objectContaining({
|
|
pluginId: 'bargauge',
|
|
options: expect.objectContaining({ displayMode: BarGaugeDisplayMode.Basic }),
|
|
}),
|
|
expect.objectContaining({
|
|
pluginId: 'bargauge',
|
|
options: expect.objectContaining({ displayMode: BarGaugeDisplayMode.Lcd }),
|
|
}),
|
|
expect.objectContaining({ pluginId: 'table' }),
|
|
expect.objectContaining({ pluginId: 'state-timeline' }),
|
|
expect.objectContaining({ pluginId: 'status-history' }),
|
|
expect.objectContaining({ pluginId: 'heatmap' }),
|
|
expect.objectContaining({ pluginId: 'histogram' }),
|
|
]);
|
|
});
|
|
|
|
it('Bar chart suggestion should be using timeseries panel', () => {
|
|
expect(ctx.suggestions.find((x) => x.name === 'Bar chart')?.pluginId).toBe('timeseries');
|
|
});
|
|
|
|
it('Scalar panels should use calcs', () => {
|
|
for (const suggestion of ctx.suggestions.filter((s) => SCALAR_PLUGINS.includes(s.pluginId))) {
|
|
expect(suggestion).toEqual(
|
|
expect.objectContaining({
|
|
options: expect.objectContaining({
|
|
reduceOptions: expect.objectContaining({ values: false, calcs: ['lastNotNull'] }),
|
|
}),
|
|
})
|
|
);
|
|
}
|
|
});
|
|
});
|
|
|
|
scenario('Single frame with time 2 number fields', (ctx) => {
|
|
ctx.setData([
|
|
toDataFrame({
|
|
fields: [
|
|
{ name: 'Time', type: FieldType.time, values: [1, 2, 3, 4, 5] },
|
|
{ name: 'ServerA', type: FieldType.number, values: [1, 10, 50, 2, 5] },
|
|
{ name: 'ServerB', type: FieldType.number, values: [1, 10, 50, 2, 5] },
|
|
],
|
|
}),
|
|
]);
|
|
|
|
it('should return correct suggestions', () => {
|
|
expect(ctx.suggestions).toEqual([
|
|
expect.objectContaining({ pluginId: 'timeseries', name: 'Line chart' }),
|
|
expect.objectContaining({ pluginId: 'timeseries', name: 'Line chart - smooth' }),
|
|
expect.objectContaining({ pluginId: 'timeseries', name: 'Area chart - stacked' }),
|
|
expect.objectContaining({ pluginId: 'timeseries', name: 'Area chart - stacked by percentage' }),
|
|
expect.objectContaining({ pluginId: 'timeseries', name: 'Bar chart - stacked' }),
|
|
expect.objectContaining({ pluginId: 'timeseries', name: 'Bar chart - stacked by percentage' }),
|
|
expect.objectContaining({ pluginId: 'gauge' }),
|
|
expect.objectContaining({ pluginId: 'gauge', options: expect.objectContaining({ showThresholdMarkers: false }) }),
|
|
expect.objectContaining({ pluginId: 'stat' }),
|
|
expect.objectContaining({
|
|
pluginId: 'stat',
|
|
options: expect.objectContaining({ colorMode: BigValueColorMode.Background }),
|
|
}),
|
|
expect.objectContaining({ pluginId: 'piechart' }),
|
|
expect.objectContaining({ pluginId: 'piechart', options: expect.objectContaining({ pieType: 'donut' }) }),
|
|
expect.objectContaining({
|
|
pluginId: 'bargauge',
|
|
options: expect.objectContaining({ displayMode: BarGaugeDisplayMode.Basic }),
|
|
}),
|
|
expect.objectContaining({
|
|
pluginId: 'bargauge',
|
|
options: expect.objectContaining({ displayMode: BarGaugeDisplayMode.Lcd }),
|
|
}),
|
|
expect.objectContaining({ pluginId: 'table' }),
|
|
expect.objectContaining({ pluginId: 'state-timeline' }),
|
|
expect.objectContaining({ pluginId: 'status-history' }),
|
|
expect.objectContaining({ pluginId: 'heatmap' }),
|
|
expect.objectContaining({ pluginId: 'histogram' }),
|
|
]);
|
|
});
|
|
|
|
it('Scalar panels should use calcs', () => {
|
|
for (const suggestion of ctx.suggestions.filter((s) => SCALAR_PLUGINS.includes(s.pluginId))) {
|
|
expect(suggestion).toEqual(
|
|
expect.objectContaining({
|
|
options: expect.objectContaining({
|
|
reduceOptions: expect.objectContaining({ values: false, calcs: ['lastNotNull'] }),
|
|
}),
|
|
})
|
|
);
|
|
}
|
|
});
|
|
});
|
|
|
|
scenario('Single time series with 100 data points', (ctx) => {
|
|
ctx.setData([
|
|
toDataFrame({
|
|
fields: [
|
|
{ name: 'Time', type: FieldType.time, values: [...Array(100).keys()] },
|
|
{ name: 'ServerA', type: FieldType.number, values: [...Array(100).keys()] },
|
|
],
|
|
}),
|
|
]);
|
|
|
|
it('should not suggest bar chart', () => {
|
|
expect(ctx.suggestions.find((x) => x.name === 'Bar chart')).toBe(undefined);
|
|
});
|
|
});
|
|
|
|
scenario('30 time series with 100 data points', (ctx) => {
|
|
ctx.setData(
|
|
repeatFrame(
|
|
30,
|
|
toDataFrame({
|
|
fields: [
|
|
{ name: 'Time', type: FieldType.time, values: [...Array(100).keys()] },
|
|
{ name: 'ServerA', type: FieldType.number, values: [...Array(100).keys()] },
|
|
],
|
|
})
|
|
)
|
|
);
|
|
|
|
it('should not suggest timeline', () => {
|
|
expect(ctx.suggestions.find((x) => x.pluginId === 'state-timeline')).toBe(undefined);
|
|
});
|
|
});
|
|
|
|
scenario('50 time series with 100 data points', (ctx) => {
|
|
ctx.setData(
|
|
repeatFrame(
|
|
50,
|
|
toDataFrame({
|
|
fields: [
|
|
{ name: 'Time', type: FieldType.time, values: [...Array(100).keys()] },
|
|
{ name: 'ServerA', type: FieldType.number, values: [...Array(100).keys()] },
|
|
],
|
|
})
|
|
)
|
|
);
|
|
|
|
it('should not suggest gauge', () => {
|
|
expect(ctx.suggestions.find((x) => x.pluginId === 'gauge')).toBe(undefined);
|
|
});
|
|
});
|
|
|
|
scenario('Single frame with string and number field', (ctx) => {
|
|
ctx.setData([
|
|
toDataFrame({
|
|
fields: [
|
|
{ name: 'Name', type: FieldType.string, values: ['Hugo', 'Dominik', 'Marcus'] },
|
|
{ name: 'ServerA', type: FieldType.number, values: [1, 2, 3] },
|
|
],
|
|
}),
|
|
]);
|
|
|
|
it('should return correct suggestions', () => {
|
|
expect(ctx.suggestions).toEqual([
|
|
expect.objectContaining({ pluginId: 'piechart' }),
|
|
expect.objectContaining({ pluginId: 'piechart', options: expect.objectContaining({ pieType: 'donut' }) }),
|
|
expect.objectContaining({ pluginId: 'barchart' }),
|
|
expect.objectContaining({
|
|
pluginId: 'barchart',
|
|
options: expect.objectContaining({ orientation: VizOrientation.Horizontal }),
|
|
}),
|
|
expect.objectContaining({ pluginId: 'gauge' }),
|
|
expect.objectContaining({ pluginId: 'gauge', options: expect.objectContaining({ showThresholdMarkers: false }) }),
|
|
expect.objectContaining({ pluginId: 'stat' }),
|
|
expect.objectContaining({
|
|
pluginId: 'stat',
|
|
options: expect.objectContaining({ colorMode: BigValueColorMode.Background }),
|
|
}),
|
|
expect.objectContaining({
|
|
pluginId: 'bargauge',
|
|
}),
|
|
expect.objectContaining({
|
|
pluginId: 'bargauge',
|
|
options: expect.objectContaining({ displayMode: BarGaugeDisplayMode.Lcd }),
|
|
}),
|
|
expect.objectContaining({ pluginId: 'table' }),
|
|
expect.objectContaining({ pluginId: 'histogram' }),
|
|
]);
|
|
});
|
|
|
|
it('Scalar panels should contain raw values', () => {
|
|
for (const suggestion of ctx.suggestions.filter((s) => SCALAR_PLUGINS.includes(s.pluginId))) {
|
|
expect(suggestion).toEqual(
|
|
expect.objectContaining({
|
|
options: expect.objectContaining({ reduceOptions: expect.objectContaining({ values: true, calcs: [] }) }),
|
|
})
|
|
);
|
|
}
|
|
});
|
|
});
|
|
|
|
scenario('Single frame with string and 2 number field', (ctx) => {
|
|
ctx.setData([
|
|
toDataFrame({
|
|
fields: [
|
|
{ name: 'Name', type: FieldType.string, values: ['Hugo', 'Dominik', 'Marcus'] },
|
|
{ name: 'ServerA', type: FieldType.number, values: [1, 2, 3] },
|
|
{ name: 'ServerB', type: FieldType.number, values: [1, 2, 3] },
|
|
],
|
|
}),
|
|
]);
|
|
|
|
it('should return correct suggestions', () => {
|
|
expect(ctx.suggestions).toEqual([
|
|
expect.objectContaining({ pluginId: 'barchart' }),
|
|
expect.objectContaining({
|
|
pluginId: 'barchart',
|
|
options: expect.objectContaining({ stacking: StackingMode.Normal }),
|
|
}),
|
|
expect.objectContaining({
|
|
pluginId: 'barchart',
|
|
options: expect.objectContaining({ stacking: StackingMode.Percent }),
|
|
}),
|
|
|
|
expect.objectContaining({
|
|
pluginId: 'barchart',
|
|
options: expect.objectContaining({ orientation: VizOrientation.Horizontal }),
|
|
}),
|
|
expect.objectContaining({
|
|
pluginId: 'barchart',
|
|
options: expect.objectContaining({ orientation: VizOrientation.Horizontal, stacking: StackingMode.Normal }),
|
|
}),
|
|
expect.objectContaining({
|
|
pluginId: 'barchart',
|
|
options: expect.objectContaining({ orientation: VizOrientation.Horizontal, stacking: StackingMode.Percent }),
|
|
}),
|
|
expect.objectContaining({ pluginId: 'gauge' }),
|
|
expect.objectContaining({ pluginId: 'gauge', options: expect.objectContaining({ showThresholdMarkers: false }) }),
|
|
expect.objectContaining({ pluginId: 'stat' }),
|
|
expect.objectContaining({
|
|
pluginId: 'stat',
|
|
options: expect.objectContaining({ colorMode: BigValueColorMode.Background }),
|
|
}),
|
|
expect.objectContaining({ pluginId: 'piechart' }),
|
|
expect.objectContaining({ pluginId: 'piechart', options: expect.objectContaining({ pieType: 'donut' }) }),
|
|
expect.objectContaining({
|
|
pluginId: 'bargauge',
|
|
options: expect.objectContaining({ displayMode: BarGaugeDisplayMode.Basic }),
|
|
}),
|
|
expect.objectContaining({
|
|
pluginId: 'bargauge',
|
|
options: expect.objectContaining({ displayMode: BarGaugeDisplayMode.Lcd }),
|
|
}),
|
|
expect.objectContaining({ pluginId: 'table' }),
|
|
expect.objectContaining({ pluginId: 'histogram' }),
|
|
]);
|
|
});
|
|
});
|
|
|
|
scenario('Single frame with only string field', (ctx) => {
|
|
ctx.setData([
|
|
toDataFrame({
|
|
fields: [{ name: 'Name', type: FieldType.string, values: ['Hugo', 'Dominik', 'Marcus'] }],
|
|
}),
|
|
]);
|
|
|
|
it('should return correct suggestions', () => {
|
|
expect(ctx.suggestions).toEqual([
|
|
expect.objectContaining({ pluginId: 'stat' }),
|
|
expect.objectContaining({ pluginId: 'table' }),
|
|
]);
|
|
});
|
|
|
|
it('Stat panels have reduceOptions.fields set to show all fields', () => {
|
|
for (const suggestion of ctx.suggestions.filter((s) => s.pluginId === 'stat')) {
|
|
if (suggestion.options?.reduceOptions) {
|
|
expect(suggestion.options.reduceOptions.fields).toBe('/.*/');
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
scenario('Given default loki logs data', (ctx) => {
|
|
ctx.setData([
|
|
toDataFrame({
|
|
fields: [
|
|
{ name: 'ts', type: FieldType.time, values: ['2021-11-11T13:38:45.440Z', '2021-11-11T13:38:45.190Z'] },
|
|
{
|
|
name: 'line',
|
|
type: FieldType.string,
|
|
values: [
|
|
't=2021-11-11T14:38:45+0100 lvl=dbug msg="Client connected" logger=live user=1 client=ee79155b-a8d1-4730-bcb3-94d8690df35c',
|
|
't=2021-11-11T14:38:45+0100 lvl=dbug msg="Adding CSP header to response" logger=http.server cfg=0xc0005fed00',
|
|
],
|
|
labels: { filename: '/var/log/grafana/grafana.log', job: 'grafana' },
|
|
},
|
|
],
|
|
meta: {
|
|
preferredVisualisationType: 'logs',
|
|
},
|
|
}),
|
|
]);
|
|
|
|
it('should return correct suggestions', () => {
|
|
expect(ctx.suggestions).toEqual([
|
|
expect.objectContaining({ pluginId: 'logs' }),
|
|
expect.objectContaining({ pluginId: 'table' }),
|
|
]);
|
|
});
|
|
});
|
|
|
|
scenario('Given a preferredVisualisationType', (ctx) => {
|
|
ctx.setData([
|
|
toDataFrame({
|
|
meta: {
|
|
preferredVisualisationType: 'table',
|
|
},
|
|
fields: [
|
|
{
|
|
name: 'Trace Id',
|
|
type: FieldType.number,
|
|
values: [1, 2, 3],
|
|
config: {},
|
|
},
|
|
{ name: 'Trace Group', type: FieldType.string, values: ['traceGroup1', 'traceGroup2', 'traceGroup3'] },
|
|
],
|
|
}),
|
|
]);
|
|
|
|
it('should return the preferred visualization first', () => {
|
|
expect(ctx.suggestions[0]).toEqual(expect.objectContaining({ pluginId: 'table' }));
|
|
});
|
|
});
|
|
|
|
describe('sortSuggestions', () => {
|
|
it('should sort suggestions correctly by score', () => {
|
|
const suggestions = [
|
|
{ pluginId: 'timeseries', name: 'Time series', hash: 'b', score: VisualizationSuggestionScore.OK },
|
|
{ pluginId: 'table', name: 'Table', hash: 'a', score: VisualizationSuggestionScore.OK },
|
|
{ pluginId: 'stat', name: 'Stat', hash: 'c', score: VisualizationSuggestionScore.Good },
|
|
] satisfies PanelPluginVisualizationSuggestion[];
|
|
|
|
const dataSummary = getPanelDataSummary([
|
|
toDataFrame({
|
|
fields: [
|
|
{ name: 'Time', type: FieldType.time, values: [1, 2, 3, 4, 5] },
|
|
{ name: 'ServerA', type: FieldType.number, values: [1, 10, 50, 2, 5] },
|
|
{ name: 'ServerB', type: FieldType.number, values: [1, 10, 50, 2, 5] },
|
|
],
|
|
}),
|
|
]);
|
|
|
|
sortSuggestions(suggestions, dataSummary);
|
|
|
|
expect(suggestions[0].pluginId).toBe('stat');
|
|
expect(suggestions[1].pluginId).toBe('timeseries');
|
|
expect(suggestions[2].pluginId).toBe('table');
|
|
});
|
|
|
|
it('should sort suggestions based on core module', () => {
|
|
const suggestions = [
|
|
{
|
|
pluginId: 'fake-external-panel',
|
|
name: 'Time series',
|
|
hash: 'b',
|
|
score: VisualizationSuggestionScore.Good,
|
|
},
|
|
{
|
|
pluginId: 'fake-external-panel',
|
|
name: 'Time series',
|
|
hash: 'd',
|
|
score: VisualizationSuggestionScore.Best,
|
|
},
|
|
{ pluginId: 'timeseries', name: 'Table', hash: 'a', score: VisualizationSuggestionScore.OK },
|
|
{ pluginId: 'stat', name: 'Stat', hash: 'c', score: VisualizationSuggestionScore.Good },
|
|
] satisfies PanelPluginVisualizationSuggestion[];
|
|
|
|
const dataSummary = getPanelDataSummary([
|
|
toDataFrame({
|
|
fields: [
|
|
{ name: 'Time', type: FieldType.time, values: [1, 2, 3, 4, 5] },
|
|
{ name: 'ServerA', type: FieldType.number, values: [1, 10, 50, 2, 5] },
|
|
{ name: 'ServerB', type: FieldType.number, values: [1, 10, 50, 2, 5] },
|
|
],
|
|
}),
|
|
]);
|
|
|
|
sortSuggestions(suggestions, dataSummary);
|
|
|
|
expect(suggestions[0].pluginId).toBe('stat');
|
|
expect(suggestions[1].pluginId).toBe('timeseries');
|
|
expect(suggestions[2].pluginId).toBe('fake-external-panel');
|
|
expect(suggestions[2].hash).toBe('d');
|
|
expect(suggestions[3].pluginId).toBe('fake-external-panel');
|
|
expect(suggestions[3].hash).toBe('b');
|
|
});
|
|
});
|
|
|
|
describe('Visualization suggestions error handling', () => {
|
|
it('returns result with hasErrors flag', async () => {
|
|
const result = await getAllSuggestions({
|
|
series: [
|
|
toDataFrame({
|
|
fields: [
|
|
{ name: 'Time', type: FieldType.time, values: [1, 2] },
|
|
{ name: 'Max', type: FieldType.number, values: [1, 10] },
|
|
],
|
|
}),
|
|
],
|
|
state: LoadingState.Done,
|
|
timeRange: getDefaultTimeRange(),
|
|
});
|
|
|
|
expect(result).toHaveProperty('suggestions');
|
|
expect(result).toHaveProperty('hasErrors');
|
|
expect(result.hasErrors).toBe(false);
|
|
});
|
|
});
|
|
|
|
// this needs to happen before any
|
|
describe('loadPlugins', () => {
|
|
beforeEach(() => {
|
|
clearPanelPluginCache();
|
|
});
|
|
|
|
afterEach(() => {
|
|
if (jest.isMockFunction(pluginImporter.importPanel)) {
|
|
jest.mocked(pluginImporter.importPanel).mockRestore();
|
|
}
|
|
});
|
|
|
|
it('should swallow errors when failing to load core plugins', async () => {
|
|
jest.spyOn(console, 'error').mockImplementation();
|
|
|
|
const _importPanel = pluginImporter.importPanel;
|
|
jest.spyOn(pluginImporter, 'importPanel').mockImplementation(async (meta) => {
|
|
if (meta.id === 'timeseries') {
|
|
throw new Error('Failed to load core panel plugin');
|
|
}
|
|
return await _importPanel(meta);
|
|
});
|
|
|
|
const panelIds = ['timeseries', 'table'];
|
|
const { plugins, hasErrors } = await loadPlugins(panelIds);
|
|
|
|
expect(plugins).toEqual([expect.objectContaining({ meta: expect.objectContaining({ id: 'table' }) })]);
|
|
expect(hasErrors).toBe(true);
|
|
expect(appEvents.publish).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('should swallow errors when failing to load external plugins', async () => {
|
|
jest.spyOn(console, 'error').mockImplementation();
|
|
|
|
const panelIds = ['non-existent-panel'];
|
|
const { plugins, hasErrors } = await loadPlugins(panelIds);
|
|
|
|
expect(plugins).toEqual([]);
|
|
expect(hasErrors).toBe(false);
|
|
expect(appEvents.publish).toHaveBeenCalledWith({
|
|
type: AppEvents.alertError.name,
|
|
payload: [expect.stringContaining('Failed to load panel plugin: non-existent-panel.')],
|
|
});
|
|
});
|
|
|
|
it('should load panel plugins with suggestions', async () => {
|
|
const panelIds = ['timeseries', 'table'];
|
|
const { plugins, hasErrors } = await loadPlugins(panelIds);
|
|
|
|
expect(plugins.map((p) => p.meta.id)).toEqual(expect.arrayContaining(['timeseries', 'table']));
|
|
expect(hasErrors).toBe(false);
|
|
});
|
|
});
|
|
|
|
function repeatFrame(count: number, frame: DataFrame): DataFrame[] {
|
|
const frames: DataFrame[] = [];
|
|
for (let i = 0; i < count; i++) {
|
|
frames.push(frame);
|
|
}
|
|
return frames;
|
|
}
|