Canvas: Add actions support (#90677)

Co-authored-by: Leon Sorokin <leeoniya@gmail.com>
This commit is contained in:
Adela Almasan
2024-09-09 09:11:55 -05:00
committed by GitHub
co-authored by Leon Sorokin
parent b89f3f8115
commit af48d3db1e
28 changed files with 1194 additions and 50 deletions
@@ -607,7 +607,7 @@ export function useFieldOverrides(
/**
* Clones the existing dataContext or creates a new one
*/
function getFieldDataContextClone(frame: DataFrame, field: Field, fieldScopedVars: ScopedVars) {
export function getFieldDataContextClone(frame: DataFrame, field: Field, fieldScopedVars: ScopedVars) {
if (fieldScopedVars?.__dataContext) {
return {
value: {
+9
View File
@@ -145,6 +145,7 @@ export {
validateFieldConfig,
applyRawFieldOverrides,
useFieldOverrides,
getFieldDataContextClone,
} from './field/fieldOverrides';
export { getFieldDisplayValuesProxy } from './field/getFieldDisplayValuesProxy';
export {
@@ -800,6 +801,14 @@ export {
VariableSuggestionsScope,
OneClickMode,
} from './types/dataLink';
export {
type Action,
type ActionModel,
HttpRequestMethod,
defaultActionConfig,
contentTypeOptions,
httpMethodOptions,
} from './types/action';
export { DataFrameType } from './types/dataFrameTypes';
export {
FieldType,
+70
View File
@@ -0,0 +1,70 @@
import { ScopedVars } from './ScopedVars';
import { DataFrame, Field, ValueLinkConfig } from './dataFrame';
import { InterpolateFunction } from './panel';
import { SelectableValue } from './select';
export interface Action<T = ActionType.Fetch, TOptions = FetchOptions> {
type: T;
title: string;
options: TOptions;
}
/**
* Processed Action Model. The values are ready to use
*/
export interface ActionModel<T = any> {
title: string;
onClick: (event: any, origin?: any) => void;
}
interface FetchOptions {
method: HttpRequestMethod;
url: string;
body?: string;
queryParams?: Array<[string, string]>;
headers?: Array<[string, string]>;
}
export enum ActionType {
Fetch = 'fetch',
}
export enum HttpRequestMethod {
POST = 'POST',
PUT = 'PUT',
GET = 'GET',
}
export const httpMethodOptions: SelectableValue[] = [
{ label: HttpRequestMethod.POST, value: HttpRequestMethod.POST },
{ label: HttpRequestMethod.PUT, value: HttpRequestMethod.PUT },
{ label: HttpRequestMethod.GET, value: HttpRequestMethod.GET },
];
export const contentTypeOptions: SelectableValue[] = [
{ label: 'application/json', value: 'application/json' },
{ label: 'text/plain', value: 'text/plain' },
{ label: 'application/xml', value: 'application/xml' },
{ label: 'application/x-www-form-urlencoded', value: 'application/x-www-form-urlencoded' },
];
export const defaultActionConfig: Action = {
type: ActionType.Fetch,
title: '',
options: {
url: '',
method: HttpRequestMethod.POST,
body: '{}',
queryParams: [],
headers: [['Content-Type', 'application/json']],
},
};
export type ActionsArgs = {
frame: DataFrame;
field: Field;
fieldScopedVars: ScopedVars;
replaceVariables: InterpolateFunction;
actions: Action[];
config: ValueLinkConfig;
};
+1 -1
View File
@@ -51,7 +51,6 @@ export interface DataLink<T extends DataQuery = any> {
internal?: InternalDataLink<T>;
origin?: DataLinkConfigOrigin;
sortIndex?: number;
}
/**
@@ -131,6 +130,7 @@ export enum VariableSuggestionsScope {
}
export enum OneClickMode {
Action = 'action',
Link = 'link',
Off = 'off',
}
@@ -38,6 +38,7 @@ export interface FeatureToggles {
autoMigrateXYChartPanel?: boolean;
disableAngular?: boolean;
canvasPanelNesting?: boolean;
vizActions?: boolean;
disableSecretsCompatibility?: boolean;
logRequestsInstrumentedAsUnknown?: boolean;
topnav?: boolean;
@@ -0,0 +1,18 @@
import { ActionModel, Field } from '@grafana/data';
import { Button, ButtonProps } from '../Button';
type ActionButtonProps = ButtonProps & {
action: ActionModel<Field>;
};
/**
* @internal
*/
export function ActionButton({ action, ...buttonProps }: ActionButtonProps) {
return (
<Button variant="primary" size="sm" onClick={action.onClick} {...buttonProps}>
{action.title}
</Button>
);
}
@@ -1,37 +1,50 @@
import { css } from '@emotion/css';
import { Field, GrafanaTheme2, LinkModel } from '@grafana/data';
import { ActionModel, Field, GrafanaTheme2, LinkModel } from '@grafana/data';
import { Button, ButtonProps, DataLinkButton, Stack } from '..';
import { useStyles2 } from '../../themes';
import { ActionButton } from '../Actions/ActionButton';
interface VizTooltipFooterProps {
dataLinks: Array<LinkModel<Field>>;
actions?: Array<ActionModel<Field>>;
annotate?: () => void;
}
export const ADD_ANNOTATION_ID = 'add-annotation-button';
export const VizTooltipFooter = ({ dataLinks, annotate }: VizTooltipFooterProps) => {
const styles = useStyles2(getStyles);
const renderDataLinks = () => {
const buttonProps: ButtonProps = {
variant: 'secondary',
};
return (
<Stack direction="column" justifyContent="flex-start">
{dataLinks.map((link, i) => (
<DataLinkButton key={i} link={link} buttonProps={buttonProps} />
))}
</Stack>
);
const renderDataLinks = (dataLinks: LinkModel[]) => {
const buttonProps: ButtonProps = {
variant: 'secondary',
};
return (
<Stack direction="column" justifyContent="flex-start">
{dataLinks.map((link, i) => (
<DataLinkButton key={i} link={link} buttonProps={buttonProps} />
))}
</Stack>
);
};
const renderActions = (actions: ActionModel[]) => {
return (
<Stack direction="column" justifyContent="flex-start">
{actions.map((action, i) => (
<ActionButton key={i} action={action} variant="secondary" />
))}
</Stack>
);
};
export const VizTooltipFooter = ({ dataLinks, actions, annotate }: VizTooltipFooterProps) => {
const styles = useStyles2(getStyles);
return (
<div className={styles.wrapper}>
{dataLinks.length > 0 && <div className={styles.dataLinks}>{renderDataLinks()}</div>}
{dataLinks.length > 0 && <div className={styles.dataLinks}>{renderDataLinks(dataLinks)}</div>}
{actions && actions.length > 0 && <div className={styles.dataLinks}>{renderActions(actions)}</div>}
{annotate != null && (
<div className={styles.addAnnotations}>
<Button icon="comment-alt" variant="secondary" size="sm" id={ADD_ANNOTATION_ID} onClick={annotate}>