mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Canvas: Add universal data link support (#84142)
This commit is contained in:
parent
0fe5b62fa5
commit
9573c13223
2940
devenv/dev-dashboards/panel-canvas/canvas-datalinks.json
Normal file
2940
devenv/dev-dashboards/panel-canvas/canvas-datalinks.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -21,6 +21,7 @@
|
|||||||
"barchart-tooltips-legends": (import '../dev-dashboards/panel-barchart/barchart-tooltips-legends.json'),
|
"barchart-tooltips-legends": (import '../dev-dashboards/panel-barchart/barchart-tooltips-legends.json'),
|
||||||
"candlestick": (import '../dev-dashboards/panel-candlestick/candlestick.json'),
|
"candlestick": (import '../dev-dashboards/panel-candlestick/candlestick.json'),
|
||||||
"canvas-connection-examples": (import '../dev-dashboards/panel-canvas/canvas-connection-examples.json'),
|
"canvas-connection-examples": (import '../dev-dashboards/panel-canvas/canvas-connection-examples.json'),
|
||||||
|
"canvas-datalinks": (import '../dev-dashboards/panel-canvas/canvas-datalinks.json'),
|
||||||
"canvas-examples": (import '../dev-dashboards/panel-canvas/canvas-examples.json'),
|
"canvas-examples": (import '../dev-dashboards/panel-canvas/canvas-examples.json'),
|
||||||
"color_modes": (import '../dev-dashboards/panel-common/color_modes.json'),
|
"color_modes": (import '../dev-dashboards/panel-common/color_modes.json'),
|
||||||
"config-from-query": (import '../dev-dashboards/transforms/config-from-query.json'),
|
"config-from-query": (import '../dev-dashboards/transforms/config-from-query.json'),
|
||||||
|
@ -72,7 +72,7 @@ export interface CanvasElementItem<TConfig = any, TData = any> extends RegistryI
|
|||||||
/** The default width/height to use when adding */
|
/** The default width/height to use when adding */
|
||||||
defaultSize?: Placement;
|
defaultSize?: Placement;
|
||||||
|
|
||||||
prepareData?: (ctx: DimensionContext, cfg: TConfig) => TData;
|
prepareData?: (dimensionContext: DimensionContext, elementOptions: CanvasElementOptions<TConfig>) => TData;
|
||||||
|
|
||||||
/** Component used to draw */
|
/** Component used to draw */
|
||||||
display: ComponentType<CanvasElementProps<TConfig, TData>>;
|
display: ComponentType<CanvasElementProps<TConfig, TData>>;
|
||||||
|
@ -13,7 +13,7 @@ import { ButtonStyleConfig, ButtonStyleEditor } from 'app/plugins/panel/canvas/e
|
|||||||
import { callApi } from 'app/plugins/panel/canvas/editor/element/utils';
|
import { callApi } from 'app/plugins/panel/canvas/editor/element/utils';
|
||||||
import { HttpRequestMethod } from 'app/plugins/panel/canvas/panelcfg.gen';
|
import { HttpRequestMethod } from 'app/plugins/panel/canvas/panelcfg.gen';
|
||||||
|
|
||||||
import { CanvasElementItem, CanvasElementProps, defaultLightTextColor } from '../element';
|
import { CanvasElementItem, CanvasElementOptions, CanvasElementProps, defaultLightTextColor } from '../element';
|
||||||
import { Align, TextConfig, TextData } from '../types';
|
import { Align, TextConfig, TextData } from '../types';
|
||||||
|
|
||||||
interface ButtonData extends Omit<TextData, 'valign'> {
|
interface ButtonData extends Omit<TextData, 'valign'> {
|
||||||
@ -130,30 +130,32 @@ export const buttonItem: CanvasElementItem<ButtonConfig, ButtonData> = {
|
|||||||
}),
|
}),
|
||||||
|
|
||||||
// Called when data changes
|
// Called when data changes
|
||||||
prepareData: (ctx: DimensionContext, cfg: ButtonConfig) => {
|
prepareData: (dimensionContext: DimensionContext, elementOptions: CanvasElementOptions<ButtonConfig>) => {
|
||||||
const getCfgApi = () => {
|
const buttonConfig = elementOptions.config;
|
||||||
if (cfg?.api) {
|
|
||||||
cfg.api = {
|
const getAPIConfig = () => {
|
||||||
...cfg.api,
|
if (buttonConfig?.api) {
|
||||||
method: cfg.api.method ?? defaultApiConfig.method,
|
buttonConfig.api = {
|
||||||
contentType: cfg.api.contentType ?? defaultApiConfig.contentType,
|
...buttonConfig.api,
|
||||||
|
method: buttonConfig.api.method ?? defaultApiConfig.method,
|
||||||
|
contentType: buttonConfig.api.contentType ?? defaultApiConfig.contentType,
|
||||||
};
|
};
|
||||||
return cfg.api;
|
return buttonConfig.api;
|
||||||
}
|
}
|
||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
const data: ButtonData = {
|
const data: ButtonData = {
|
||||||
text: cfg?.text ? ctx.getText(cfg.text).value() : '',
|
text: buttonConfig?.text ? dimensionContext.getText(buttonConfig.text).value() : '',
|
||||||
align: cfg.align ?? Align.Center,
|
align: buttonConfig?.align ?? Align.Center,
|
||||||
size: cfg.size ?? 14,
|
size: buttonConfig?.size ?? 14,
|
||||||
api: getCfgApi(),
|
api: getAPIConfig(),
|
||||||
style: cfg?.style ?? defaultStyleConfig,
|
style: buttonConfig?.style ?? defaultStyleConfig,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (cfg.color) {
|
if (buttonConfig?.color) {
|
||||||
data.color = ctx.getColor(cfg.color).value();
|
data.color = dimensionContext.getColor(buttonConfig.color).value();
|
||||||
}
|
}
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
|
@ -7,7 +7,7 @@ import { useStyles2 } from '@grafana/ui';
|
|||||||
import { DimensionContext } from 'app/features/dimensions';
|
import { DimensionContext } from 'app/features/dimensions';
|
||||||
import { ScalarDimensionEditor } from 'app/features/dimensions/editors';
|
import { ScalarDimensionEditor } from 'app/features/dimensions/editors';
|
||||||
|
|
||||||
import { CanvasElementItem, CanvasElementProps, defaultBgColor } from '../element';
|
import { CanvasElementItem, CanvasElementOptions, CanvasElementProps, defaultBgColor } from '../element';
|
||||||
|
|
||||||
interface DroneFrontData {
|
interface DroneFrontData {
|
||||||
rollAngle?: number;
|
rollAngle?: number;
|
||||||
@ -97,9 +97,11 @@ export const droneFrontItem: CanvasElementItem = {
|
|||||||
}),
|
}),
|
||||||
|
|
||||||
// Called when data changes
|
// Called when data changes
|
||||||
prepareData: (ctx: DimensionContext, cfg: DroneFrontConfig) => {
|
prepareData: (dimensionContext: DimensionContext, elementOptions: CanvasElementOptions<DroneFrontConfig>) => {
|
||||||
|
const droneFrontConfig = elementOptions.config;
|
||||||
|
|
||||||
const data: DroneFrontData = {
|
const data: DroneFrontData = {
|
||||||
rollAngle: cfg?.rollAngle ? ctx.getScalar(cfg.rollAngle).value() : 0,
|
rollAngle: droneFrontConfig?.rollAngle ? dimensionContext.getScalar(droneFrontConfig.rollAngle).value() : 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
|
@ -7,7 +7,7 @@ import { useStyles2 } from '@grafana/ui';
|
|||||||
import { DimensionContext } from 'app/features/dimensions';
|
import { DimensionContext } from 'app/features/dimensions';
|
||||||
import { ScalarDimensionEditor } from 'app/features/dimensions/editors';
|
import { ScalarDimensionEditor } from 'app/features/dimensions/editors';
|
||||||
|
|
||||||
import { CanvasElementItem, CanvasElementProps, defaultBgColor } from '../element';
|
import { CanvasElementItem, CanvasElementOptions, CanvasElementProps, defaultBgColor } from '../element';
|
||||||
|
|
||||||
interface DroneSideData {
|
interface DroneSideData {
|
||||||
pitchAngle?: number;
|
pitchAngle?: number;
|
||||||
@ -96,9 +96,11 @@ export const droneSideItem: CanvasElementItem = {
|
|||||||
}),
|
}),
|
||||||
|
|
||||||
// Called when data changes
|
// Called when data changes
|
||||||
prepareData: (ctx: DimensionContext, cfg: DroneSideConfig) => {
|
prepareData: (dimensionContext: DimensionContext, elementOptions: CanvasElementOptions<DroneSideConfig>) => {
|
||||||
|
const droneSideConfig = elementOptions.config;
|
||||||
|
|
||||||
const data: DroneSideData = {
|
const data: DroneSideData = {
|
||||||
pitchAngle: cfg?.pitchAngle ? ctx.getScalar(cfg.pitchAngle).value() : 0,
|
pitchAngle: droneSideConfig?.pitchAngle ? dimensionContext.getScalar(droneSideConfig.pitchAngle).value() : 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
|
@ -7,7 +7,7 @@ import { useStyles2 } from '@grafana/ui';
|
|||||||
import { DimensionContext } from 'app/features/dimensions';
|
import { DimensionContext } from 'app/features/dimensions';
|
||||||
import { ScalarDimensionEditor } from 'app/features/dimensions/editors';
|
import { ScalarDimensionEditor } from 'app/features/dimensions/editors';
|
||||||
|
|
||||||
import { CanvasElementItem, CanvasElementProps, defaultBgColor } from '../element';
|
import { CanvasElementItem, CanvasElementOptions, CanvasElementProps, defaultBgColor } from '../element';
|
||||||
|
|
||||||
interface DroneTopData {
|
interface DroneTopData {
|
||||||
bRightRotorRPM?: number;
|
bRightRotorRPM?: number;
|
||||||
@ -102,13 +102,23 @@ export const droneTopItem: CanvasElementItem = {
|
|||||||
}),
|
}),
|
||||||
|
|
||||||
// Called when data changes
|
// Called when data changes
|
||||||
prepareData: (ctx: DimensionContext, cfg: DroneTopConfig) => {
|
prepareData: (dimensionContext: DimensionContext, elementOptions: CanvasElementOptions<DroneTopConfig>) => {
|
||||||
|
const droneTopConfig = elementOptions.config;
|
||||||
|
|
||||||
const data: DroneTopData = {
|
const data: DroneTopData = {
|
||||||
bRightRotorRPM: cfg?.bRightRotorRPM ? ctx.getScalar(cfg.bRightRotorRPM).value() : 0,
|
bRightRotorRPM: droneTopConfig?.bRightRotorRPM
|
||||||
bLeftRotorRPM: cfg?.bLeftRotorRPM ? ctx.getScalar(cfg.bLeftRotorRPM).value() : 0,
|
? dimensionContext.getScalar(droneTopConfig.bRightRotorRPM).value()
|
||||||
fRightRotorRPM: cfg?.fRightRotorRPM ? ctx.getScalar(cfg.fRightRotorRPM).value() : 0,
|
: 0,
|
||||||
fLeftRotorRPM: cfg?.fLeftRotorRPM ? ctx.getScalar(cfg.fLeftRotorRPM).value() : 0,
|
bLeftRotorRPM: droneTopConfig?.bLeftRotorRPM
|
||||||
yawAngle: cfg?.yawAngle ? ctx.getScalar(cfg.yawAngle).value() : 0,
|
? dimensionContext.getScalar(droneTopConfig.bLeftRotorRPM).value()
|
||||||
|
: 0,
|
||||||
|
fRightRotorRPM: droneTopConfig?.fRightRotorRPM
|
||||||
|
? dimensionContext.getScalar(droneTopConfig.fRightRotorRPM).value()
|
||||||
|
: 0,
|
||||||
|
fLeftRotorRPM: droneTopConfig?.fLeftRotorRPM
|
||||||
|
? dimensionContext.getScalar(droneTopConfig.fLeftRotorRPM).value()
|
||||||
|
: 0,
|
||||||
|
yawAngle: droneTopConfig?.yawAngle ? dimensionContext.getScalar(droneTopConfig.yawAngle).value() : 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
|
@ -8,7 +8,13 @@ import { ColorDimensionEditor } from 'app/features/dimensions/editors/ColorDimen
|
|||||||
import { TextDimensionEditor } from 'app/features/dimensions/editors/TextDimensionEditor';
|
import { TextDimensionEditor } from 'app/features/dimensions/editors/TextDimensionEditor';
|
||||||
import { getDataLinks } from 'app/plugins/panel/canvas/utils';
|
import { getDataLinks } from 'app/plugins/panel/canvas/utils';
|
||||||
|
|
||||||
import { CanvasElementItem, CanvasElementProps, defaultBgColor, defaultTextColor } from '../element';
|
import {
|
||||||
|
CanvasElementItem,
|
||||||
|
CanvasElementOptions,
|
||||||
|
CanvasElementProps,
|
||||||
|
defaultBgColor,
|
||||||
|
defaultTextColor,
|
||||||
|
} from '../element';
|
||||||
import { Align, VAlign, EllipseConfig, EllipseData } from '../types';
|
import { Align, VAlign, EllipseConfig, EllipseData } from '../types';
|
||||||
|
|
||||||
class EllipseDisplay extends PureComponent<CanvasElementProps<EllipseConfig, EllipseData>> {
|
class EllipseDisplay extends PureComponent<CanvasElementProps<EllipseConfig, EllipseData>> {
|
||||||
@ -80,26 +86,28 @@ export const ellipseItem: CanvasElementItem<EllipseConfig, EllipseData> = {
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
|
||||||
prepareData: (ctx: DimensionContext, cfg: EllipseConfig) => {
|
prepareData: (dimensionContext: DimensionContext, elementOptions: CanvasElementOptions<EllipseConfig>) => {
|
||||||
|
const ellipseConfig = elementOptions.config;
|
||||||
|
|
||||||
const data: EllipseData = {
|
const data: EllipseData = {
|
||||||
width: cfg.width,
|
width: ellipseConfig?.width,
|
||||||
text: cfg.text ? ctx.getText(cfg.text).value() : '',
|
text: ellipseConfig?.text ? dimensionContext.getText(ellipseConfig.text).value() : '',
|
||||||
align: cfg.align ?? Align.Center,
|
align: ellipseConfig?.align ?? Align.Center,
|
||||||
valign: cfg.valign ?? VAlign.Middle,
|
valign: ellipseConfig?.valign ?? VAlign.Middle,
|
||||||
size: cfg.size,
|
size: ellipseConfig?.size,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (cfg.backgroundColor) {
|
if (ellipseConfig?.backgroundColor) {
|
||||||
data.backgroundColor = ctx.getColor(cfg.backgroundColor).value();
|
data.backgroundColor = dimensionContext.getColor(ellipseConfig.backgroundColor).value();
|
||||||
}
|
}
|
||||||
if (cfg.borderColor) {
|
if (ellipseConfig?.borderColor) {
|
||||||
data.borderColor = ctx.getColor(cfg.borderColor).value();
|
data.borderColor = dimensionContext.getColor(ellipseConfig.borderColor).value();
|
||||||
}
|
}
|
||||||
if (cfg.color) {
|
if (ellipseConfig?.color) {
|
||||||
data.color = ctx.getColor(cfg.color).value();
|
data.color = dimensionContext.getColor(ellipseConfig.color).value();
|
||||||
}
|
}
|
||||||
|
|
||||||
data.links = getDataLinks(ctx, cfg, data.text);
|
data.links = getDataLinks(dimensionContext, elementOptions, data.text);
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
|
@ -2,13 +2,15 @@ import { css } from '@emotion/css';
|
|||||||
import { isString } from 'lodash';
|
import { isString } from 'lodash';
|
||||||
import React, { CSSProperties } from 'react';
|
import React, { CSSProperties } from 'react';
|
||||||
|
|
||||||
|
import { LinkModel } from '@grafana/data';
|
||||||
import { ColorDimensionConfig, ResourceDimensionConfig, ResourceDimensionMode } from '@grafana/schema';
|
import { ColorDimensionConfig, ResourceDimensionConfig, ResourceDimensionMode } from '@grafana/schema';
|
||||||
import { SanitizedSVG } from 'app/core/components/SVG/SanitizedSVG';
|
import { SanitizedSVG } from 'app/core/components/SVG/SanitizedSVG';
|
||||||
import { getPublicOrAbsoluteUrl } from 'app/features/dimensions';
|
import { getPublicOrAbsoluteUrl } from 'app/features/dimensions';
|
||||||
import { DimensionContext } from 'app/features/dimensions/context';
|
import { DimensionContext } from 'app/features/dimensions/context';
|
||||||
import { ColorDimensionEditor, ResourceDimensionEditor } from 'app/features/dimensions/editors';
|
import { ColorDimensionEditor, ResourceDimensionEditor } from 'app/features/dimensions/editors';
|
||||||
|
import { getDataLinks } from 'app/plugins/panel/canvas/utils';
|
||||||
|
|
||||||
import { CanvasElementItem, CanvasElementProps, defaultBgColor } from '../element';
|
import { CanvasElementItem, CanvasElementOptions, CanvasElementProps, defaultBgColor } from '../element';
|
||||||
import { LineConfig } from '../types';
|
import { LineConfig } from '../types';
|
||||||
|
|
||||||
export interface IconConfig {
|
export interface IconConfig {
|
||||||
@ -22,6 +24,7 @@ interface IconData {
|
|||||||
fill: string;
|
fill: string;
|
||||||
strokeColor?: string;
|
strokeColor?: string;
|
||||||
stroke?: number;
|
stroke?: number;
|
||||||
|
links?: LinkModel[];
|
||||||
}
|
}
|
||||||
|
|
||||||
// When a stoke is defined, we want the path to be in page units
|
// When a stoke is defined, we want the path to be in page units
|
||||||
@ -80,10 +83,12 @@ export const iconItem: CanvasElementItem<IconConfig, IconData> = {
|
|||||||
}),
|
}),
|
||||||
|
|
||||||
// Called when data changes
|
// Called when data changes
|
||||||
prepareData: (ctx: DimensionContext, cfg: IconConfig) => {
|
prepareData: (dimensionContext: DimensionContext, elementOptions: CanvasElementOptions<IconConfig>) => {
|
||||||
|
const iconConfig = elementOptions.config;
|
||||||
|
|
||||||
let path: string | undefined = undefined;
|
let path: string | undefined = undefined;
|
||||||
if (cfg.path) {
|
if (iconConfig?.path) {
|
||||||
path = ctx.getResource(cfg.path).value();
|
path = dimensionContext.getResource(iconConfig.path).value();
|
||||||
}
|
}
|
||||||
if (!path || !isString(path)) {
|
if (!path || !isString(path)) {
|
||||||
path = getPublicOrAbsoluteUrl('img/icons/unicons/question-circle.svg');
|
path = getPublicOrAbsoluteUrl('img/icons/unicons/question-circle.svg');
|
||||||
@ -91,15 +96,18 @@ export const iconItem: CanvasElementItem<IconConfig, IconData> = {
|
|||||||
|
|
||||||
const data: IconData = {
|
const data: IconData = {
|
||||||
path,
|
path,
|
||||||
fill: cfg.fill ? ctx.getColor(cfg.fill).value() : defaultBgColor,
|
fill: iconConfig?.fill ? dimensionContext.getColor(iconConfig.fill).value() : defaultBgColor,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (cfg.stroke?.width && cfg.stroke.color) {
|
if (iconConfig?.stroke?.width && iconConfig?.stroke.color) {
|
||||||
if (cfg.stroke.width > 0) {
|
if (iconConfig.stroke.width > 0) {
|
||||||
data.stroke = cfg.stroke?.width;
|
data.stroke = iconConfig.stroke?.width;
|
||||||
data.strokeColor = ctx.getColor(cfg.stroke.color).value();
|
data.strokeColor = dimensionContext.getColor(iconConfig.stroke.color).value();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data.links = getDataLinks(dimensionContext, elementOptions, data.path);
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -13,7 +13,13 @@ import { ColorDimensionEditor } from 'app/features/dimensions/editors/ColorDimen
|
|||||||
import { TextDimensionEditor } from 'app/features/dimensions/editors/TextDimensionEditor';
|
import { TextDimensionEditor } from 'app/features/dimensions/editors/TextDimensionEditor';
|
||||||
import { getDataLinks } from 'app/plugins/panel/canvas/utils';
|
import { getDataLinks } from 'app/plugins/panel/canvas/utils';
|
||||||
|
|
||||||
import { CanvasElementItem, CanvasElementProps, defaultBgColor, defaultTextColor } from '../element';
|
import {
|
||||||
|
CanvasElementItem,
|
||||||
|
CanvasElementOptions,
|
||||||
|
CanvasElementProps,
|
||||||
|
defaultBgColor,
|
||||||
|
defaultTextColor,
|
||||||
|
} from '../element';
|
||||||
import { ElementState } from '../runtime/element';
|
import { ElementState } from '../runtime/element';
|
||||||
import { Align, TextConfig, TextData, VAlign } from '../types';
|
import { Align, TextConfig, TextData, VAlign } from '../types';
|
||||||
|
|
||||||
@ -171,19 +177,21 @@ export const metricValueItem: CanvasElementItem<TextConfig, TextData> = {
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
|
||||||
prepareData: (ctx: DimensionContext, cfg: TextConfig) => {
|
prepareData: (dimensionContext: DimensionContext, elementOptions: CanvasElementOptions<TextConfig>) => {
|
||||||
|
const textConfig = elementOptions.config;
|
||||||
|
|
||||||
const data: TextData = {
|
const data: TextData = {
|
||||||
text: cfg.text ? ctx.getText(cfg.text).value() : '',
|
text: textConfig?.text ? dimensionContext.getText(textConfig.text).value() : '',
|
||||||
align: cfg.align ?? Align.Center,
|
align: textConfig?.align ?? Align.Center,
|
||||||
valign: cfg.valign ?? VAlign.Middle,
|
valign: textConfig?.valign ?? VAlign.Middle,
|
||||||
size: cfg.size,
|
size: textConfig?.size,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (cfg.color) {
|
if (textConfig?.color) {
|
||||||
data.color = ctx.getColor(cfg.color).value();
|
data.color = dimensionContext.getColor(textConfig.color).value();
|
||||||
}
|
}
|
||||||
|
|
||||||
data.links = getDataLinks(ctx, cfg, data.text);
|
data.links = getDataLinks(dimensionContext, elementOptions, data.text);
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
|
@ -9,7 +9,13 @@ import { ColorDimensionEditor } from 'app/features/dimensions/editors/ColorDimen
|
|||||||
import { TextDimensionEditor } from 'app/features/dimensions/editors/TextDimensionEditor';
|
import { TextDimensionEditor } from 'app/features/dimensions/editors/TextDimensionEditor';
|
||||||
import { getDataLinks } from 'app/plugins/panel/canvas/utils';
|
import { getDataLinks } from 'app/plugins/panel/canvas/utils';
|
||||||
|
|
||||||
import { CanvasElementItem, CanvasElementProps, defaultBgColor, defaultTextColor } from '../element';
|
import {
|
||||||
|
CanvasElementItem,
|
||||||
|
CanvasElementOptions,
|
||||||
|
CanvasElementProps,
|
||||||
|
defaultBgColor,
|
||||||
|
defaultTextColor,
|
||||||
|
} from '../element';
|
||||||
import { Align, TextConfig, TextData, VAlign } from '../types';
|
import { Align, TextConfig, TextData, VAlign } from '../types';
|
||||||
|
|
||||||
class RectangleDisplay extends PureComponent<CanvasElementProps<TextConfig, TextData>> {
|
class RectangleDisplay extends PureComponent<CanvasElementProps<TextConfig, TextData>> {
|
||||||
@ -70,19 +76,21 @@ export const rectangleItem: CanvasElementItem<TextConfig, TextData> = {
|
|||||||
}),
|
}),
|
||||||
|
|
||||||
// Called when data changes
|
// Called when data changes
|
||||||
prepareData: (ctx: DimensionContext, cfg: TextConfig) => {
|
prepareData: (dimensionContext: DimensionContext, elementOptions: CanvasElementOptions<TextConfig>) => {
|
||||||
|
const textConfig = elementOptions.config;
|
||||||
|
|
||||||
const data: TextData = {
|
const data: TextData = {
|
||||||
text: cfg.text ? ctx.getText(cfg.text).value() : '',
|
text: textConfig?.text ? dimensionContext.getText(textConfig.text).value() : '',
|
||||||
align: cfg.align ?? Align.Center,
|
align: textConfig?.align ?? Align.Center,
|
||||||
valign: cfg.valign ?? VAlign.Middle,
|
valign: textConfig?.valign ?? VAlign.Middle,
|
||||||
size: cfg.size,
|
size: textConfig?.size,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (cfg.color) {
|
if (textConfig?.color) {
|
||||||
data.color = ctx.getColor(cfg.color).value();
|
data.color = dimensionContext.getColor(textConfig.color).value();
|
||||||
}
|
}
|
||||||
|
|
||||||
data.links = getDataLinks(ctx, cfg, data.text);
|
data.links = getDataLinks(dimensionContext, elementOptions, data.text);
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { GrafanaTheme2 } from '@grafana/data';
|
import { GrafanaTheme2, LinkModel } from '@grafana/data';
|
||||||
import { ColorDimensionConfig, ScalarDimensionConfig } from '@grafana/schema';
|
import { ColorDimensionConfig, ScalarDimensionConfig } from '@grafana/schema';
|
||||||
import config from 'app/core/config';
|
import config from 'app/core/config';
|
||||||
import { DimensionContext } from 'app/features/dimensions';
|
import { DimensionContext } from 'app/features/dimensions';
|
||||||
import { ColorDimensionEditor, ScalarDimensionEditor } from 'app/features/dimensions/editors';
|
import { ColorDimensionEditor, ScalarDimensionEditor } from 'app/features/dimensions/editors';
|
||||||
|
import { getDataLinks } from 'app/plugins/panel/canvas/utils';
|
||||||
|
|
||||||
import { CanvasElementItem, CanvasElementProps } from '../../element';
|
import { CanvasElementItem, CanvasElementOptions, CanvasElementProps } from '../../element';
|
||||||
|
|
||||||
import { ServerDatabase } from './types/database';
|
import { ServerDatabase } from './types/database';
|
||||||
import { ServerSingle } from './types/single';
|
import { ServerSingle } from './types/single';
|
||||||
@ -26,6 +27,7 @@ export interface ServerData {
|
|||||||
statusColor?: string;
|
statusColor?: string;
|
||||||
bulbColor?: string;
|
bulbColor?: string;
|
||||||
type: ServerType;
|
type: ServerType;
|
||||||
|
links?: LinkModel[];
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ServerType {
|
enum ServerType {
|
||||||
@ -85,14 +87,20 @@ export const serverItem: CanvasElementItem<ServerConfig, ServerData> = {
|
|||||||
}),
|
}),
|
||||||
|
|
||||||
// Called when data changes
|
// Called when data changes
|
||||||
prepareData: (ctx: DimensionContext, cfg: ServerConfig) => {
|
prepareData: (dimensionContext: DimensionContext, elementOptions: CanvasElementOptions<ServerConfig>) => {
|
||||||
|
const serverConfig = elementOptions.config;
|
||||||
|
|
||||||
const data: ServerData = {
|
const data: ServerData = {
|
||||||
blinkRate: cfg?.blinkRate ? ctx.getScalar(cfg.blinkRate).value() : 0,
|
blinkRate: serverConfig?.blinkRate ? dimensionContext.getScalar(serverConfig.blinkRate).value() : 0,
|
||||||
statusColor: cfg?.statusColor ? ctx.getColor(cfg.statusColor).value() : 'transparent',
|
statusColor: serverConfig?.statusColor
|
||||||
bulbColor: cfg?.bulbColor ? ctx.getColor(cfg.bulbColor).value() : 'green',
|
? dimensionContext.getColor(serverConfig.statusColor).value()
|
||||||
type: cfg.type,
|
: 'transparent',
|
||||||
|
bulbColor: serverConfig?.bulbColor ? dimensionContext.getColor(serverConfig.bulbColor).value() : 'green',
|
||||||
|
type: serverConfig?.type ?? ServerType.Single,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
data.links = getDataLinks(dimensionContext, elementOptions, data.statusColor);
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ import { ColorDimensionEditor } from 'app/features/dimensions/editors/ColorDimen
|
|||||||
import { TextDimensionEditor } from 'app/features/dimensions/editors/TextDimensionEditor';
|
import { TextDimensionEditor } from 'app/features/dimensions/editors/TextDimensionEditor';
|
||||||
|
|
||||||
import { getDataLinks } from '../../../plugins/panel/canvas/utils';
|
import { getDataLinks } from '../../../plugins/panel/canvas/utils';
|
||||||
import { CanvasElementItem, CanvasElementProps, defaultThemeTextColor } from '../element';
|
import { CanvasElementItem, CanvasElementOptions, CanvasElementProps, defaultThemeTextColor } from '../element';
|
||||||
import { ElementState } from '../runtime/element';
|
import { ElementState } from '../runtime/element';
|
||||||
import { Align, TextConfig, TextData, VAlign } from '../types';
|
import { Align, TextConfig, TextData, VAlign } from '../types';
|
||||||
|
|
||||||
@ -149,19 +149,21 @@ export const textItem: CanvasElementItem<TextConfig, TextData> = {
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
|
||||||
prepareData: (ctx: DimensionContext, cfg: TextConfig) => {
|
prepareData: (dimensionContext: DimensionContext, elementOptions: CanvasElementOptions<TextConfig>) => {
|
||||||
|
const textConfig = elementOptions.config;
|
||||||
|
|
||||||
const data: TextData = {
|
const data: TextData = {
|
||||||
text: cfg.text ? ctx.getText(cfg.text).value() : '',
|
text: textConfig?.text ? dimensionContext.getText(textConfig.text).value() : '',
|
||||||
align: cfg.align ?? Align.Center,
|
align: textConfig?.align ?? Align.Center,
|
||||||
valign: cfg.valign ?? VAlign.Middle,
|
valign: textConfig?.valign ?? VAlign.Middle,
|
||||||
size: cfg.size,
|
size: textConfig?.size,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (cfg.color) {
|
if (textConfig?.color) {
|
||||||
data.color = ctx.getColor(cfg.color).value();
|
data.color = dimensionContext.getColor(textConfig.color).value();
|
||||||
}
|
}
|
||||||
|
|
||||||
data.links = getDataLinks(ctx, cfg, data.text);
|
data.links = getDataLinks(dimensionContext, elementOptions, data.text);
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
|
@ -1,16 +1,18 @@
|
|||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { GrafanaTheme2 } from '@grafana/data';
|
import { GrafanaTheme2, LinkModel } from '@grafana/data';
|
||||||
import { ScalarDimensionConfig } from '@grafana/schema';
|
import { ScalarDimensionConfig } from '@grafana/schema';
|
||||||
import { useStyles2 } from '@grafana/ui';
|
import { useStyles2 } from '@grafana/ui';
|
||||||
import { DimensionContext } from 'app/features/dimensions';
|
import { DimensionContext } from 'app/features/dimensions';
|
||||||
import { ScalarDimensionEditor } from 'app/features/dimensions/editors';
|
import { ScalarDimensionEditor } from 'app/features/dimensions/editors';
|
||||||
|
import { getDataLinks } from 'app/plugins/panel/canvas/utils';
|
||||||
|
|
||||||
import { CanvasElementItem, CanvasElementProps, defaultBgColor } from '../element';
|
import { CanvasElementItem, CanvasElementOptions, CanvasElementProps, defaultBgColor } from '../element';
|
||||||
|
|
||||||
interface WindTurbineData {
|
interface WindTurbineData {
|
||||||
rpm?: number;
|
rpm?: number;
|
||||||
|
links?: LinkModel[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface WindTurbineConfig {
|
interface WindTurbineConfig {
|
||||||
@ -92,11 +94,15 @@ export const windTurbineItem: CanvasElementItem = {
|
|||||||
}),
|
}),
|
||||||
|
|
||||||
// Called when data changes
|
// Called when data changes
|
||||||
prepareData: (ctx: DimensionContext, cfg: WindTurbineConfig) => {
|
prepareData: (dimensionContext: DimensionContext, elementOptions: CanvasElementOptions<WindTurbineConfig>) => {
|
||||||
|
const windTurbineConfig = elementOptions.config;
|
||||||
|
|
||||||
const data: WindTurbineData = {
|
const data: WindTurbineData = {
|
||||||
rpm: cfg?.rpm ? ctx.getScalar(cfg.rpm).value() : 0,
|
rpm: windTurbineConfig?.rpm ? dimensionContext.getScalar(windTurbineConfig.rpm).value() : 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
data.links = getDataLinks(dimensionContext, elementOptions, `${data.rpm}`);
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -296,7 +296,7 @@ export class ElementState implements LayerElement {
|
|||||||
|
|
||||||
updateData(ctx: DimensionContext) {
|
updateData(ctx: DimensionContext) {
|
||||||
if (this.item.prepareData) {
|
if (this.item.prepareData) {
|
||||||
this.data = this.item.prepareData(ctx, this.options.config);
|
this.data = this.item.prepareData(ctx, this.options);
|
||||||
this.revId++; // rerender
|
this.revId++; // rerender
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,7 +9,6 @@ import {
|
|||||||
CanvasElementItem,
|
CanvasElementItem,
|
||||||
canvasElementRegistry,
|
canvasElementRegistry,
|
||||||
CanvasElementOptions,
|
CanvasElementOptions,
|
||||||
TextConfig,
|
|
||||||
CanvasConnection,
|
CanvasConnection,
|
||||||
} from 'app/features/canvas';
|
} from 'app/features/canvas';
|
||||||
import { notFoundItem } from 'app/features/canvas/elements/notFound';
|
import { notFoundItem } from 'app/features/canvas/elements/notFound';
|
||||||
@ -107,28 +106,155 @@ export function onAddItem(sel: SelectableValue<string>, rootLayer: FrameState |
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getDataLinks(ctx: DimensionContext, cfg: TextConfig, textData: string | undefined): LinkModel[] {
|
/*
|
||||||
const panelData = ctx.getPanelData();
|
* Provided a given field add any matching data links
|
||||||
|
* Mutates the links object in place which is then returned by the `getDataLinks` function downstream
|
||||||
|
*/
|
||||||
|
const addDataLinkForField = (
|
||||||
|
field: Field<unknown>,
|
||||||
|
data: string | undefined,
|
||||||
|
linkLookup: Set<string>,
|
||||||
|
links: Array<LinkModel<Field>>
|
||||||
|
): void => {
|
||||||
|
if (field?.getLinks) {
|
||||||
|
const disp = field.display ? field.display(data) : { text: `${data}`, numeric: +data! };
|
||||||
|
field.getLinks({ calculatedValue: disp }).forEach((link) => {
|
||||||
|
const key = `${link.title}/${link.href}`;
|
||||||
|
if (!linkLookup.has(key)) {
|
||||||
|
links.push(link);
|
||||||
|
linkLookup.add(key);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: This could be refactored a fair amount, ideally the element specific config code should be owned by each element and not in this shared util file
|
||||||
|
export function getDataLinks(
|
||||||
|
dimensionContext: DimensionContext,
|
||||||
|
elementOptions: CanvasElementOptions,
|
||||||
|
data: string | undefined
|
||||||
|
): LinkModel[] {
|
||||||
|
const panelData = dimensionContext.getPanelData();
|
||||||
const frames = panelData?.series;
|
const frames = panelData?.series;
|
||||||
|
|
||||||
const links: Array<LinkModel<Field>> = [];
|
const links: Array<LinkModel<Field>> = [];
|
||||||
const linkLookup = new Set<string>();
|
const linkLookup = new Set<string>();
|
||||||
|
|
||||||
|
const elementConfig = elementOptions.config;
|
||||||
|
|
||||||
frames?.forEach((frame) => {
|
frames?.forEach((frame) => {
|
||||||
const visibleFields = frame.fields.filter((field) => !Boolean(field.config.custom?.hideFrom?.tooltip));
|
const visibleFields = frame.fields.filter((field) => !Boolean(field.config.custom?.hideFrom?.tooltip));
|
||||||
|
|
||||||
if (cfg.text?.field && visibleFields.some((f) => getFieldDisplayName(f, frame) === cfg.text?.field)) {
|
// Text config
|
||||||
const field = visibleFields.filter((field) => getFieldDisplayName(field, frame) === cfg.text?.field)[0];
|
const isTextTiedToFieldData =
|
||||||
if (field?.getLinks) {
|
elementConfig.text?.field &&
|
||||||
const disp = field.display ? field.display(textData) : { text: `${textData}`, numeric: +textData! };
|
visibleFields.some((field) => getFieldDisplayName(field, frame) === elementConfig.text?.field);
|
||||||
field.getLinks({ calculatedValue: disp }).forEach((link) => {
|
const isTextColorTiedToFieldData =
|
||||||
const key = `${link.title}/${link.href}`;
|
elementConfig.color?.field &&
|
||||||
if (!linkLookup.has(key)) {
|
visibleFields.some((field) => getFieldDisplayName(field, frame) === elementConfig.color?.field);
|
||||||
links.push(link);
|
|
||||||
linkLookup.add(key);
|
// General element config
|
||||||
}
|
const isElementBackgroundColorTiedToFieldData =
|
||||||
});
|
elementOptions?.background?.color?.field &&
|
||||||
}
|
visibleFields.some((field) => getFieldDisplayName(field, frame) === elementOptions?.background?.color?.field);
|
||||||
|
const isElementBackgroundImageTiedToFieldData =
|
||||||
|
elementOptions?.background?.image?.field &&
|
||||||
|
visibleFields.some((field) => getFieldDisplayName(field, frame) === elementOptions?.background?.image?.field);
|
||||||
|
const isElementBorderColorTiedToFieldData =
|
||||||
|
elementOptions?.border?.color?.field &&
|
||||||
|
visibleFields.some((field) => getFieldDisplayName(field, frame) === elementOptions?.border?.color?.field);
|
||||||
|
|
||||||
|
// Icon config
|
||||||
|
const isIconSVGTiedToFieldData =
|
||||||
|
elementConfig.path?.field &&
|
||||||
|
visibleFields.some((field) => getFieldDisplayName(field, frame) === elementConfig.path?.field);
|
||||||
|
const isIconColorTiedToFieldData =
|
||||||
|
elementConfig.fill?.field &&
|
||||||
|
visibleFields.some((field) => getFieldDisplayName(field, frame) === elementConfig.fill?.field);
|
||||||
|
|
||||||
|
// Wind turbine config (maybe remove / not support this?)
|
||||||
|
const isWindTurbineRPMTiedToFieldData =
|
||||||
|
elementConfig.rpm?.field &&
|
||||||
|
visibleFields.some((field) => getFieldDisplayName(field, frame) === elementConfig.rpm?.field);
|
||||||
|
|
||||||
|
// Server config
|
||||||
|
const isServerBlinkRateTiedToFieldData =
|
||||||
|
elementConfig.blinkRate?.field &&
|
||||||
|
visibleFields.some((field) => getFieldDisplayName(field, frame) === elementConfig.blinkRate?.field);
|
||||||
|
const isServerStatusColorTiedToFieldData =
|
||||||
|
elementConfig.statusColor?.field &&
|
||||||
|
visibleFields.some((field) => getFieldDisplayName(field, frame) === elementConfig.statusColor?.field);
|
||||||
|
const isServerBulbColorTiedToFieldData =
|
||||||
|
elementConfig.bulbColor?.field &&
|
||||||
|
visibleFields.some((field) => getFieldDisplayName(field, frame) === elementConfig.bulbColor?.field);
|
||||||
|
|
||||||
|
if (isTextTiedToFieldData) {
|
||||||
|
const field = visibleFields.filter((field) => getFieldDisplayName(field, frame) === elementConfig.text?.field)[0];
|
||||||
|
addDataLinkForField(field, data, linkLookup, links);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isTextColorTiedToFieldData) {
|
||||||
|
const field = visibleFields.filter(
|
||||||
|
(field) => getFieldDisplayName(field, frame) === elementConfig.color?.field
|
||||||
|
)[0];
|
||||||
|
addDataLinkForField(field, data, linkLookup, links);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isElementBackgroundColorTiedToFieldData) {
|
||||||
|
const field = visibleFields.filter(
|
||||||
|
(field) => getFieldDisplayName(field, frame) === elementOptions?.background?.color?.field
|
||||||
|
)[0];
|
||||||
|
addDataLinkForField(field, data, linkLookup, links);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isElementBackgroundImageTiedToFieldData) {
|
||||||
|
const field = visibleFields.filter(
|
||||||
|
(field) => getFieldDisplayName(field, frame) === elementOptions?.background?.image?.field
|
||||||
|
)[0];
|
||||||
|
addDataLinkForField(field, data, linkLookup, links);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isElementBorderColorTiedToFieldData) {
|
||||||
|
const field = visibleFields.filter(
|
||||||
|
(field) => getFieldDisplayName(field, frame) === elementOptions?.border?.color?.field
|
||||||
|
)[0];
|
||||||
|
addDataLinkForField(field, data, linkLookup, links);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isIconSVGTiedToFieldData) {
|
||||||
|
const field = visibleFields.filter((field) => getFieldDisplayName(field, frame) === elementConfig.path?.field)[0];
|
||||||
|
addDataLinkForField(field, data, linkLookup, links);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isIconColorTiedToFieldData) {
|
||||||
|
const field = visibleFields.filter((field) => getFieldDisplayName(field, frame) === elementConfig.fill?.field)[0];
|
||||||
|
addDataLinkForField(field, data, linkLookup, links);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isWindTurbineRPMTiedToFieldData) {
|
||||||
|
const field = visibleFields.filter((field) => getFieldDisplayName(field, frame) === elementConfig.rpm?.field)[0];
|
||||||
|
addDataLinkForField(field, data, linkLookup, links);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isServerBlinkRateTiedToFieldData) {
|
||||||
|
const field = visibleFields.filter(
|
||||||
|
(field) => getFieldDisplayName(field, frame) === elementConfig.blinkRate?.field
|
||||||
|
)[0];
|
||||||
|
addDataLinkForField(field, data, linkLookup, links);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isServerStatusColorTiedToFieldData) {
|
||||||
|
const field = visibleFields.filter(
|
||||||
|
(field) => getFieldDisplayName(field, frame) === elementConfig.statusColor?.field
|
||||||
|
)[0];
|
||||||
|
addDataLinkForField(field, data, linkLookup, links);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isServerBulbColorTiedToFieldData) {
|
||||||
|
const field = visibleFields.filter(
|
||||||
|
(field) => getFieldDisplayName(field, frame) === elementConfig.bulbColor?.field
|
||||||
|
)[0];
|
||||||
|
addDataLinkForField(field, data, linkLookup, links);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user