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'),
|
||||
"candlestick": (import '../dev-dashboards/panel-candlestick/candlestick.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'),
|
||||
"color_modes": (import '../dev-dashboards/panel-common/color_modes.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 */
|
||||
defaultSize?: Placement;
|
||||
|
||||
prepareData?: (ctx: DimensionContext, cfg: TConfig) => TData;
|
||||
prepareData?: (dimensionContext: DimensionContext, elementOptions: CanvasElementOptions<TConfig>) => TData;
|
||||
|
||||
/** Component used to draw */
|
||||
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 { 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';
|
||||
|
||||
interface ButtonData extends Omit<TextData, 'valign'> {
|
||||
@ -130,30 +130,32 @@ export const buttonItem: CanvasElementItem<ButtonConfig, ButtonData> = {
|
||||
}),
|
||||
|
||||
// Called when data changes
|
||||
prepareData: (ctx: DimensionContext, cfg: ButtonConfig) => {
|
||||
const getCfgApi = () => {
|
||||
if (cfg?.api) {
|
||||
cfg.api = {
|
||||
...cfg.api,
|
||||
method: cfg.api.method ?? defaultApiConfig.method,
|
||||
contentType: cfg.api.contentType ?? defaultApiConfig.contentType,
|
||||
prepareData: (dimensionContext: DimensionContext, elementOptions: CanvasElementOptions<ButtonConfig>) => {
|
||||
const buttonConfig = elementOptions.config;
|
||||
|
||||
const getAPIConfig = () => {
|
||||
if (buttonConfig?.api) {
|
||||
buttonConfig.api = {
|
||||
...buttonConfig.api,
|
||||
method: buttonConfig.api.method ?? defaultApiConfig.method,
|
||||
contentType: buttonConfig.api.contentType ?? defaultApiConfig.contentType,
|
||||
};
|
||||
return cfg.api;
|
||||
return buttonConfig.api;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const data: ButtonData = {
|
||||
text: cfg?.text ? ctx.getText(cfg.text).value() : '',
|
||||
align: cfg.align ?? Align.Center,
|
||||
size: cfg.size ?? 14,
|
||||
api: getCfgApi(),
|
||||
style: cfg?.style ?? defaultStyleConfig,
|
||||
text: buttonConfig?.text ? dimensionContext.getText(buttonConfig.text).value() : '',
|
||||
align: buttonConfig?.align ?? Align.Center,
|
||||
size: buttonConfig?.size ?? 14,
|
||||
api: getAPIConfig(),
|
||||
style: buttonConfig?.style ?? defaultStyleConfig,
|
||||
};
|
||||
|
||||
if (cfg.color) {
|
||||
data.color = ctx.getColor(cfg.color).value();
|
||||
if (buttonConfig?.color) {
|
||||
data.color = dimensionContext.getColor(buttonConfig.color).value();
|
||||
}
|
||||
|
||||
return data;
|
||||
|
@ -7,7 +7,7 @@ import { useStyles2 } from '@grafana/ui';
|
||||
import { DimensionContext } from 'app/features/dimensions';
|
||||
import { ScalarDimensionEditor } from 'app/features/dimensions/editors';
|
||||
|
||||
import { CanvasElementItem, CanvasElementProps, defaultBgColor } from '../element';
|
||||
import { CanvasElementItem, CanvasElementOptions, CanvasElementProps, defaultBgColor } from '../element';
|
||||
|
||||
interface DroneFrontData {
|
||||
rollAngle?: number;
|
||||
@ -97,9 +97,11 @@ export const droneFrontItem: CanvasElementItem = {
|
||||
}),
|
||||
|
||||
// Called when data changes
|
||||
prepareData: (ctx: DimensionContext, cfg: DroneFrontConfig) => {
|
||||
prepareData: (dimensionContext: DimensionContext, elementOptions: CanvasElementOptions<DroneFrontConfig>) => {
|
||||
const droneFrontConfig = elementOptions.config;
|
||||
|
||||
const data: DroneFrontData = {
|
||||
rollAngle: cfg?.rollAngle ? ctx.getScalar(cfg.rollAngle).value() : 0,
|
||||
rollAngle: droneFrontConfig?.rollAngle ? dimensionContext.getScalar(droneFrontConfig.rollAngle).value() : 0,
|
||||
};
|
||||
|
||||
return data;
|
||||
|
@ -7,7 +7,7 @@ import { useStyles2 } from '@grafana/ui';
|
||||
import { DimensionContext } from 'app/features/dimensions';
|
||||
import { ScalarDimensionEditor } from 'app/features/dimensions/editors';
|
||||
|
||||
import { CanvasElementItem, CanvasElementProps, defaultBgColor } from '../element';
|
||||
import { CanvasElementItem, CanvasElementOptions, CanvasElementProps, defaultBgColor } from '../element';
|
||||
|
||||
interface DroneSideData {
|
||||
pitchAngle?: number;
|
||||
@ -96,9 +96,11 @@ export const droneSideItem: CanvasElementItem = {
|
||||
}),
|
||||
|
||||
// Called when data changes
|
||||
prepareData: (ctx: DimensionContext, cfg: DroneSideConfig) => {
|
||||
prepareData: (dimensionContext: DimensionContext, elementOptions: CanvasElementOptions<DroneSideConfig>) => {
|
||||
const droneSideConfig = elementOptions.config;
|
||||
|
||||
const data: DroneSideData = {
|
||||
pitchAngle: cfg?.pitchAngle ? ctx.getScalar(cfg.pitchAngle).value() : 0,
|
||||
pitchAngle: droneSideConfig?.pitchAngle ? dimensionContext.getScalar(droneSideConfig.pitchAngle).value() : 0,
|
||||
};
|
||||
|
||||
return data;
|
||||
|
@ -7,7 +7,7 @@ import { useStyles2 } from '@grafana/ui';
|
||||
import { DimensionContext } from 'app/features/dimensions';
|
||||
import { ScalarDimensionEditor } from 'app/features/dimensions/editors';
|
||||
|
||||
import { CanvasElementItem, CanvasElementProps, defaultBgColor } from '../element';
|
||||
import { CanvasElementItem, CanvasElementOptions, CanvasElementProps, defaultBgColor } from '../element';
|
||||
|
||||
interface DroneTopData {
|
||||
bRightRotorRPM?: number;
|
||||
@ -102,13 +102,23 @@ export const droneTopItem: CanvasElementItem = {
|
||||
}),
|
||||
|
||||
// Called when data changes
|
||||
prepareData: (ctx: DimensionContext, cfg: DroneTopConfig) => {
|
||||
prepareData: (dimensionContext: DimensionContext, elementOptions: CanvasElementOptions<DroneTopConfig>) => {
|
||||
const droneTopConfig = elementOptions.config;
|
||||
|
||||
const data: DroneTopData = {
|
||||
bRightRotorRPM: cfg?.bRightRotorRPM ? ctx.getScalar(cfg.bRightRotorRPM).value() : 0,
|
||||
bLeftRotorRPM: cfg?.bLeftRotorRPM ? ctx.getScalar(cfg.bLeftRotorRPM).value() : 0,
|
||||
fRightRotorRPM: cfg?.fRightRotorRPM ? ctx.getScalar(cfg.fRightRotorRPM).value() : 0,
|
||||
fLeftRotorRPM: cfg?.fLeftRotorRPM ? ctx.getScalar(cfg.fLeftRotorRPM).value() : 0,
|
||||
yawAngle: cfg?.yawAngle ? ctx.getScalar(cfg.yawAngle).value() : 0,
|
||||
bRightRotorRPM: droneTopConfig?.bRightRotorRPM
|
||||
? dimensionContext.getScalar(droneTopConfig.bRightRotorRPM).value()
|
||||
: 0,
|
||||
bLeftRotorRPM: droneTopConfig?.bLeftRotorRPM
|
||||
? 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;
|
||||
|
@ -8,7 +8,13 @@ import { ColorDimensionEditor } from 'app/features/dimensions/editors/ColorDimen
|
||||
import { TextDimensionEditor } from 'app/features/dimensions/editors/TextDimensionEditor';
|
||||
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';
|
||||
|
||||
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 = {
|
||||
width: cfg.width,
|
||||
text: cfg.text ? ctx.getText(cfg.text).value() : '',
|
||||
align: cfg.align ?? Align.Center,
|
||||
valign: cfg.valign ?? VAlign.Middle,
|
||||
size: cfg.size,
|
||||
width: ellipseConfig?.width,
|
||||
text: ellipseConfig?.text ? dimensionContext.getText(ellipseConfig.text).value() : '',
|
||||
align: ellipseConfig?.align ?? Align.Center,
|
||||
valign: ellipseConfig?.valign ?? VAlign.Middle,
|
||||
size: ellipseConfig?.size,
|
||||
};
|
||||
|
||||
if (cfg.backgroundColor) {
|
||||
data.backgroundColor = ctx.getColor(cfg.backgroundColor).value();
|
||||
if (ellipseConfig?.backgroundColor) {
|
||||
data.backgroundColor = dimensionContext.getColor(ellipseConfig.backgroundColor).value();
|
||||
}
|
||||
if (cfg.borderColor) {
|
||||
data.borderColor = ctx.getColor(cfg.borderColor).value();
|
||||
if (ellipseConfig?.borderColor) {
|
||||
data.borderColor = dimensionContext.getColor(ellipseConfig.borderColor).value();
|
||||
}
|
||||
if (cfg.color) {
|
||||
data.color = ctx.getColor(cfg.color).value();
|
||||
if (ellipseConfig?.color) {
|
||||
data.color = dimensionContext.getColor(ellipseConfig.color).value();
|
||||
}
|
||||
|
||||
data.links = getDataLinks(ctx, cfg, data.text);
|
||||
data.links = getDataLinks(dimensionContext, elementOptions, data.text);
|
||||
|
||||
return data;
|
||||
},
|
||||
|
@ -2,13 +2,15 @@ import { css } from '@emotion/css';
|
||||
import { isString } from 'lodash';
|
||||
import React, { CSSProperties } from 'react';
|
||||
|
||||
import { LinkModel } from '@grafana/data';
|
||||
import { ColorDimensionConfig, ResourceDimensionConfig, ResourceDimensionMode } from '@grafana/schema';
|
||||
import { SanitizedSVG } from 'app/core/components/SVG/SanitizedSVG';
|
||||
import { getPublicOrAbsoluteUrl } from 'app/features/dimensions';
|
||||
import { DimensionContext } from 'app/features/dimensions/context';
|
||||
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';
|
||||
|
||||
export interface IconConfig {
|
||||
@ -22,6 +24,7 @@ interface IconData {
|
||||
fill: string;
|
||||
strokeColor?: string;
|
||||
stroke?: number;
|
||||
links?: LinkModel[];
|
||||
}
|
||||
|
||||
// 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
|
||||
prepareData: (ctx: DimensionContext, cfg: IconConfig) => {
|
||||
prepareData: (dimensionContext: DimensionContext, elementOptions: CanvasElementOptions<IconConfig>) => {
|
||||
const iconConfig = elementOptions.config;
|
||||
|
||||
let path: string | undefined = undefined;
|
||||
if (cfg.path) {
|
||||
path = ctx.getResource(cfg.path).value();
|
||||
if (iconConfig?.path) {
|
||||
path = dimensionContext.getResource(iconConfig.path).value();
|
||||
}
|
||||
if (!path || !isString(path)) {
|
||||
path = getPublicOrAbsoluteUrl('img/icons/unicons/question-circle.svg');
|
||||
@ -91,15 +96,18 @@ export const iconItem: CanvasElementItem<IconConfig, IconData> = {
|
||||
|
||||
const data: IconData = {
|
||||
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 (cfg.stroke.width > 0) {
|
||||
data.stroke = cfg.stroke?.width;
|
||||
data.strokeColor = ctx.getColor(cfg.stroke.color).value();
|
||||
if (iconConfig?.stroke?.width && iconConfig?.stroke.color) {
|
||||
if (iconConfig.stroke.width > 0) {
|
||||
data.stroke = iconConfig.stroke?.width;
|
||||
data.strokeColor = dimensionContext.getColor(iconConfig.stroke.color).value();
|
||||
}
|
||||
}
|
||||
|
||||
data.links = getDataLinks(dimensionContext, elementOptions, data.path);
|
||||
|
||||
return data;
|
||||
},
|
||||
|
||||
|
@ -13,7 +13,13 @@ import { ColorDimensionEditor } from 'app/features/dimensions/editors/ColorDimen
|
||||
import { TextDimensionEditor } from 'app/features/dimensions/editors/TextDimensionEditor';
|
||||
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 { 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 = {
|
||||
text: cfg.text ? ctx.getText(cfg.text).value() : '',
|
||||
align: cfg.align ?? Align.Center,
|
||||
valign: cfg.valign ?? VAlign.Middle,
|
||||
size: cfg.size,
|
||||
text: textConfig?.text ? dimensionContext.getText(textConfig.text).value() : '',
|
||||
align: textConfig?.align ?? Align.Center,
|
||||
valign: textConfig?.valign ?? VAlign.Middle,
|
||||
size: textConfig?.size,
|
||||
};
|
||||
|
||||
if (cfg.color) {
|
||||
data.color = ctx.getColor(cfg.color).value();
|
||||
if (textConfig?.color) {
|
||||
data.color = dimensionContext.getColor(textConfig.color).value();
|
||||
}
|
||||
|
||||
data.links = getDataLinks(ctx, cfg, data.text);
|
||||
data.links = getDataLinks(dimensionContext, elementOptions, data.text);
|
||||
|
||||
return data;
|
||||
},
|
||||
|
@ -9,7 +9,13 @@ import { ColorDimensionEditor } from 'app/features/dimensions/editors/ColorDimen
|
||||
import { TextDimensionEditor } from 'app/features/dimensions/editors/TextDimensionEditor';
|
||||
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';
|
||||
|
||||
class RectangleDisplay extends PureComponent<CanvasElementProps<TextConfig, TextData>> {
|
||||
@ -70,19 +76,21 @@ export const rectangleItem: CanvasElementItem<TextConfig, TextData> = {
|
||||
}),
|
||||
|
||||
// Called when data changes
|
||||
prepareData: (ctx: DimensionContext, cfg: TextConfig) => {
|
||||
prepareData: (dimensionContext: DimensionContext, elementOptions: CanvasElementOptions<TextConfig>) => {
|
||||
const textConfig = elementOptions.config;
|
||||
|
||||
const data: TextData = {
|
||||
text: cfg.text ? ctx.getText(cfg.text).value() : '',
|
||||
align: cfg.align ?? Align.Center,
|
||||
valign: cfg.valign ?? VAlign.Middle,
|
||||
size: cfg.size,
|
||||
text: textConfig?.text ? dimensionContext.getText(textConfig.text).value() : '',
|
||||
align: textConfig?.align ?? Align.Center,
|
||||
valign: textConfig?.valign ?? VAlign.Middle,
|
||||
size: textConfig?.size,
|
||||
};
|
||||
|
||||
if (cfg.color) {
|
||||
data.color = ctx.getColor(cfg.color).value();
|
||||
if (textConfig?.color) {
|
||||
data.color = dimensionContext.getColor(textConfig.color).value();
|
||||
}
|
||||
|
||||
data.links = getDataLinks(ctx, cfg, data.text);
|
||||
data.links = getDataLinks(dimensionContext, elementOptions, data.text);
|
||||
|
||||
return data;
|
||||
},
|
||||
|
@ -1,13 +1,14 @@
|
||||
import { css } from '@emotion/css';
|
||||
import React from 'react';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { GrafanaTheme2, LinkModel } from '@grafana/data';
|
||||
import { ColorDimensionConfig, ScalarDimensionConfig } from '@grafana/schema';
|
||||
import config from 'app/core/config';
|
||||
import { DimensionContext } from 'app/features/dimensions';
|
||||
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 { ServerSingle } from './types/single';
|
||||
@ -26,6 +27,7 @@ export interface ServerData {
|
||||
statusColor?: string;
|
||||
bulbColor?: string;
|
||||
type: ServerType;
|
||||
links?: LinkModel[];
|
||||
}
|
||||
|
||||
enum ServerType {
|
||||
@ -85,14 +87,20 @@ export const serverItem: CanvasElementItem<ServerConfig, ServerData> = {
|
||||
}),
|
||||
|
||||
// Called when data changes
|
||||
prepareData: (ctx: DimensionContext, cfg: ServerConfig) => {
|
||||
prepareData: (dimensionContext: DimensionContext, elementOptions: CanvasElementOptions<ServerConfig>) => {
|
||||
const serverConfig = elementOptions.config;
|
||||
|
||||
const data: ServerData = {
|
||||
blinkRate: cfg?.blinkRate ? ctx.getScalar(cfg.blinkRate).value() : 0,
|
||||
statusColor: cfg?.statusColor ? ctx.getColor(cfg.statusColor).value() : 'transparent',
|
||||
bulbColor: cfg?.bulbColor ? ctx.getColor(cfg.bulbColor).value() : 'green',
|
||||
type: cfg.type,
|
||||
blinkRate: serverConfig?.blinkRate ? dimensionContext.getScalar(serverConfig.blinkRate).value() : 0,
|
||||
statusColor: serverConfig?.statusColor
|
||||
? dimensionContext.getColor(serverConfig.statusColor).value()
|
||||
: 'transparent',
|
||||
bulbColor: serverConfig?.bulbColor ? dimensionContext.getColor(serverConfig.bulbColor).value() : 'green',
|
||||
type: serverConfig?.type ?? ServerType.Single,
|
||||
};
|
||||
|
||||
data.links = getDataLinks(dimensionContext, elementOptions, data.statusColor);
|
||||
|
||||
return data;
|
||||
},
|
||||
|
||||
|
@ -10,7 +10,7 @@ import { ColorDimensionEditor } from 'app/features/dimensions/editors/ColorDimen
|
||||
import { TextDimensionEditor } from 'app/features/dimensions/editors/TextDimensionEditor';
|
||||
|
||||
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 { 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 = {
|
||||
text: cfg.text ? ctx.getText(cfg.text).value() : '',
|
||||
align: cfg.align ?? Align.Center,
|
||||
valign: cfg.valign ?? VAlign.Middle,
|
||||
size: cfg.size,
|
||||
text: textConfig?.text ? dimensionContext.getText(textConfig.text).value() : '',
|
||||
align: textConfig?.align ?? Align.Center,
|
||||
valign: textConfig?.valign ?? VAlign.Middle,
|
||||
size: textConfig?.size,
|
||||
};
|
||||
|
||||
if (cfg.color) {
|
||||
data.color = ctx.getColor(cfg.color).value();
|
||||
if (textConfig?.color) {
|
||||
data.color = dimensionContext.getColor(textConfig.color).value();
|
||||
}
|
||||
|
||||
data.links = getDataLinks(ctx, cfg, data.text);
|
||||
data.links = getDataLinks(dimensionContext, elementOptions, data.text);
|
||||
|
||||
return data;
|
||||
},
|
||||
|
@ -1,16 +1,18 @@
|
||||
import { css } from '@emotion/css';
|
||||
import React from 'react';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { GrafanaTheme2, LinkModel } from '@grafana/data';
|
||||
import { ScalarDimensionConfig } from '@grafana/schema';
|
||||
import { useStyles2 } from '@grafana/ui';
|
||||
import { DimensionContext } from 'app/features/dimensions';
|
||||
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 {
|
||||
rpm?: number;
|
||||
links?: LinkModel[];
|
||||
}
|
||||
|
||||
interface WindTurbineConfig {
|
||||
@ -92,11 +94,15 @@ export const windTurbineItem: CanvasElementItem = {
|
||||
}),
|
||||
|
||||
// Called when data changes
|
||||
prepareData: (ctx: DimensionContext, cfg: WindTurbineConfig) => {
|
||||
prepareData: (dimensionContext: DimensionContext, elementOptions: CanvasElementOptions<WindTurbineConfig>) => {
|
||||
const windTurbineConfig = elementOptions.config;
|
||||
|
||||
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;
|
||||
},
|
||||
|
||||
|
@ -296,7 +296,7 @@ export class ElementState implements LayerElement {
|
||||
|
||||
updateData(ctx: DimensionContext) {
|
||||
if (this.item.prepareData) {
|
||||
this.data = this.item.prepareData(ctx, this.options.config);
|
||||
this.data = this.item.prepareData(ctx, this.options);
|
||||
this.revId++; // rerender
|
||||
}
|
||||
|
||||
|
@ -9,7 +9,6 @@ import {
|
||||
CanvasElementItem,
|
||||
canvasElementRegistry,
|
||||
CanvasElementOptions,
|
||||
TextConfig,
|
||||
CanvasConnection,
|
||||
} from 'app/features/canvas';
|
||||
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 links: Array<LinkModel<Field>> = [];
|
||||
const linkLookup = new Set<string>();
|
||||
|
||||
const elementConfig = elementOptions.config;
|
||||
|
||||
frames?.forEach((frame) => {
|
||||
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)) {
|
||||
const field = visibleFields.filter((field) => getFieldDisplayName(field, frame) === cfg.text?.field)[0];
|
||||
if (field?.getLinks) {
|
||||
const disp = field.display ? field.display(textData) : { text: `${textData}`, numeric: +textData! };
|
||||
field.getLinks({ calculatedValue: disp }).forEach((link) => {
|
||||
const key = `${link.title}/${link.href}`;
|
||||
if (!linkLookup.has(key)) {
|
||||
links.push(link);
|
||||
linkLookup.add(key);
|
||||
}
|
||||
});
|
||||
}
|
||||
// Text config
|
||||
const isTextTiedToFieldData =
|
||||
elementConfig.text?.field &&
|
||||
visibleFields.some((field) => getFieldDisplayName(field, frame) === elementConfig.text?.field);
|
||||
const isTextColorTiedToFieldData =
|
||||
elementConfig.color?.field &&
|
||||
visibleFields.some((field) => getFieldDisplayName(field, frame) === elementConfig.color?.field);
|
||||
|
||||
// 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