mirror of
https://github.com/grafana/grafana.git
synced 2025-02-04 12:41:12 -06:00
798 lines
21 KiB
TypeScript
798 lines
21 KiB
TypeScript
import { omitBy, pickBy, isNil, isNumber, isString } from 'lodash';
|
|
|
|
import {
|
|
ConfigOverrideRule,
|
|
DynamicConfigValue,
|
|
FieldColorModeId,
|
|
FieldConfig,
|
|
FieldConfigProperty,
|
|
FieldConfigSource,
|
|
FieldMatcherID,
|
|
fieldReducers,
|
|
FieldType,
|
|
NullValueMode,
|
|
PanelTypeChangedHandler,
|
|
ReducerID,
|
|
Threshold,
|
|
ThresholdsMode,
|
|
} from '@grafana/data';
|
|
import {
|
|
LegendDisplayMode,
|
|
TooltipDisplayMode,
|
|
AxisPlacement,
|
|
GraphDrawStyle,
|
|
GraphFieldConfig,
|
|
GraphGradientMode,
|
|
GraphThresholdsStyleMode,
|
|
LineInterpolation,
|
|
LineStyle,
|
|
VisibilityMode,
|
|
ScaleDistribution,
|
|
StackingMode,
|
|
SortOrder,
|
|
GraphTransform,
|
|
AnnotationQuery,
|
|
ComparisonOperation,
|
|
} from '@grafana/schema';
|
|
import { TimeRegionConfig } from 'app/core/utils/timeRegions';
|
|
import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv';
|
|
import { getTimeSrv } from 'app/features/dashboard/services/TimeSrv';
|
|
import { DashboardAnnotationsDataLayer } from 'app/features/dashboard-scene/scene/DashboardAnnotationsDataLayer';
|
|
import { DashboardScene } from 'app/features/dashboard-scene/scene/DashboardScene';
|
|
import { dashboardSceneGraph } from 'app/features/dashboard-scene/utils/dashboardSceneGraph';
|
|
import { GrafanaQuery, GrafanaQueryType } from 'app/plugins/datasource/grafana/types';
|
|
|
|
import { defaultGraphConfig } from './config';
|
|
import { Options } from './panelcfg.gen';
|
|
|
|
let dashboardRefreshDebouncer: ReturnType<typeof setTimeout> | null = null;
|
|
|
|
/**
|
|
* This is called when the panel changes from another panel
|
|
*/
|
|
export const graphPanelChangedHandler: PanelTypeChangedHandler = (
|
|
panel,
|
|
prevPluginId,
|
|
prevOptions,
|
|
prevFieldConfig
|
|
) => {
|
|
// Changing from angular/flot panel to react/uPlot
|
|
if (prevPluginId === 'graph' && prevOptions.angular) {
|
|
const { fieldConfig, options, annotations } = graphToTimeseriesOptions({
|
|
...prevOptions.angular,
|
|
fieldConfig: prevFieldConfig,
|
|
panel: panel,
|
|
});
|
|
|
|
if (annotations?.length > 0) {
|
|
addAnnotationsToDashboard(annotations);
|
|
}
|
|
|
|
panel.fieldConfig = fieldConfig; // Mutates the incoming panel
|
|
panel.alert = prevOptions.angular.alert;
|
|
return options;
|
|
}
|
|
|
|
//fixes graph -> viz renaming in custom.hideFrom field config by mutation.
|
|
migrateHideFrom(panel);
|
|
|
|
return {};
|
|
};
|
|
|
|
export function graphToTimeseriesOptions(angular: any): {
|
|
fieldConfig: FieldConfigSource;
|
|
options: Options;
|
|
annotations: AnnotationQuery[];
|
|
} {
|
|
let annotations: AnnotationQuery[] = [];
|
|
|
|
const overrides: ConfigOverrideRule[] = angular.fieldConfig?.overrides ?? [];
|
|
const yaxes = angular.yaxes ?? [];
|
|
let y1 = getFieldConfigFromOldAxis(yaxes[0]);
|
|
if (angular.fieldConfig?.defaults) {
|
|
y1 = {
|
|
...angular.fieldConfig?.defaults,
|
|
...y1, // Keep the y-axis unit and custom
|
|
};
|
|
}
|
|
|
|
// Dashes
|
|
const dash: LineStyle = {
|
|
fill: angular.dashes ? 'dash' : 'solid',
|
|
dash: [angular.dashLength ?? 10, angular.spaceLength ?? 10],
|
|
};
|
|
|
|
// "seriesOverrides": [
|
|
// {
|
|
// "$$hashKey": "object:183",
|
|
// "alias": "B-series",
|
|
// "fill": 3,
|
|
// "nullPointMode": "null as zero",
|
|
// "lines": true,
|
|
// "linewidth": 2
|
|
// }
|
|
// ],
|
|
|
|
if (angular.aliasColors) {
|
|
for (const alias of Object.keys(angular.aliasColors)) {
|
|
const color = angular.aliasColors[alias];
|
|
if (color) {
|
|
overrides.push({
|
|
matcher: {
|
|
id: FieldMatcherID.byName,
|
|
options: alias,
|
|
},
|
|
properties: [
|
|
{
|
|
id: FieldConfigProperty.Color,
|
|
value: {
|
|
mode: FieldColorModeId.Fixed,
|
|
fixedColor: color,
|
|
},
|
|
},
|
|
],
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
let hasFillBelowTo = false;
|
|
|
|
if (angular.seriesOverrides?.length) {
|
|
for (const seriesOverride of angular.seriesOverrides) {
|
|
if (!seriesOverride.alias) {
|
|
continue; // the matcher config
|
|
}
|
|
const aliasIsRegex = /^([/~@;%#'])(.*?)\1([gimsuy]*)$/.test(seriesOverride.alias);
|
|
const rule: ConfigOverrideRule = {
|
|
matcher: {
|
|
id: aliasIsRegex ? FieldMatcherID.byRegexp : FieldMatcherID.byName,
|
|
options: seriesOverride.alias,
|
|
},
|
|
properties: [],
|
|
};
|
|
let dashOverride: LineStyle | undefined = undefined;
|
|
|
|
for (const p of Object.keys(seriesOverride)) {
|
|
const v = seriesOverride[p];
|
|
switch (p) {
|
|
// Ignore
|
|
case 'alias':
|
|
case '$$hashKey':
|
|
break;
|
|
// Link to y axis settings
|
|
case 'yaxis':
|
|
if (2 === v) {
|
|
const y2 = getFieldConfigFromOldAxis(yaxes[1]);
|
|
fillY2DynamicValues(y1, y2, rule.properties);
|
|
}
|
|
break;
|
|
case 'fill':
|
|
rule.properties.push({
|
|
id: 'custom.fillOpacity',
|
|
value: v * 10, // was 0-10, new graph is 0 - 100
|
|
});
|
|
break;
|
|
case 'fillBelowTo':
|
|
hasFillBelowTo = true;
|
|
rule.properties.push({
|
|
id: 'custom.fillBelowTo',
|
|
value: v,
|
|
});
|
|
break;
|
|
case 'fillGradient':
|
|
if (v) {
|
|
rule.properties.push({
|
|
id: 'custom.fillGradient',
|
|
value: 'opacity', // was 0-10
|
|
});
|
|
rule.properties.push({
|
|
id: 'custom.fillOpacity',
|
|
value: v * 10, // was 0-10, new graph is 0 - 100
|
|
});
|
|
}
|
|
break;
|
|
case 'points':
|
|
rule.properties.push({
|
|
id: 'custom.showPoints',
|
|
value: v ? VisibilityMode.Always : VisibilityMode.Never,
|
|
});
|
|
break;
|
|
case 'bars':
|
|
if (v) {
|
|
rule.properties.push({
|
|
id: 'custom.drawStyle',
|
|
value: GraphDrawStyle.Bars,
|
|
});
|
|
rule.properties.push({
|
|
id: 'custom.fillOpacity',
|
|
value: 100, // solid bars
|
|
});
|
|
} else {
|
|
rule.properties.push({
|
|
id: 'custom.drawStyle',
|
|
value: GraphDrawStyle.Line, // Change from bars
|
|
});
|
|
}
|
|
break;
|
|
case 'lines':
|
|
if (v) {
|
|
rule.properties.push({
|
|
id: 'custom.drawStyle',
|
|
value: 'line',
|
|
});
|
|
} else {
|
|
rule.properties.push({
|
|
id: 'custom.lineWidth',
|
|
value: 0,
|
|
});
|
|
}
|
|
break;
|
|
case 'linewidth':
|
|
rule.properties.push({
|
|
id: 'custom.lineWidth',
|
|
value: v,
|
|
});
|
|
break;
|
|
case 'pointradius':
|
|
rule.properties.push({
|
|
id: 'custom.pointSize',
|
|
value: 2 + v * 2,
|
|
});
|
|
break;
|
|
case 'dashLength':
|
|
case 'spaceLength':
|
|
case 'dashes':
|
|
if (!dashOverride) {
|
|
dashOverride = {
|
|
fill: dash.fill,
|
|
dash: [...dash.dash!],
|
|
};
|
|
}
|
|
switch (p) {
|
|
case 'dashLength':
|
|
dashOverride.dash![0] = v;
|
|
break;
|
|
case 'spaceLength':
|
|
dashOverride.dash![1] = v;
|
|
break;
|
|
case 'dashes':
|
|
dashOverride.fill = v ? 'dash' : 'solid';
|
|
break;
|
|
}
|
|
break;
|
|
case 'stack':
|
|
rule.properties.push({
|
|
id: 'custom.stacking',
|
|
value: getStackingFromOverrides(v),
|
|
});
|
|
break;
|
|
case 'color':
|
|
rule.properties.push({
|
|
id: 'color',
|
|
value: {
|
|
fixedColor: v,
|
|
mode: FieldColorModeId.Fixed,
|
|
},
|
|
});
|
|
break;
|
|
case 'transform':
|
|
rule.properties.push({
|
|
id: 'custom.transform',
|
|
value: v === 'negative-Y' ? GraphTransform.NegativeY : GraphTransform.Constant,
|
|
});
|
|
break;
|
|
default:
|
|
console.log('Ignore override migration:', seriesOverride.alias, p, v);
|
|
}
|
|
}
|
|
if (dashOverride) {
|
|
rule.properties.push({
|
|
id: 'custom.lineStyle',
|
|
value: dashOverride,
|
|
});
|
|
}
|
|
if (rule.properties.length) {
|
|
overrides.push(rule);
|
|
}
|
|
}
|
|
}
|
|
|
|
const graph: GraphFieldConfig = y1.custom ?? {};
|
|
graph.drawStyle = angular.bars ? GraphDrawStyle.Bars : angular.lines ? GraphDrawStyle.Line : GraphDrawStyle.Points;
|
|
|
|
if (angular.points) {
|
|
graph.showPoints = VisibilityMode.Always;
|
|
|
|
if (isNumber(angular.pointradius)) {
|
|
graph.pointSize = 2 + angular.pointradius * 2;
|
|
}
|
|
} else if (graph.drawStyle !== GraphDrawStyle.Points) {
|
|
graph.showPoints = VisibilityMode.Never;
|
|
}
|
|
|
|
graph.lineWidth = angular.linewidth;
|
|
if (dash.fill !== 'solid') {
|
|
graph.lineStyle = dash;
|
|
}
|
|
|
|
if (hasFillBelowTo) {
|
|
graph.fillOpacity = 35; // bands are hardcoded in flot
|
|
} else if (isNumber(angular.fill)) {
|
|
graph.fillOpacity = angular.fill * 10; // fill was 0 - 10, new is 0 to 100
|
|
}
|
|
|
|
if (isNumber(angular.fillGradient) && angular.fillGradient > 0) {
|
|
graph.gradientMode = GraphGradientMode.Opacity;
|
|
graph.fillOpacity = angular.fillGradient * 10; // fill is 0-10
|
|
}
|
|
|
|
graph.spanNulls = angular.nullPointMode === NullValueMode.Ignore;
|
|
|
|
if (angular.steppedLine) {
|
|
graph.lineInterpolation = LineInterpolation.StepAfter;
|
|
}
|
|
|
|
if (graph.drawStyle === GraphDrawStyle.Bars) {
|
|
graph.fillOpacity = 100; // bars were always
|
|
}
|
|
|
|
if (angular.stack) {
|
|
graph.stacking = {
|
|
mode: angular.percentage ? StackingMode.Percent : StackingMode.Normal,
|
|
group: defaultGraphConfig.stacking!.group,
|
|
};
|
|
|
|
if (angular.percentage) {
|
|
if (angular.yaxis) {
|
|
delete y1.min;
|
|
delete y1.max;
|
|
|
|
// TimeSeries currently uses 0-1 for percent, so allowing zero leaves only top and bottom ticks.
|
|
// removing it feels better. probably should fix in TimeSeries, but let's kick it down the road
|
|
if (y1.decimals === 0) {
|
|
delete y1.decimals;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
y1.custom = omitBy(graph, isNil);
|
|
y1.nullValueMode = angular.nullPointMode;
|
|
|
|
const options: Options = {
|
|
legend: {
|
|
displayMode: LegendDisplayMode.List,
|
|
showLegend: true,
|
|
placement: 'bottom',
|
|
calcs: [],
|
|
},
|
|
tooltip: {
|
|
mode: TooltipDisplayMode.Single,
|
|
sort: SortOrder.None,
|
|
},
|
|
};
|
|
|
|
// Legend config migration
|
|
const legendConfig = angular.legend;
|
|
if (legendConfig) {
|
|
if (legendConfig.show) {
|
|
options.legend.displayMode = legendConfig.alignAsTable ? LegendDisplayMode.Table : LegendDisplayMode.List;
|
|
} else {
|
|
options.legend.showLegend = false;
|
|
}
|
|
|
|
if (legendConfig.rightSide) {
|
|
options.legend.placement = 'right';
|
|
}
|
|
|
|
if (angular.legend.values) {
|
|
const enabledLegendValues = pickBy(angular.legend);
|
|
options.legend.calcs = getReducersFromLegend(enabledLegendValues);
|
|
}
|
|
|
|
if (angular.legend.sideWidth) {
|
|
options.legend.width = angular.legend.sideWidth;
|
|
}
|
|
|
|
if (legendConfig.hideZero) {
|
|
overrides.push(getLegendHideFromOverride(ReducerID.allIsZero));
|
|
}
|
|
|
|
if (legendConfig.hideEmpty) {
|
|
overrides.push(getLegendHideFromOverride(ReducerID.allIsNull));
|
|
}
|
|
}
|
|
|
|
// timeRegions migration
|
|
if (angular.timeRegions?.length) {
|
|
let regions = angular.timeRegions.map((old: GraphTimeRegionConfig, idx: number) => ({
|
|
name: `T${idx}`,
|
|
color: old.colorMode !== 'custom' ? old.colorMode : old.fillColor,
|
|
line: old.line,
|
|
fill: old.fill,
|
|
fromDayOfWeek: old.fromDayOfWeek,
|
|
toDayOfWeek: old.toDayOfWeek,
|
|
from: old.from,
|
|
to: old.to,
|
|
}));
|
|
|
|
regions.forEach((region: GraphTimeRegionConfig, idx: number) => {
|
|
const anno: AnnotationQuery<GrafanaQuery> = {
|
|
datasource: {
|
|
type: 'datasource',
|
|
uid: 'grafana',
|
|
},
|
|
enable: true,
|
|
hide: true, // don't show the toggle at the top of the dashboard
|
|
filter: {
|
|
exclude: false,
|
|
ids: [angular.panel.id],
|
|
},
|
|
iconColor: region.fillColor ?? (region as any).color,
|
|
name: `Time region for panel ${angular.panel.title}${idx > 0 ? ` ${idx}` : ''}`,
|
|
target: {
|
|
queryType: GrafanaQueryType.TimeRegions,
|
|
refId: 'Anno',
|
|
timeRegion: {
|
|
fromDayOfWeek: region.fromDayOfWeek,
|
|
toDayOfWeek: region.toDayOfWeek,
|
|
from: region.from,
|
|
to: region.to,
|
|
timezone: 'utc', // graph panel was always UTC
|
|
},
|
|
},
|
|
};
|
|
|
|
if (region.fill) {
|
|
annotations.push(anno);
|
|
} else if (region.line) {
|
|
anno.iconColor = region.lineColor ?? 'white';
|
|
annotations.push(anno);
|
|
}
|
|
});
|
|
}
|
|
|
|
const tooltipConfig = angular.tooltip;
|
|
if (tooltipConfig) {
|
|
if (tooltipConfig.shared !== undefined) {
|
|
options.tooltip.mode = tooltipConfig.shared ? TooltipDisplayMode.Multi : TooltipDisplayMode.Single;
|
|
}
|
|
|
|
if (tooltipConfig.sort !== undefined && tooltipConfig.shared) {
|
|
switch (tooltipConfig.sort) {
|
|
case 1:
|
|
options.tooltip.sort = SortOrder.Ascending;
|
|
break;
|
|
case 2:
|
|
options.tooltip.sort = SortOrder.Descending;
|
|
break;
|
|
default:
|
|
options.tooltip.sort = SortOrder.None;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (angular.thresholds && angular.thresholds.length > 0) {
|
|
let steps: Threshold[] = [];
|
|
let area = false;
|
|
let line = false;
|
|
|
|
const sorted = (angular.thresholds as AngularThreshold[]).sort((a, b) => (a.value > b.value ? 1 : -1));
|
|
|
|
for (let idx = 0; idx < sorted.length; idx++) {
|
|
const threshold = sorted[idx];
|
|
const next = sorted.length > idx + 1 ? sorted[idx + 1] : null;
|
|
|
|
if (threshold.fill) {
|
|
area = true;
|
|
}
|
|
|
|
if (threshold.line) {
|
|
line = true;
|
|
}
|
|
|
|
if (threshold.op === 'gt') {
|
|
steps.push({
|
|
value: threshold.value,
|
|
color: getThresholdColor(threshold),
|
|
});
|
|
}
|
|
|
|
if (threshold.op === 'lt') {
|
|
if (steps.length === 0) {
|
|
steps.push({
|
|
value: -Infinity,
|
|
color: getThresholdColor(threshold),
|
|
});
|
|
}
|
|
|
|
// next op is gt and there is a gap set color to transparent
|
|
if (next && next.op === 'gt' && next.value > threshold.value) {
|
|
steps.push({
|
|
value: threshold.value,
|
|
color: 'transparent',
|
|
});
|
|
// if next is a lt we need to use its color
|
|
} else if (next && next.op === 'lt') {
|
|
steps.push({
|
|
value: threshold.value,
|
|
color: getThresholdColor(next),
|
|
});
|
|
} else {
|
|
steps.push({
|
|
value: threshold.value,
|
|
color: 'transparent',
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
// if now less then threshold add an -Infinity base that is transparent
|
|
if (steps.length > 0 && steps[0].value !== -Infinity) {
|
|
steps.unshift({
|
|
color: 'transparent',
|
|
value: -Infinity,
|
|
});
|
|
}
|
|
|
|
let displayMode = area ? GraphThresholdsStyleMode.Area : GraphThresholdsStyleMode.Line;
|
|
if (line && area) {
|
|
displayMode = GraphThresholdsStyleMode.LineAndArea;
|
|
}
|
|
|
|
// TODO move into standard ThresholdConfig ?
|
|
y1.custom.thresholdsStyle = { mode: displayMode };
|
|
|
|
y1.thresholds = {
|
|
mode: ThresholdsMode.Absolute,
|
|
steps,
|
|
};
|
|
}
|
|
|
|
if (angular.xaxis && angular.xaxis.show === false && angular.xaxis.mode === 'time') {
|
|
overrides.push({
|
|
matcher: {
|
|
id: FieldMatcherID.byType,
|
|
options: FieldType.time,
|
|
},
|
|
properties: [
|
|
{
|
|
id: 'custom.axisPlacement',
|
|
value: AxisPlacement.Hidden,
|
|
},
|
|
],
|
|
});
|
|
}
|
|
return {
|
|
fieldConfig: {
|
|
defaults: omitBy(y1, isNil),
|
|
overrides,
|
|
},
|
|
options,
|
|
annotations,
|
|
};
|
|
}
|
|
|
|
interface GraphTimeRegionConfig extends TimeRegionConfig {
|
|
colorMode: string;
|
|
fill: boolean;
|
|
fillColor: string;
|
|
line: boolean;
|
|
lineColor: string;
|
|
}
|
|
|
|
function getThresholdColor(threshold: AngularThreshold): string {
|
|
if (threshold.colorMode === 'critical') {
|
|
return 'red';
|
|
}
|
|
|
|
if (threshold.colorMode === 'warning') {
|
|
return 'orange';
|
|
}
|
|
|
|
if (threshold.colorMode === 'custom') {
|
|
return threshold.fillColor || threshold.lineColor;
|
|
}
|
|
|
|
return 'red';
|
|
}
|
|
|
|
interface AngularThreshold {
|
|
op: string;
|
|
fill: boolean;
|
|
line: boolean;
|
|
value: number;
|
|
colorMode: 'critical' | 'warning' | 'custom';
|
|
yaxis?: 'left' | 'right';
|
|
fillColor: string;
|
|
lineColor: string;
|
|
}
|
|
|
|
// {
|
|
// "label": "Y111",
|
|
// "show": true,
|
|
// "logBase": 10,
|
|
// "min": "0",
|
|
// "max": "1000",
|
|
// "format": "areaMI2",
|
|
// "$$hashKey": "object:19",
|
|
// "decimals": 3
|
|
// },
|
|
function getFieldConfigFromOldAxis(obj: any): FieldConfig<GraphFieldConfig> {
|
|
if (!obj) {
|
|
return {};
|
|
}
|
|
const graph: GraphFieldConfig = {
|
|
axisPlacement: obj.show ? AxisPlacement.Auto : AxisPlacement.Hidden,
|
|
};
|
|
if (obj.label) {
|
|
graph.axisLabel = obj.label;
|
|
}
|
|
if (obj.logBase) {
|
|
const log: number = obj.logBase;
|
|
if (log === 2 || log === 10) {
|
|
graph.scaleDistribution = {
|
|
type: ScaleDistribution.Log,
|
|
log,
|
|
};
|
|
}
|
|
}
|
|
return omitBy(
|
|
{
|
|
unit: obj.format,
|
|
decimals: validNumber(obj.decimals),
|
|
min: validNumber(obj.min),
|
|
max: validNumber(obj.max),
|
|
custom: graph,
|
|
},
|
|
isNil
|
|
);
|
|
}
|
|
|
|
function fillY2DynamicValues(
|
|
y1: FieldConfig<GraphFieldConfig>,
|
|
y2: FieldConfig<GraphFieldConfig>,
|
|
props: DynamicConfigValue[]
|
|
) {
|
|
// The standard properties
|
|
for (const [key, value] of Object.entries(y2)) {
|
|
if (key !== 'custom' && value !== (y1 as any)[key]) {
|
|
props.push({
|
|
id: key,
|
|
value,
|
|
});
|
|
}
|
|
}
|
|
|
|
props.push({
|
|
id: `custom.axisPlacement`,
|
|
value: AxisPlacement.Right,
|
|
});
|
|
|
|
// Add any custom property
|
|
const y1G = y1.custom ?? {};
|
|
const y2G = y2.custom ?? {};
|
|
for (const [key, value] of Object.entries(y2G)) {
|
|
if (value !== (y1G as any)[key]) {
|
|
props.push({
|
|
id: `custom.${key}`,
|
|
value,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
function validNumber(val: unknown): number | undefined {
|
|
if (isNumber(val)) {
|
|
return val;
|
|
}
|
|
if (isString(val)) {
|
|
const num = Number(val);
|
|
if (!isNaN(num)) {
|
|
return num;
|
|
}
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
function getReducersFromLegend(obj: Record<string, unknown>): string[] {
|
|
const ids: string[] = [];
|
|
for (const key in obj) {
|
|
const r = fieldReducers.getIfExists(key);
|
|
if (r) {
|
|
ids.push(r.id);
|
|
}
|
|
}
|
|
return ids;
|
|
}
|
|
|
|
function migrateHideFrom(panel: {
|
|
fieldConfig?: { defaults?: { custom?: { hideFrom?: any } }; overrides: ConfigOverrideRule[] };
|
|
}) {
|
|
if (panel.fieldConfig?.defaults?.custom?.hideFrom?.graph !== undefined) {
|
|
panel.fieldConfig.defaults.custom.hideFrom.viz = panel.fieldConfig.defaults.custom.hideFrom.graph;
|
|
delete panel.fieldConfig.defaults.custom.hideFrom.graph;
|
|
}
|
|
if (panel.fieldConfig?.overrides) {
|
|
panel.fieldConfig.overrides = panel.fieldConfig.overrides.map((fr) => {
|
|
fr.properties = fr.properties.map((p) => {
|
|
if (p.id === 'custom.hideFrom' && p.value.graph) {
|
|
p.value.viz = p.value.graph;
|
|
delete p.value.graph;
|
|
}
|
|
return p;
|
|
});
|
|
return fr;
|
|
});
|
|
}
|
|
}
|
|
|
|
function getLegendHideFromOverride(reducer: ReducerID.allIsZero | ReducerID.allIsNull) {
|
|
return {
|
|
matcher: {
|
|
id: FieldMatcherID.byValue,
|
|
options: {
|
|
reducer: reducer,
|
|
op: ComparisonOperation.GTE,
|
|
value: 0,
|
|
},
|
|
},
|
|
properties: [
|
|
{
|
|
id: 'custom.hideFrom',
|
|
value: {
|
|
tooltip: true,
|
|
viz: false,
|
|
legend: true,
|
|
},
|
|
},
|
|
],
|
|
};
|
|
}
|
|
|
|
function getStackingFromOverrides(value: Boolean | string) {
|
|
const defaultGroupName = defaultGraphConfig.stacking?.group;
|
|
return {
|
|
mode: value ? StackingMode.Normal : StackingMode.None,
|
|
group: isString(value) ? value : defaultGroupName,
|
|
};
|
|
}
|
|
|
|
function addAnnotationsToDashboard(annotations: AnnotationQuery[]) {
|
|
const scene = window.__grafanaSceneContext;
|
|
|
|
if (scene instanceof DashboardScene) {
|
|
const dataLayers = dashboardSceneGraph.getDataLayers(scene);
|
|
const annotationLayers = [...dataLayers.state.annotationLayers];
|
|
|
|
for (let annotation of annotations) {
|
|
const newAnnotation = new DashboardAnnotationsDataLayer({
|
|
key: `annotations-${annotation.name}`,
|
|
query: annotation,
|
|
name: annotation.name,
|
|
isEnabled: annotation.enable,
|
|
isHidden: annotation.hide,
|
|
});
|
|
|
|
annotationLayers.push(newAnnotation);
|
|
}
|
|
|
|
dataLayers.setState({ annotationLayers });
|
|
return;
|
|
}
|
|
|
|
const dashboard = getDashboardSrv().getCurrent();
|
|
if (dashboard) {
|
|
dashboard.annotations.list = [...dashboard.annotations.list, ...annotations];
|
|
|
|
// Trigger a full dashboard refresh when annotations change
|
|
if (dashboardRefreshDebouncer == null) {
|
|
dashboardRefreshDebouncer = setTimeout(() => {
|
|
dashboardRefreshDebouncer = null;
|
|
getTimeSrv().refreshTimeModel();
|
|
});
|
|
}
|
|
}
|
|
}
|