diff --git a/public/app/features/canvas/elements/button.tsx b/public/app/features/canvas/elements/button.tsx index 170bb9ed544..5771c0621f0 100644 --- a/public/app/features/canvas/elements/button.tsx +++ b/public/app/features/canvas/elements/button.tsx @@ -5,8 +5,9 @@ import { TextDimensionConfig, TextDimensionMode } from '@grafana/schema'; import { Button } from '@grafana/ui'; import { DimensionContext } from 'app/features/dimensions/context'; 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 { callApi } from 'app/plugins/panel/canvas/editor/element/utils'; import { HttpRequestMethod } from 'app/plugins/panel/canvas/panelcfg.gen'; import { CanvasElementItem, CanvasElementProps } from '../element'; @@ -40,7 +41,7 @@ class ButtonDisplay extends PureComponent { - if (data?.api) { + if (data?.api && data?.api?.endpoint) { callApi(data.api); } }; diff --git a/public/app/features/canvas/elements/icon.tsx b/public/app/features/canvas/elements/icon.tsx index 3c5a860b0a4..151e87e35a1 100644 --- a/public/app/features/canvas/elements/icon.tsx +++ b/public/app/features/canvas/elements/icon.tsx @@ -7,7 +7,6 @@ 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 { APIEditorConfig, callApi } from 'app/plugins/panel/canvas/editor/element/APIEditor'; import { CanvasElementItem, CanvasElementProps, defaultBgColor } from '../element'; import { LineConfig } from '../types'; @@ -16,7 +15,6 @@ export interface IconConfig { path?: ResourceDimensionConfig; fill?: ColorDimensionConfig; stroke?: LineConfig; - api?: APIEditorConfig; } interface IconData { @@ -24,7 +22,6 @@ 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,12 +37,6 @@ export function IconDisplay(props: CanvasElementProps) { return null; } - const onClick = () => { - if (data?.api) { - callApi(data.api); - } - }; - const svgStyle: CSSProperties = { fill: data?.fill, stroke: data?.strokeColor, @@ -55,13 +46,7 @@ export function IconDisplay(props: CanvasElementProps) { }; return ( - + ); } @@ -107,7 +92,6 @@ export const iconItem: CanvasElementItem = { const data: IconData = { path, fill: cfg.fill ? ctx.getColor(cfg.fill).value() : defaultBgColor, - api: cfg?.api ?? undefined, }; if (cfg.stroke?.width && cfg.stroke.color) { @@ -168,12 +152,5 @@ export const iconItem: CanvasElementItem = { // }, // showIf: (cfg) => Boolean(cfg?.config?.stroke?.width), // }) - // .addCustomEditor({ - // category, - // id: 'apiSelector', - // path: 'config.api', - // name: 'API', - // editor: APIEditor, - // }); }, }; diff --git a/public/app/plugins/panel/canvas/editor/element/APIEditor.tsx b/public/app/plugins/panel/canvas/editor/element/APIEditor.tsx index fc31ce442d2..9a4e9445a2e 100644 --- a/public/app/plugins/panel/canvas/editor/element/APIEditor.tsx +++ b/public/app/plugins/panel/canvas/editor/element/APIEditor.tsx @@ -1,21 +1,20 @@ import React, { useCallback } from 'react'; import { - AppEvents, - SelectableValue, StandardEditorProps, StandardEditorsRegistryItem, StringFieldConfigSettings, + SelectableValue, } 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 { StringValueEditor } from 'app/core/components/OptionsUI/string'; -import { appEvents } from 'app/core/core'; import { defaultApiConfig } from 'app/features/canvas/elements/button'; import { HttpRequestMethod } from '../../panelcfg.gen'; import { ParamsEditor } from './ParamsEditor'; +import { callApi, interpolateVariables } from './utils'; export interface APIEditorConfig { method: string; @@ -30,71 +29,6 @@ const dummyStringSettings = { settings: {}, } as StandardEditorsRegistryItem; -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; const httpMethodOptions = [ @@ -184,7 +118,7 @@ export function APIEditor({ value, context, onChange }: Props) { const renderJSON = (data: string) => { try { - const json = JSON.parse(data); + const json = JSON.parse(interpolateVariables(data)); return ; } catch (error) { if (error instanceof Error) { @@ -198,7 +132,7 @@ export function APIEditor({ value, context, onChange }: Props) { const renderTestAPIButton = (api: APIEditorConfig) => { if (api && api.endpoint) { return ( - ); diff --git a/public/app/plugins/panel/canvas/editor/element/utils.ts b/public/app/plugins/panel/canvas/editor/element/utils.ts new file mode 100644 index 00000000000..b26544b8610 --- /dev/null +++ b/public/app/plugins/panel/canvas/editor/element/utils.ts @@ -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; +};