mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
VizActions: Add confirmation message (#100012)
Co-authored-by: Leon Sorokin <leeoniya@gmail.com>
This commit is contained in:
@@ -15,6 +15,7 @@ export interface Action {
|
||||
// Currently this is required because there is only one valid type (fetch)
|
||||
// once multiple types are valid, usage of this will need to be optional
|
||||
[ActionType.Fetch]: FetchOptions;
|
||||
confirmation?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -23,6 +24,7 @@ export interface Action {
|
||||
export interface ActionModel<T = any> {
|
||||
title: string;
|
||||
onClick: (event: any, origin?: any) => void;
|
||||
confirmation?: string;
|
||||
}
|
||||
|
||||
interface FetchOptions {
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
import { ActionModel, Field } from '@grafana/data';
|
||||
|
||||
import { t } from '../../utils/i18n';
|
||||
import { Button, ButtonProps } from '../Button';
|
||||
import { ConfirmModal } from '../ConfirmModal/ConfirmModal';
|
||||
|
||||
type ActionButtonProps = ButtonProps & {
|
||||
action: ActionModel<Field>;
|
||||
@@ -10,9 +14,29 @@ type ActionButtonProps = ButtonProps & {
|
||||
* @internal
|
||||
*/
|
||||
export function ActionButton({ action, ...buttonProps }: ActionButtonProps) {
|
||||
const [showConfirm, setShowConfirm] = useState(false);
|
||||
|
||||
return (
|
||||
<Button variant="primary" size="sm" onClick={action.onClick} {...buttonProps}>
|
||||
{action.title}
|
||||
</Button>
|
||||
<>
|
||||
<Button variant="primary" size="sm" onClick={() => setShowConfirm(true)} {...buttonProps}>
|
||||
{action.title}
|
||||
</Button>
|
||||
{showConfirm && (
|
||||
<ConfirmModal
|
||||
isOpen={true}
|
||||
title={t('grafana-ui.action-editor.button.confirm-action', 'Confirm action')}
|
||||
body={action.confirmation}
|
||||
confirmText={t('grafana-ui.action-editor.button.confirm', 'Confirm')}
|
||||
confirmButtonVariant="primary"
|
||||
onConfirm={() => {
|
||||
setShowConfirm(false);
|
||||
action.onClick(new MouseEvent('click'));
|
||||
}}
|
||||
onDismiss={() => {
|
||||
setShowConfirm(false);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import { InlineFieldRow } from '@grafana/ui/src/components/Forms/InlineFieldRow'
|
||||
import { RadioButtonGroup } from '@grafana/ui/src/components/Forms/RadioButtonGroup/RadioButtonGroup';
|
||||
import { JSONFormatter } from '@grafana/ui/src/components/JSONFormatter/JSONFormatter';
|
||||
import { useStyles2 } from '@grafana/ui/src/themes';
|
||||
import { t } from '@grafana/ui/src/utils/i18n';
|
||||
|
||||
import { HTMLElementType, SuggestionsInput } from '../transformers/suggestionsInput/SuggestionsInput';
|
||||
|
||||
@@ -29,6 +30,10 @@ export const ActionEditor = memo(({ index, value, onChange, suggestions }: Actio
|
||||
onChange(index, { ...value, title });
|
||||
};
|
||||
|
||||
const onConfirmationChange = (confirmation: string) => {
|
||||
onChange(index, { ...value, confirmation });
|
||||
};
|
||||
|
||||
const onUrlChange = (url: string) => {
|
||||
onChange(index, {
|
||||
...value,
|
||||
@@ -98,18 +103,42 @@ export const ActionEditor = memo(({ index, value, onChange, suggestions }: Actio
|
||||
|
||||
return (
|
||||
<div className={styles.listItem}>
|
||||
<Field label="Title">
|
||||
<Field label={t('grafana-ui.action-editor.modal.action-title', 'Title')} className={styles.inputField}>
|
||||
<SuggestionsInput
|
||||
value={value.title}
|
||||
onChange={onTitleChange}
|
||||
suggestions={suggestions}
|
||||
autoFocus={value.title === ''}
|
||||
placeholder="Action title"
|
||||
placeholder={t('grafana-ui.action-editor.modal.action-title-placeholder', 'Action title')}
|
||||
/>
|
||||
</Field>
|
||||
|
||||
<Field
|
||||
label={t('grafana-ui.viz-tooltip.actions-confirmation-label', 'Confirmation message')}
|
||||
description={t(
|
||||
'grafana-ui.viz-tooltip.actions-confirmation-message',
|
||||
'Provide a descriptive prompt to confirm or cancel the action.'
|
||||
)}
|
||||
className={styles.inputField}
|
||||
>
|
||||
<SuggestionsInput
|
||||
value={value.confirmation}
|
||||
onChange={onConfirmationChange}
|
||||
suggestions={suggestions}
|
||||
placeholder={t(
|
||||
'grafana-ui.viz-tooltip.actions-confirmation-input-placeholder',
|
||||
'Are you sure you want to {{ actionTitle }}?',
|
||||
{ actionTitle: value.title || '... ' }
|
||||
)}
|
||||
/>
|
||||
</Field>
|
||||
|
||||
<InlineFieldRow>
|
||||
<InlineField label="Method" labelWidth={LABEL_WIDTH} grow={true}>
|
||||
<InlineField
|
||||
label={t('grafana-ui.action-editor.modal.action-method', 'Method')}
|
||||
labelWidth={LABEL_WIDTH}
|
||||
grow={true}
|
||||
>
|
||||
<RadioButtonGroup<HttpRequestMethod>
|
||||
value={value?.fetch.method}
|
||||
options={httpMethodOptions}
|
||||
@@ -130,7 +159,10 @@ export const ActionEditor = memo(({ index, value, onChange, suggestions }: Actio
|
||||
</InlineField>
|
||||
</InlineFieldRow>
|
||||
|
||||
<Field label="Query parameters" className={styles.fieldGap}>
|
||||
<Field
|
||||
label={t('grafana-ui.action-editor.modal.action-query-params', 'Query parameters')}
|
||||
className={styles.fieldGap}
|
||||
>
|
||||
<ParamsEditor value={value?.fetch.queryParams ?? []} onChange={onQueryParamsChange} suggestions={suggestions} />
|
||||
</Field>
|
||||
|
||||
@@ -144,7 +176,7 @@ export const ActionEditor = memo(({ index, value, onChange, suggestions }: Actio
|
||||
</Field>
|
||||
|
||||
{value?.fetch.method !== HttpRequestMethod.GET && (
|
||||
<Field label="Body">
|
||||
<Field label={t('grafana-ui.action-editor.modal.action-body', 'Body')} className={styles.inputField}>
|
||||
<SuggestionsInput
|
||||
value={value.fetch.body}
|
||||
onChange={onBodyChange}
|
||||
@@ -176,6 +208,9 @@ const getStyles = (theme: GrafanaTheme2) => ({
|
||||
fieldGap: css({
|
||||
marginTop: theme.spacing(2),
|
||||
}),
|
||||
inputField: css({
|
||||
marginRight: 4,
|
||||
}),
|
||||
});
|
||||
|
||||
ActionEditor.displayName = 'ActionEditor';
|
||||
|
||||
@@ -50,10 +50,15 @@ export const getActions = (
|
||||
dataContext.value.calculatedValue = config.calculatedValue;
|
||||
}
|
||||
|
||||
let actionModel: ActionModel<Field> = { title: '', onClick: (e) => {} };
|
||||
const title = replaceVariables(action.title, actionScopedVars);
|
||||
const confirmation = replaceVariables(
|
||||
action.confirmation || `Are you sure you want to ${action.title}?`,
|
||||
actionScopedVars
|
||||
);
|
||||
|
||||
actionModel = {
|
||||
title: replaceVariables(action.title || '', actionScopedVars),
|
||||
const actionModel: ActionModel<Field> = {
|
||||
title,
|
||||
confirmation,
|
||||
onClick: (evt: MouseEvent, origin: Field) => {
|
||||
buildActionOnClick(action, boundReplaceVariables);
|
||||
},
|
||||
|
||||
@@ -1569,6 +1569,19 @@
|
||||
"send-custom-feedback": "Send"
|
||||
},
|
||||
"grafana-ui": {
|
||||
"action-editor": {
|
||||
"button": {
|
||||
"confirm": "Confirm",
|
||||
"confirm-action": "Confirm action"
|
||||
},
|
||||
"modal": {
|
||||
"action-body": "Body",
|
||||
"action-method": "Method",
|
||||
"action-query-params": "Query parameters",
|
||||
"action-title": "Title",
|
||||
"action-title-placeholder": "Action title"
|
||||
}
|
||||
},
|
||||
"auto-save-field": {
|
||||
"saved": "Saved!",
|
||||
"saving": "Saving <1></1>"
|
||||
@@ -1676,6 +1689,9 @@
|
||||
"right-axis-indicator": "(right y-axis)"
|
||||
},
|
||||
"viz-tooltip": {
|
||||
"actions-confirmation-input-placeholder": "Are you sure you want to {{ actionTitle }}?",
|
||||
"actions-confirmation-label": "Confirmation message",
|
||||
"actions-confirmation-message": "Provide a descriptive prompt to confirm or cancel the action.",
|
||||
"footer-add-annotation": "Add annotation",
|
||||
"footer-click-to-navigate": "Click to open {{linkTitle}}"
|
||||
}
|
||||
|
||||
@@ -1569,6 +1569,19 @@
|
||||
"send-custom-feedback": "Ŝęʼnđ"
|
||||
},
|
||||
"grafana-ui": {
|
||||
"action-editor": {
|
||||
"button": {
|
||||
"confirm": "Cőʼnƒįřm",
|
||||
"confirm-action": "Cőʼnƒįřm äčŧįőʼn"
|
||||
},
|
||||
"modal": {
|
||||
"action-body": "ßőđy",
|
||||
"action-method": "Męŧĥőđ",
|
||||
"action-query-params": "Qūęřy päřämęŧęřş",
|
||||
"action-title": "Ŧįŧľę",
|
||||
"action-title-placeholder": "Åčŧįőʼn ŧįŧľę"
|
||||
}
|
||||
},
|
||||
"auto-save-field": {
|
||||
"saved": "Ŝävęđ!",
|
||||
"saving": "Ŝävįʼnģ <1></1>"
|
||||
@@ -1676,6 +1689,9 @@
|
||||
"right-axis-indicator": "(řįģĥŧ y-äχįş)"
|
||||
},
|
||||
"viz-tooltip": {
|
||||
"actions-confirmation-input-placeholder": "Åřę yőū şūřę yőū ŵäʼnŧ ŧő {{ actionTitle }}?",
|
||||
"actions-confirmation-label": "Cőʼnƒįřmäŧįőʼn męşşäģę",
|
||||
"actions-confirmation-message": "Přővįđę ä đęşčřįpŧįvę přőmpŧ ŧő čőʼnƒįřm őř čäʼnčęľ ŧĥę äčŧįőʼn.",
|
||||
"footer-add-annotation": "Åđđ äʼnʼnőŧäŧįőʼn",
|
||||
"footer-click-to-navigate": "Cľįčĸ ŧő őpęʼn {{linkTitle}}"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user