mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Canvas: Button API Editor support template variables (#74779)
This commit is contained in:
parent
426e161c9e
commit
5caf4e1485
@ -5,8 +5,9 @@ import { TextDimensionConfig, TextDimensionMode } from '@grafana/schema';
|
|||||||
import { Button } from '@grafana/ui';
|
import { Button } from '@grafana/ui';
|
||||||
import { DimensionContext } from 'app/features/dimensions/context';
|
import { DimensionContext } from 'app/features/dimensions/context';
|
||||||
import { TextDimensionEditor } from 'app/features/dimensions/editors/TextDimensionEditor';
|
import { TextDimensionEditor } from 'app/features/dimensions/editors/TextDimensionEditor';
|
||||||
import { APIEditor, APIEditorConfig, callApi } from 'app/plugins/panel/canvas/editor/element/APIEditor';
|
import { APIEditor, APIEditorConfig } from 'app/plugins/panel/canvas/editor/element/APIEditor';
|
||||||
import { ButtonStyleConfig, ButtonStyleEditor } from 'app/plugins/panel/canvas/editor/element/ButtonStyleEditor';
|
import { ButtonStyleConfig, ButtonStyleEditor } from 'app/plugins/panel/canvas/editor/element/ButtonStyleEditor';
|
||||||
|
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 } from '../element';
|
import { CanvasElementItem, CanvasElementProps } from '../element';
|
||||||
@ -40,7 +41,7 @@ class ButtonDisplay extends PureComponent<CanvasElementProps<ButtonConfig, Butto
|
|||||||
render() {
|
render() {
|
||||||
const { data } = this.props;
|
const { data } = this.props;
|
||||||
const onClick = () => {
|
const onClick = () => {
|
||||||
if (data?.api) {
|
if (data?.api && data?.api?.endpoint) {
|
||||||
callApi(data.api);
|
callApi(data.api);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -7,7 +7,6 @@ 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 { APIEditorConfig, callApi } from 'app/plugins/panel/canvas/editor/element/APIEditor';
|
|
||||||
|
|
||||||
import { CanvasElementItem, CanvasElementProps, defaultBgColor } from '../element';
|
import { CanvasElementItem, CanvasElementProps, defaultBgColor } from '../element';
|
||||||
import { LineConfig } from '../types';
|
import { LineConfig } from '../types';
|
||||||
@ -16,7 +15,6 @@ export interface IconConfig {
|
|||||||
path?: ResourceDimensionConfig;
|
path?: ResourceDimensionConfig;
|
||||||
fill?: ColorDimensionConfig;
|
fill?: ColorDimensionConfig;
|
||||||
stroke?: LineConfig;
|
stroke?: LineConfig;
|
||||||
api?: APIEditorConfig;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IconData {
|
interface IconData {
|
||||||
@ -24,7 +22,6 @@ interface IconData {
|
|||||||
fill: string;
|
fill: string;
|
||||||
strokeColor?: string;
|
strokeColor?: string;
|
||||||
stroke?: number;
|
stroke?: number;
|
||||||
api?: APIEditorConfig;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
||||||
@ -40,12 +37,6 @@ export function IconDisplay(props: CanvasElementProps) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const onClick = () => {
|
|
||||||
if (data?.api) {
|
|
||||||
callApi(data.api);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const svgStyle: CSSProperties = {
|
const svgStyle: CSSProperties = {
|
||||||
fill: data?.fill,
|
fill: data?.fill,
|
||||||
stroke: data?.strokeColor,
|
stroke: data?.strokeColor,
|
||||||
@ -55,13 +46,7 @@ export function IconDisplay(props: CanvasElementProps) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SanitizedSVG
|
<SanitizedSVG src={data.path} style={svgStyle} className={svgStyle.strokeWidth ? svgStrokePathClass : undefined} />
|
||||||
onClick={onClick}
|
|
||||||
src={data.path}
|
|
||||||
style={svgStyle}
|
|
||||||
className={svgStyle.strokeWidth ? svgStrokePathClass : undefined}
|
|
||||||
cleanStyle={true}
|
|
||||||
/>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -107,7 +92,6 @@ export const iconItem: CanvasElementItem<IconConfig, IconData> = {
|
|||||||
const data: IconData = {
|
const data: IconData = {
|
||||||
path,
|
path,
|
||||||
fill: cfg.fill ? ctx.getColor(cfg.fill).value() : defaultBgColor,
|
fill: cfg.fill ? ctx.getColor(cfg.fill).value() : defaultBgColor,
|
||||||
api: cfg?.api ?? undefined,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (cfg.stroke?.width && cfg.stroke.color) {
|
if (cfg.stroke?.width && cfg.stroke.color) {
|
||||||
@ -168,12 +152,5 @@ export const iconItem: CanvasElementItem<IconConfig, IconData> = {
|
|||||||
// },
|
// },
|
||||||
// showIf: (cfg) => Boolean(cfg?.config?.stroke?.width),
|
// showIf: (cfg) => Boolean(cfg?.config?.stroke?.width),
|
||||||
// })
|
// })
|
||||||
// .addCustomEditor({
|
|
||||||
// category,
|
|
||||||
// id: 'apiSelector',
|
|
||||||
// path: 'config.api',
|
|
||||||
// name: 'API',
|
|
||||||
// editor: APIEditor,
|
|
||||||
// });
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -1,21 +1,20 @@
|
|||||||
import React, { useCallback } from 'react';
|
import React, { useCallback } from 'react';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
AppEvents,
|
|
||||||
SelectableValue,
|
|
||||||
StandardEditorProps,
|
StandardEditorProps,
|
||||||
StandardEditorsRegistryItem,
|
StandardEditorsRegistryItem,
|
||||||
StringFieldConfigSettings,
|
StringFieldConfigSettings,
|
||||||
|
SelectableValue,
|
||||||
} from '@grafana/data';
|
} from '@grafana/data';
|
||||||
import { BackendSrvRequest, config, getBackendSrv } from '@grafana/runtime';
|
import { config } from '@grafana/runtime';
|
||||||
import { Button, Field, InlineField, InlineFieldRow, JSONFormatter, RadioButtonGroup, Select } from '@grafana/ui';
|
import { Button, Field, InlineField, InlineFieldRow, JSONFormatter, RadioButtonGroup, Select } from '@grafana/ui';
|
||||||
import { StringValueEditor } from 'app/core/components/OptionsUI/string';
|
import { StringValueEditor } from 'app/core/components/OptionsUI/string';
|
||||||
import { appEvents } from 'app/core/core';
|
|
||||||
import { defaultApiConfig } from 'app/features/canvas/elements/button';
|
import { defaultApiConfig } from 'app/features/canvas/elements/button';
|
||||||
|
|
||||||
import { HttpRequestMethod } from '../../panelcfg.gen';
|
import { HttpRequestMethod } from '../../panelcfg.gen';
|
||||||
|
|
||||||
import { ParamsEditor } from './ParamsEditor';
|
import { ParamsEditor } from './ParamsEditor';
|
||||||
|
import { callApi, interpolateVariables } from './utils';
|
||||||
|
|
||||||
export interface APIEditorConfig {
|
export interface APIEditorConfig {
|
||||||
method: string;
|
method: string;
|
||||||
@ -30,71 +29,6 @@ const dummyStringSettings = {
|
|||||||
settings: {},
|
settings: {},
|
||||||
} as StandardEditorsRegistryItem<string, StringFieldConfigSettings>;
|
} as StandardEditorsRegistryItem<string, StringFieldConfigSettings>;
|
||||||
|
|
||||||
const getRequest = (api: APIEditorConfig) => {
|
|
||||||
const requestHeaders: HeadersInit = [];
|
|
||||||
|
|
||||||
let request: BackendSrvRequest = {
|
|
||||||
url: api.endpoint,
|
|
||||||
method: api.method,
|
|
||||||
data: getData(api),
|
|
||||||
headers: requestHeaders,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (api.headerParams) {
|
|
||||||
api.headerParams.forEach((param) => {
|
|
||||||
requestHeaders.push([param[0], param[1]]);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (api.queryParams) {
|
|
||||||
const symbol = api.endpoint.match(questionMarkRegex) ? '&' : '?';
|
|
||||||
request.url = api.endpoint + symbol + api.queryParams?.map((param) => param[0] + '=' + param[1]).join('&');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (api.method === HttpRequestMethod.POST) {
|
|
||||||
requestHeaders.push(['Content-Type', 'application/json']);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (api.method === HttpRequestMethod.POST) {
|
|
||||||
requestHeaders.push(['Content-Type', api.contentType!]);
|
|
||||||
}
|
|
||||||
|
|
||||||
request.headers = requestHeaders;
|
|
||||||
|
|
||||||
return request;
|
|
||||||
};
|
|
||||||
|
|
||||||
const questionMarkRegex = '.+\\?.*';
|
|
||||||
|
|
||||||
export const callApi = (api: APIEditorConfig, isTest = false) => {
|
|
||||||
if (api && api.endpoint) {
|
|
||||||
getBackendSrv()
|
|
||||||
.fetch(getRequest(api))
|
|
||||||
.subscribe({
|
|
||||||
error: (error) => {
|
|
||||||
if (isTest) {
|
|
||||||
appEvents.emit(AppEvents.alertError, ['Error has occurred: ', JSON.stringify(error)]);
|
|
||||||
console.error(error);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
complete: () => {
|
|
||||||
if (isTest) {
|
|
||||||
appEvents.emit(AppEvents.alertSuccess, ['Test successful']);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const getData = (api: APIEditorConfig) => {
|
|
||||||
let data: string | undefined = api.data ?? '{}';
|
|
||||||
if (api.method === HttpRequestMethod.GET) {
|
|
||||||
data = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return data;
|
|
||||||
};
|
|
||||||
|
|
||||||
type Props = StandardEditorProps<APIEditorConfig>;
|
type Props = StandardEditorProps<APIEditorConfig>;
|
||||||
|
|
||||||
const httpMethodOptions = [
|
const httpMethodOptions = [
|
||||||
@ -184,7 +118,7 @@ export function APIEditor({ value, context, onChange }: Props) {
|
|||||||
|
|
||||||
const renderJSON = (data: string) => {
|
const renderJSON = (data: string) => {
|
||||||
try {
|
try {
|
||||||
const json = JSON.parse(data);
|
const json = JSON.parse(interpolateVariables(data));
|
||||||
return <JSONFormatter json={json} />;
|
return <JSONFormatter json={json} />;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof Error) {
|
if (error instanceof Error) {
|
||||||
@ -198,7 +132,7 @@ export function APIEditor({ value, context, onChange }: Props) {
|
|||||||
const renderTestAPIButton = (api: APIEditorConfig) => {
|
const renderTestAPIButton = (api: APIEditorConfig) => {
|
||||||
if (api && api.endpoint) {
|
if (api && api.endpoint) {
|
||||||
return (
|
return (
|
||||||
<Button onClick={() => callApi(api, true)} title={'Test API'}>
|
<Button onClick={() => callApi(api, true)} title="Test API">
|
||||||
Test API
|
Test API
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
|
79
public/app/plugins/panel/canvas/editor/element/utils.ts
Normal file
79
public/app/plugins/panel/canvas/editor/element/utils.ts
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
import { AppEvents } from '@grafana/data';
|
||||||
|
import { BackendSrvRequest, getBackendSrv, getTemplateSrv } from '@grafana/runtime';
|
||||||
|
import { appEvents } from 'app/core/core';
|
||||||
|
import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv';
|
||||||
|
|
||||||
|
import { HttpRequestMethod } from '../../panelcfg.gen';
|
||||||
|
|
||||||
|
import { APIEditorConfig } from './APIEditor';
|
||||||
|
|
||||||
|
export const callApi = (api: APIEditorConfig, isTest = false) => {
|
||||||
|
if (api && api.endpoint) {
|
||||||
|
const request = getRequest(api);
|
||||||
|
|
||||||
|
getBackendSrv()
|
||||||
|
.fetch(request)
|
||||||
|
.subscribe({
|
||||||
|
error: (error) => {
|
||||||
|
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 interpolateVariables = (text: string) => {
|
||||||
|
const panel = getDashboardSrv().getCurrent()?.panelInEdit;
|
||||||
|
return getTemplateSrv().replace(text, panel?.scopedVars);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getRequest = (api: APIEditorConfig) => {
|
||||||
|
const requestHeaders: HeadersInit = [];
|
||||||
|
|
||||||
|
const url = new URL(interpolateVariables(api.endpoint!));
|
||||||
|
|
||||||
|
let request: BackendSrvRequest = {
|
||||||
|
url: url.toString(),
|
||||||
|
method: api.method,
|
||||||
|
data: getData(api),
|
||||||
|
headers: requestHeaders,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (api.headerParams) {
|
||||||
|
api.headerParams.forEach((param) => {
|
||||||
|
requestHeaders.push([interpolateVariables(param[0]), interpolateVariables(param[1])]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (api.queryParams) {
|
||||||
|
api.queryParams?.forEach((param) => {
|
||||||
|
url.searchParams.append(interpolateVariables(param[0]), interpolateVariables(param[1]));
|
||||||
|
});
|
||||||
|
|
||||||
|
request.url = url.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (api.method === HttpRequestMethod.POST) {
|
||||||
|
requestHeaders.push(['Content-Type', api.contentType!]);
|
||||||
|
}
|
||||||
|
|
||||||
|
request.headers = requestHeaders;
|
||||||
|
|
||||||
|
return request;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getData = (api: APIEditorConfig) => {
|
||||||
|
let data: string | undefined = api.data ? interpolateVariables(api.data) : '{}';
|
||||||
|
if (api.method === HttpRequestMethod.GET) {
|
||||||
|
data = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user