grafana/public/app/features/dashboard-scene/saving/SaveDashboardForm.tsx

264 lines
8.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useState } from 'react';
import { selectors } from '@grafana/e2e-selectors';
import { Trans, useTranslate } from '@grafana/i18n';
import { Button, Checkbox, TextArea, Stack, Alert, Box, Field } from '@grafana/ui';
import { SaveDashboardOptions } from 'app/features/dashboard/components/SaveDashboard/types';
import { DashboardScene } from '../scene/DashboardScene';
import { SaveDashboardDrawer } from './SaveDashboardDrawer';
import {
DashboardChangeInfo,
NameAlreadyExistsError,
SaveButton,
isNameExistsError,
isPluginDashboardError,
isVersionMismatchError,
} from './shared';
import { useSaveDashboard } from './useSaveDashboard';
export interface Props {
dashboard: DashboardScene;
drawer: SaveDashboardDrawer;
changeInfo: DashboardChangeInfo;
}
export function SaveDashboardForm({ dashboard, drawer, changeInfo }: Props) {
const { hasChanges, hasMigratedToV2, changedSaveModel } = changeInfo;
const { state, onSaveDashboard } = useSaveDashboard(false);
const [options, setOptions] = useState<SaveDashboardOptions>({
folderUid: dashboard.state.meta.folderUid,
// we need to set the uid here in order to save the dashboard
// in schema v2 we don't have the uid in the spec
k8s: {
...dashboard.serializer.getK8SMetadata(),
},
});
const { t } = useTranslate();
const onSave = async (overwrite: boolean) => {
const result = await onSaveDashboard(dashboard, { ...options, rawDashboardJSON: changedSaveModel, overwrite });
if (result.status === 'success') {
dashboard.closeModal();
drawer.state.onSaveSuccess?.();
}
};
const cancelButton = (
<Button variant="secondary" onClick={() => dashboard.closeModal()} fill="outline">
<Trans i18nKey="dashboard-scene.save-dashboard-form.cancel-button.cancel">Cancel</Trans>
</Button>
);
const saveButton = (overwrite: boolean) => (
<SaveButton isValid={hasChanges} isLoading={state.loading} onSave={onSave} overwrite={overwrite} />
);
const isMessageTooLongError = (message?: string) => {
return message && message.length > 500;
};
function renderFooter(error?: Error) {
if (isMessageTooLongError(options.message)) {
const messageLength = options.message?.length ?? 0;
return (
<Alert title={t('save-dashboards.message-length.title', 'Message too long')} severity="error">
<p>
<Trans i18nKey="save-dashboards.message-length.info">
The message is {{ messageLength }} characters, which exceeds the maximum length of 500 characters. Please
shorten it before saving.
</Trans>
</p>
</Alert>
);
}
if (isVersionMismatchError(error)) {
return (
<Alert
title={t(
'dashboard-scene.save-dashboard-form.render-footer.title-someone-else-has-updated-this-dashboard',
'Someone else has updated this dashboard'
)}
severity="error"
>
<p>
<Trans i18nKey="dashboard-scene.save-dashboard-form.render-footer.would-still-dashboard">
Would you still like to save this dashboard?
</Trans>
</p>
<Box paddingTop={2}>
<Stack alignItems="center">
{cancelButton}
{saveButton(true)}
</Stack>
</Box>
</Alert>
);
}
if (isNameExistsError(error)) {
return <NameAlreadyExistsError cancelButton={cancelButton} saveButton={saveButton} />;
}
if (isPluginDashboardError(error)) {
return (
<Alert
title={t('dashboard-scene.save-dashboard-form.render-footer.title-plugin-dashboard', 'Plugin dashboard')}
severity="error"
>
<p>
<Trans i18nKey="dashboard-scene.save-dashboard-form.render-footer.body-plugin-dashboard">
Your changes will be lost when you update the plugin. Use <strong>Save as</strong> to create custom
version.
</Trans>
</p>
<Box paddingTop={2}>
<Stack alignItems="center">
{cancelButton}
{saveButton(true)}
</Stack>
</Box>
</Alert>
);
}
return (
<>
{error && (
<Alert
title={t(
'dashboard-scene.save-dashboard-form.render-footer.title-failed-to-save-dashboard',
'Failed to save dashboard'
)}
severity="error"
>
<p>{error.message}</p>
</Alert>
)}
<Stack alignItems="center">
{cancelButton}
{saveButton(false)}
{!hasChanges && (
<div>
<Trans i18nKey="dashboard-scene.save-dashboard-form.render-footer.no-changes-to-save">
No changes to save
</Trans>
</div>
)}
</Stack>
</>
);
}
return (
<Stack gap={2} direction="column">
<SaveDashboardFormCommonOptions drawer={drawer} changeInfo={changeInfo} />
{hasMigratedToV2 && (
<Alert
title={t(
'dashboard-scene.save-dashboard-form.title-dashboard-drastically-changed',
'Dashboard irreversibly changed'
)}
severity="warning"
>
<p>
<Trans i18nKey="dashboard-scene.save-dashboard-form.body-dashboard-drastically-changed">
The dashboard will be saved using the new experimental Grafana dashboard schema. This action cant be
reverted and could result in the irreversible loss of data. We recommend that you save this dashboard as a
copy instead. If youre seeing this message in a production environment, contact Support to have the
feature disabled.
</Trans>
</p>
</Alert>
)}
<Field label={t('dashboard-scene.save-dashboard-form.label-message', 'Message')}>
<TextArea
aria-label={t('dashboard-scene.save-dashboard-form.aria-label-message', 'message')}
value={options.message ?? ''}
onChange={(e) => {
setOptions({
...options,
message: e.currentTarget.value,
});
}}
placeholder={t(
'dashboard-scene.save-dashboard-form.placeholder-describe-changes-optional',
'Add a note to describe your changes (optional).'
)}
autoFocus
rows={5}
/>
</Field>
{renderFooter(state.error)}
</Stack>
);
}
export interface SaveDashboardFormCommonOptionsProps {
drawer: SaveDashboardDrawer;
changeInfo: DashboardChangeInfo;
}
export function SaveDashboardFormCommonOptions({ drawer, changeInfo }: SaveDashboardFormCommonOptionsProps) {
const { t } = useTranslate();
const { saveVariables = false, saveTimeRange = false, saveRefresh = false } = drawer.useState();
const { hasTimeChanges, hasVariableValueChanges, hasRefreshChange } = changeInfo;
return (
<Stack direction={'column'} alignItems={'flex-start'}>
{hasTimeChanges && (
<Checkbox
id="save-timerange"
checked={saveTimeRange}
onChange={drawer.onToggleSaveTimeRange}
label={t(
'dashboard-scene.save-dashboard-form-common-options.save-timerange-label-update-default-time-range',
'Update default time range'
)}
description={t(
'dashboard-scene.save-dashboard-form-common-options.save-timerange-description-current-range-default',
'Will make current time range the new default'
)}
data-testid={selectors.pages.SaveDashboardModal.saveTimerange}
/>
)}
{hasRefreshChange && (
<Checkbox
id="save-refresh"
label={t(
'dashboard-scene.save-dashboard-form-common-options.save-refresh-label-update-default-refresh-value',
'Update default refresh value'
)}
description={t(
'dashboard-scene.save-dashboard-form-common-options.save-refresh-description-current-refresh-default',
'Will make the current refresh the new default'
)}
checked={saveRefresh}
onChange={drawer.onToggleSaveRefresh}
data-testid={selectors.pages.SaveDashboardModal.saveRefresh}
/>
)}
{hasVariableValueChanges && (
<Checkbox
id="save-variables"
label={t(
'dashboard-scene.save-dashboard-form-common-options.save-variables-label-update-default-variable-values',
'Update default variable values'
)}
description={t(
'dashboard-scene.save-dashboard-form-common-options.save-variables-description-current-values-default',
'Will make the current values the new default'
)}
checked={saveVariables}
onChange={drawer.onToggleSaveVariables}
data-testid={selectors.pages.SaveDashboardModal.saveVariables}
/>
)}
</Stack>
);
}