Canvas: Add button element + basic onClick support (#43252)

This commit is contained in:
Nathan Marrs 2021-12-17 14:05:57 -08:00 committed by GitHub
parent 197f4f81f2
commit 8be4182e49
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 158 additions and 8 deletions

View File

@ -5,19 +5,26 @@ import { DimensionContext } from 'app/features/dimensions/context';
import { TextDimensionEditor } from 'app/features/dimensions/editors/TextDimensionEditor';
import { TextDimensionConfig } from 'app/features/dimensions/types';
import { CanvasElementItem, CanvasElementProps } from '../element';
import { APIEditor, APIEditorConfig, callApi } from 'app/plugins/panel/canvas/editor/APIEditor';
interface ButtonData {
text?: string;
api?: APIEditorConfig;
}
interface ButtonConfig {
text?: TextDimensionConfig;
api?: APIEditorConfig;
}
class ButtonDisplay extends PureComponent<CanvasElementProps<ButtonConfig, ButtonData>> {
render() {
const { data } = this.props;
const onClick = () => console.log('button being clicked :)');
const onClick = () => {
if (data?.api) {
callApi(data.api);
}
};
return <Button onClick={onClick}>{data?.text}</Button>;
}
@ -43,6 +50,7 @@ export const buttonItem: CanvasElementItem<ButtonConfig, ButtonData> = {
prepareData: (ctx: DimensionContext, cfg: ButtonConfig) => {
const data: ButtonData = {
text: cfg?.text ? ctx.getText(cfg.text).value() : '',
api: cfg?.api ?? undefined,
};
return data;
@ -51,12 +59,20 @@ export const buttonItem: CanvasElementItem<ButtonConfig, ButtonData> = {
// Heatmap overlay options
registerOptionsUI: (builder) => {
const category = ['Button'];
builder.addCustomEditor({
category,
id: 'textSelector',
path: 'config.text',
name: 'Text',
editor: TextDimensionEditor,
});
builder
.addCustomEditor({
category,
id: 'textSelector',
path: 'config.text',
name: 'Text',
editor: TextDimensionEditor,
})
.addCustomEditor({
category,
id: 'apiSelector',
path: 'config.api',
name: 'API',
editor: APIEditor,
});
},
};

View File

@ -13,11 +13,13 @@ import { css } from '@emotion/css';
import { isString } from 'lodash';
import { LineConfig } from '../types';
import { DimensionContext } from 'app/features/dimensions/context';
import { APIEditor, APIEditorConfig, callApi } from 'app/plugins/panel/canvas/editor/APIEditor';
export interface IconConfig {
path?: ResourceDimensionConfig;
fill?: ColorDimensionConfig;
stroke?: LineConfig;
api?: APIEditorConfig;
}
interface IconData {
@ -25,6 +27,7 @@ interface IconData {
fill: string;
strokeColor?: string;
stroke?: number;
api?: APIEditorConfig;
}
// When a stoke is defined, we want the path to be in page units
@ -40,6 +43,12 @@ export function IconDisplay(props: CanvasElementProps) {
return null;
}
const onClick = () => {
if (data?.api) {
callApi(data.api);
}
};
const svgStyle: CSSProperties = {
fill: data?.fill,
stroke: data?.strokeColor,
@ -48,6 +57,7 @@ export function IconDisplay(props: CanvasElementProps) {
return (
<SVG
onClick={onClick}
src={data.path}
width={width}
height={height}
@ -92,6 +102,7 @@ export const iconItem: CanvasElementItem<IconConfig, IconData> = {
const data: IconData = {
path,
fill: cfg.fill ? ctx.getColor(cfg.fill).value() : '#CCC',
api: cfg?.api ?? undefined,
};
if (cfg.stroke?.width && cfg.stroke.color) {
@ -151,6 +162,13 @@ export const iconItem: CanvasElementItem<IconConfig, IconData> = {
fixed: 'grey',
},
showIf: (cfg) => Boolean(cfg?.config?.stroke?.width),
})
.addCustomEditor({
category,
id: 'apiSelector',
path: 'config.api',
name: 'API',
editor: APIEditor,
});
},
};

View File

@ -1,5 +1,6 @@
import { Registry } from '@grafana/data';
import { CanvasElementItem, CanvasElementOptions } from './element';
import { buttonItem } from './elements/button';
import { droneFrontItem } from './elements/droneFront';
import { droneSideItem } from './elements/droneSide';
import { droneTopItem } from './elements/droneTop';
@ -15,6 +16,7 @@ export const DEFAULT_CANVAS_ELEMENT_CONFIG: CanvasElementOptions = {
export const canvasElementRegistry = new Registry<CanvasElementItem>(() => [
iconItem, // default for now
textBoxItem,
buttonItem,
droneTopItem,
droneFrontItem,
droneSideItem,

View File

@ -0,0 +1,114 @@
import React, { FC, useCallback } from 'react';
import { Button, InlineField, InlineFieldRow, JSONFormatter, StringValueEditor } from '@grafana/ui';
import { AppEvents, StandardEditorProps, StandardEditorsRegistryItem, StringFieldConfigSettings } from '@grafana/data';
import { config, getBackendSrv } from '@grafana/runtime';
import { appEvents } from 'app/core/core';
export interface APIEditorConfig {
endpoint: string;
data?: string;
}
const dummyStringSettings: StandardEditorsRegistryItem<string, StringFieldConfigSettings> = {
settings: {},
} as any;
export const callApi = (api: APIEditorConfig, isTest = false) => {
if (api) {
getBackendSrv()
.fetch({
url: api.endpoint!,
method: 'POST',
data: api.data ?? {},
})
.subscribe({
error: (error: any) => {
if (isTest) {
appEvents.emit(AppEvents.alertError, ['Error has occurred: ', JSON.stringify(error)]);
console.error(error);
}
},
complete: () => {
if (isTest) {
appEvents.emit(AppEvents.alertSuccess, ['Test successful']);
}
},
});
}
};
export const APIEditor: FC<StandardEditorProps<APIEditorConfig, any, any>> = (props) => {
const { value, context, onChange } = props;
const labelWidth = 9;
const onEndpointChange = useCallback(
(endpoint) => {
onChange({
...value,
endpoint,
});
},
[onChange, value]
);
const onDataChange = useCallback(
(data) => {
onChange({
...value,
data,
});
},
[onChange, value]
);
const renderJSON = (data: string) => {
try {
const json = JSON.parse(data);
return <JSONFormatter json={json} />;
} catch (error) {
return `Invalid JSON provided: ${error.message}`;
}
};
const renderTestAPIButton = (api: APIEditorConfig) => {
if (api && api.endpoint) {
return (
<Button onClick={() => callApi(api, true)} title={'Test API'}>
Test API
</Button>
);
}
return;
};
return config.disableSanitizeHtml ? (
<>
<InlineFieldRow>
<InlineField label={'Endpoint'} labelWidth={labelWidth} grow={true}>
<StringValueEditor
context={context}
value={value?.endpoint}
onChange={onEndpointChange}
item={dummyStringSettings}
/>
</InlineField>
</InlineFieldRow>
<InlineFieldRow>
<InlineField label={'Data'} labelWidth={labelWidth} grow={true}>
<StringValueEditor
context={context}
value={value?.data ?? '{}'}
onChange={onDataChange}
item={dummyStringSettings}
/>
</InlineField>
</InlineFieldRow>
{renderTestAPIButton(value)}
<br />
{renderJSON(value?.data ?? '{}')}
</>
) : (
<>Must enable disableSanitizeHtml feature flag to access</>
);
};