mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Canvas: Button API Editor visual feedback on response (#76499)
This commit is contained in:
parent
18128c2666
commit
d5945bc26e
@ -92,4 +92,4 @@ export interface CanvasElementItem<TConfig = any, TData = any> extends RegistryI
|
|||||||
export const defaultBgColor = '#D9D9D9';
|
export const defaultBgColor = '#D9D9D9';
|
||||||
export const defaultTextColor = '#000000';
|
export const defaultTextColor = '#000000';
|
||||||
export const defaultLightTextColor = '#F0F4FD';
|
export const defaultLightTextColor = '#F0F4FD';
|
||||||
export const defaultThemeTextColor = config.theme2.colors.background.primary;
|
export const defaultThemeTextColor = config.theme2.colors.text.primary;
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
import React, { PureComponent } from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { GrafanaTheme2 } from '@grafana/data';
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
import { PluginState } from '@grafana/data/src';
|
import { PluginState } from '@grafana/data/src';
|
||||||
import { TextDimensionMode } from '@grafana/schema';
|
import { TextDimensionMode } from '@grafana/schema';
|
||||||
import { Button, stylesFactory } from '@grafana/ui';
|
import { Button, Spinner, useStyles2 } from '@grafana/ui';
|
||||||
import { config } from 'app/core/config';
|
|
||||||
import { DimensionContext } from 'app/features/dimensions/context';
|
import { DimensionContext } from 'app/features/dimensions/context';
|
||||||
import { ColorDimensionEditor } from 'app/features/dimensions/editors';
|
import { ColorDimensionEditor } from 'app/features/dimensions/editors';
|
||||||
import { TextDimensionEditor } from 'app/features/dimensions/editors/TextDimensionEditor';
|
import { TextDimensionEditor } from 'app/features/dimensions/editors/TextDimensionEditor';
|
||||||
@ -40,26 +39,39 @@ export const defaultStyleConfig: ButtonStyleConfig = {
|
|||||||
variant: 'primary',
|
variant: 'primary',
|
||||||
};
|
};
|
||||||
|
|
||||||
class ButtonDisplay extends PureComponent<CanvasElementProps<ButtonConfig, ButtonData>> {
|
const ButtonDisplay = ({ data }: CanvasElementProps<ButtonConfig, ButtonData>) => {
|
||||||
render() {
|
const styles = useStyles2(getStyles, data);
|
||||||
const { data } = this.props;
|
|
||||||
const styles = getStyles(config.theme2, data);
|
|
||||||
|
|
||||||
const onClick = () => {
|
const [isLoading, setIsLoading] = React.useState(false);
|
||||||
if (data?.api && data?.api?.endpoint) {
|
|
||||||
callApi(data.api);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
const updateLoadingStateCallback = (loading: boolean) => {
|
||||||
<Button type="submit" variant={data?.style?.variant} onClick={onClick} className={styles.button}>
|
setIsLoading(loading);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onClick = () => {
|
||||||
|
if (data?.api && data?.api?.endpoint) {
|
||||||
|
setIsLoading(true);
|
||||||
|
callApi(data.api, updateLoadingStateCallback);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
variant={data?.style?.variant}
|
||||||
|
onClick={onClick}
|
||||||
|
className={styles.button}
|
||||||
|
disabled={!data?.api?.endpoint}
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
{isLoading && <Spinner inline={true} className={styles.buttonSpinner} />}
|
||||||
{data?.text}
|
{data?.text}
|
||||||
</Button>
|
</span>
|
||||||
);
|
</Button>
|
||||||
}
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
const getStyles = stylesFactory((theme: GrafanaTheme2, data: ButtonData | undefined) => ({
|
const getStyles = (theme: GrafanaTheme2, data: ButtonData | undefined) => ({
|
||||||
button: css({
|
button: css({
|
||||||
height: '100%',
|
height: '100%',
|
||||||
width: '100%',
|
width: '100%',
|
||||||
@ -67,12 +79,16 @@ const getStyles = stylesFactory((theme: GrafanaTheme2, data: ButtonData | undefi
|
|||||||
|
|
||||||
'> span': {
|
'> span': {
|
||||||
display: 'inline-grid',
|
display: 'inline-grid',
|
||||||
|
gridAutoFlow: 'column',
|
||||||
textAlign: data?.align,
|
textAlign: data?.align,
|
||||||
fontSize: `${data?.size}px`,
|
fontSize: `${data?.size}px`,
|
||||||
color: data?.color,
|
color: data?.color,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
}));
|
buttonSpinner: css({
|
||||||
|
marginRight: theme.spacing(0.5),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
export const buttonItem: CanvasElementItem<ButtonConfig, ButtonData> = {
|
export const buttonItem: CanvasElementItem<ButtonConfig, ButtonData> = {
|
||||||
id: 'button',
|
id: 'button',
|
||||||
@ -137,7 +153,7 @@ export const buttonItem: CanvasElementItem<ButtonConfig, ButtonData> = {
|
|||||||
const data: ButtonData = {
|
const data: ButtonData = {
|
||||||
text: cfg?.text ? ctx.getText(cfg.text).value() : '',
|
text: cfg?.text ? ctx.getText(cfg.text).value() : '',
|
||||||
align: cfg.align ?? Align.Center,
|
align: cfg.align ?? Align.Center,
|
||||||
size: cfg.size,
|
size: cfg.size ?? 14,
|
||||||
api: getCfgApi(),
|
api: getCfgApi(),
|
||||||
style: cfg?.style ?? defaultStyleConfig,
|
style: cfg?.style ?? defaultStyleConfig,
|
||||||
};
|
};
|
||||||
|
@ -132,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)} title="Test API">
|
||||||
Test API
|
Test API
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
@ -158,36 +158,36 @@ export function APIEditor({ value, context, onChange }: Props) {
|
|||||||
<RadioButtonGroup value={value?.method} options={httpMethodOptions} onChange={onMethodChange} fullWidth />
|
<RadioButtonGroup value={value?.method} options={httpMethodOptions} onChange={onMethodChange} fullWidth />
|
||||||
</InlineField>
|
</InlineField>
|
||||||
</InlineFieldRow>
|
</InlineFieldRow>
|
||||||
|
{value?.method === HttpRequestMethod.POST && (
|
||||||
|
<InlineFieldRow>
|
||||||
|
<InlineField label="Content-Type" labelWidth={LABEL_WIDTH} grow={true}>
|
||||||
|
<Select
|
||||||
|
options={contentTypeOptions}
|
||||||
|
allowCustomValue={true}
|
||||||
|
formatCreateLabel={formatCreateLabel}
|
||||||
|
value={value?.contentType}
|
||||||
|
onChange={onContentTypeChange}
|
||||||
|
/>
|
||||||
|
</InlineField>
|
||||||
|
</InlineFieldRow>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<br />
|
||||||
<Field label="Query parameters">
|
<Field label="Query parameters">
|
||||||
<ParamsEditor value={value?.queryParams ?? []} onChange={onQueryParamsChange} />
|
<ParamsEditor value={value?.queryParams ?? []} onChange={onQueryParamsChange} />
|
||||||
</Field>
|
</Field>
|
||||||
<Field label="Header parameters">
|
<Field label="Header parameters">
|
||||||
<ParamsEditor value={value?.headerParams ?? []} onChange={onHeaderParamsChange} />
|
<ParamsEditor value={value?.headerParams ?? []} onChange={onHeaderParamsChange} />
|
||||||
</Field>
|
</Field>
|
||||||
{value?.method === HttpRequestMethod.POST && (
|
{value?.method === HttpRequestMethod.POST && value?.contentType && (
|
||||||
<>
|
<Field label="Payload">
|
||||||
<InlineFieldRow>
|
<StringValueEditor
|
||||||
<InlineField label="Content-Type" labelWidth={LABEL_WIDTH} grow={true}>
|
context={context}
|
||||||
<Select
|
value={value?.data ?? '{}'}
|
||||||
options={contentTypeOptions}
|
onChange={onDataChange}
|
||||||
allowCustomValue={true}
|
item={{ ...dummyStringSettings, settings: { useTextarea: true } }}
|
||||||
formatCreateLabel={formatCreateLabel}
|
/>
|
||||||
value={value?.contentType}
|
</Field>
|
||||||
onChange={onContentTypeChange}
|
|
||||||
/>
|
|
||||||
</InlineField>
|
|
||||||
</InlineFieldRow>
|
|
||||||
{value?.contentType && (
|
|
||||||
<Field label="Payload">
|
|
||||||
<StringValueEditor
|
|
||||||
context={context}
|
|
||||||
value={value?.data ?? '{}'}
|
|
||||||
onChange={onDataChange}
|
|
||||||
item={{ ...dummyStringSettings, settings: { useTextarea: true } }}
|
|
||||||
/>
|
|
||||||
</Field>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
{renderTestAPIButton(value)}
|
{renderTestAPIButton(value)}
|
||||||
<br />
|
<br />
|
||||||
|
@ -7,11 +7,14 @@ import { HttpRequestMethod } from '../../panelcfg.gen';
|
|||||||
|
|
||||||
import { APIEditorConfig } from './APIEditor';
|
import { APIEditorConfig } from './APIEditor';
|
||||||
|
|
||||||
export const callApi = (api: APIEditorConfig, isTest = false) => {
|
type IsLoadingCallback = (loading: boolean) => void;
|
||||||
|
|
||||||
|
export const callApi = (api: APIEditorConfig, updateLoadingStateCallback?: IsLoadingCallback) => {
|
||||||
if (api && api.endpoint) {
|
if (api && api.endpoint) {
|
||||||
// If API endpoint origin matches Grafana origin, don't call it.
|
// If API endpoint origin matches Grafana origin, don't call it.
|
||||||
if (requestMatchesGrafanaOrigin(api.endpoint)) {
|
if (requestMatchesGrafanaOrigin(api.endpoint)) {
|
||||||
appEvents.emit(AppEvents.alertError, ['Cannot call API at Grafana origin.']);
|
appEvents.emit(AppEvents.alertError, ['Cannot call API at Grafana origin.']);
|
||||||
|
updateLoadingStateCallback && updateLoadingStateCallback(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const request = getRequest(api);
|
const request = getRequest(api);
|
||||||
@ -20,15 +23,12 @@ export const callApi = (api: APIEditorConfig, isTest = false) => {
|
|||||||
.fetch(request)
|
.fetch(request)
|
||||||
.subscribe({
|
.subscribe({
|
||||||
error: (error) => {
|
error: (error) => {
|
||||||
if (isTest) {
|
appEvents.emit(AppEvents.alertError, ['An error has occurred: ', JSON.stringify(error)]);
|
||||||
appEvents.emit(AppEvents.alertError, ['Error has occurred: ', JSON.stringify(error)]);
|
updateLoadingStateCallback && updateLoadingStateCallback(false);
|
||||||
console.error(error);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
complete: () => {
|
complete: () => {
|
||||||
if (isTest) {
|
appEvents.emit(AppEvents.alertSuccess, ['API call was successful']);
|
||||||
appEvents.emit(AppEvents.alertSuccess, ['Test successful']);
|
updateLoadingStateCallback && updateLoadingStateCallback(false);
|
||||||
}
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user