Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Canvas: Add universal data link support #84142

Merged
merged 8 commits into from Mar 13, 2024
2 changes: 1 addition & 1 deletion public/app/features/canvas/element.ts
Expand Up @@ -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;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the primary change / refactor that is allowing the more universal data link support. TLDR; before we were just passing the element's config (specific options for each element) whereas now we are passing down the entire element's options which includes the specific config as well as the the generic container background / border info that can be tied to field data and thus be applicable to data link functionality


/** Component used to draw */
display: ComponentType<CanvasElementProps<TConfig, TData>>;
Expand Down
34 changes: 18 additions & 16 deletions public/app/features/canvas/elements/button.tsx
Expand Up @@ -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'> {
Expand Down Expand Up @@ -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;
Expand Down
8 changes: 5 additions & 3 deletions public/app/features/canvas/elements/droneFront.tsx
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
8 changes: 5 additions & 3 deletions public/app/features/canvas/elements/droneSide.tsx
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
24 changes: 17 additions & 7 deletions public/app/features/canvas/elements/droneTop.tsx
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
36 changes: 22 additions & 14 deletions public/app/features/canvas/elements/ellipse.tsx
Expand Up @@ -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>> {
Expand Down Expand Up @@ -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;
},
Expand Down
26 changes: 17 additions & 9 deletions public/app/features/canvas/elements/icon.tsx
Expand Up @@ -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 {
Expand All @@ -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
Expand Down Expand Up @@ -80,26 +83,31 @@ 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');
}

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;
},

Expand Down
26 changes: 17 additions & 9 deletions public/app/features/canvas/elements/metricValue.tsx
Expand Up @@ -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';

Expand Down Expand Up @@ -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;
},
Expand Down
26 changes: 17 additions & 9 deletions public/app/features/canvas/elements/rectangle.tsx
Expand Up @@ -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>> {
Expand Down Expand Up @@ -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;
},
Expand Down