Commit 9ac7263e by Dominik Prokop Committed by GitHub

DashboardSave: fix save dashboard when changes are detected (#23909)

parent 5211a23f
......@@ -7,21 +7,6 @@ import { stylesFactory, ThemeContext } from '../../themes';
import { GrafanaTheme } from '@grafana/data';
import { HorizontalGroup } from '..';
const getStyles = stylesFactory((theme: GrafanaTheme) => ({
modal: css`
width: 500px;
`,
modalContent: css`
text-align: center;
`,
modalText: css`
font-size: ${theme.typography.heading.h4};
color: ${theme.colors.link};
margin-bottom: calc(${theme.spacing.d} * 2);
padding-top: ${theme.spacing.d};
`,
}));
const defaultIcon: IconName = 'exclamation-triangle';
interface Props {
......@@ -64,3 +49,18 @@ export const ConfirmModal: FC<Props> = ({
</Modal>
);
};
const getStyles = stylesFactory((theme: GrafanaTheme) => ({
modal: css`
width: 500px;
`,
modalContent: css`
text-align: center;
`,
modalText: css`
font-size: ${theme.typography.heading.h4};
color: ${theme.colors.link};
margin-bottom: calc(${theme.spacing.d} * 2);
padding-top: ${theme.spacing.d};
`,
}));
......@@ -76,9 +76,10 @@ export const VerticalGroup: React.FC<Omit<LayoutProps, 'orientation' | 'wrap'>>
children,
spacing,
justify,
align,
width,
}) => (
<Layout spacing={spacing} justify={justify} orientation={Orientation.Vertical} width={width}>
<Layout spacing={spacing} justify={justify} orientation={Orientation.Vertical} align={align} width={width}>
{children}
</Layout>
);
......@@ -92,7 +93,11 @@ export const Container: React.FC<ContainerProps> = ({ children, padding, margin
const getStyles = stylesFactory(
(theme: GrafanaTheme, orientation: Orientation, spacing: Spacing, justify: Justify, align, wrap) => {
const finalSpacing = spacing !== 'none' ? theme.spacing[spacing] : 0;
const marginCompensation = orientation === Orientation.Horizontal && !wrap ? 0 : `-${finalSpacing}`;
// compensate for last row margin when wrapped, horizontal layout
const marginCompensation =
(orientation === Orientation.Horizontal && !wrap) || orientation === Orientation.Vertical
? 0
: `-${finalSpacing}`;
return {
layout: css`
......
......@@ -14,15 +14,9 @@ interface SaveDashboardButtonProps {
*/
getDashboard?: () => DashboardModel;
onSaveSuccess?: () => void;
useNewForms?: boolean;
}
export const SaveDashboardButton: React.FC<SaveDashboardButtonProps> = ({
dashboard,
onSaveSuccess,
getDashboard,
useNewForms,
}) => {
export const SaveDashboardButton: React.FC<SaveDashboardButtonProps> = ({ dashboard, onSaveSuccess, getDashboard }) => {
return (
<ModalsController>
{({ showModal, hideModal }) => {
......
......@@ -9,6 +9,7 @@ import { SaveDashboardModalProps } from './types';
export const SaveDashboardModal: React.FC<SaveDashboardModalProps> = ({ dashboard, onDismiss, onSaveSuccess }) => {
const { state, onDashboardSave } = useDashboardSave(dashboard);
const [dashboardSaveModelClone, setDashboardSaveModelClone] = useState();
return (
<>
{state.error && (
......
import React from 'react';
import { Button, HorizontalGroup, Modal, VerticalGroup } from '@grafana/ui';
import { SaveDashboardButton } from './SaveDashboardButton';
import { DashboardModel } from '../../state';
import { css } from 'emotion';
interface UnsavedChangesModalProps {
dashboard: DashboardModel;
onDiscard: () => void;
onDismiss: () => void;
onSaveSuccess?: () => void;
}
export const UnsavedChangesModal: React.FC<UnsavedChangesModalProps> = ({
dashboard,
onSaveSuccess,
onDiscard,
onDismiss,
}) => {
return (
<Modal
isOpen={true}
title="Unsaved changes"
onDismiss={onDismiss}
icon="exclamation-triangle"
className={css`
width: 500px;
`}
>
<VerticalGroup align={'center'} spacing={'md'}>
<h4>Do you want to save your changes?</h4>
<HorizontalGroup justify="center">
<SaveDashboardButton dashboard={dashboard} onSaveSuccess={onSaveSuccess} />
<Button
variant="destructive"
onClick={() => {
onDiscard();
onDismiss();
}}
>
Discard
</Button>
<Button variant="secondary" onClick={onDismiss}>
Cancel
</Button>
</HorizontalGroup>
</VerticalGroup>
</Modal>
);
};
import coreModule from 'app/core/core_module';
const template = `
<div class="modal-body">
<div class="modal-header">
<h2 class="modal-header-title">
<icon name="'exclamation-triangle'" size="'lg'"></icon>
<span class="p-l-1">Unsaved changes</span>
</h2>
<a class="modal-header-close" ng-click="ctrl.dismiss();">
<icon name="'times'"></icon>
</a>
</div>
<div class="modal-content text-center">
<div class="confirm-modal-text">
Do you want to save your changes?
</div>
<div class="confirm-modal-buttons">
<save-dashboard-button dashboard="ctrl.unsavedChangesSrv.tracker.current" onSaveSuccess="ctrl.onSaveSuccess" >Save</save-dashboard-button>
<button type="button" class="btn btn-danger" ng-click="ctrl.discard()">Discard</button>
<button type="button" class="btn btn-inverse" ng-click="ctrl.dismiss()">Cancel</button>
</div>
</div>
</div>
`;
export class UnsavedChangesModalCtrl {
clone: any;
dismiss: () => void;
/** @ngInject */
constructor(private unsavedChangesSrv: any) {}
discard() {
this.dismiss();
this.unsavedChangesSrv.tracker.discardChanges();
}
save() {
this.dismiss();
this.unsavedChangesSrv.tracker.saveChanges();
}
onSaveSuccess = () => {
this.dismiss();
this.unsavedChangesSrv.tracker.onSaveSuccess();
};
}
export function unsavedChangesModalDirective() {
return {
restrict: 'E',
template: template,
controller: UnsavedChangesModalCtrl,
bindToController: true,
controllerAs: 'ctrl',
scope: { dismiss: '&' },
};
}
coreModule.directive('unsavedChangesModal', unsavedChangesModalDirective);
export { UnsavedChangesModalCtrl } from './UnsavedChangesModalCtrl';
......@@ -10,7 +10,6 @@ import './components/DashNav';
import './components/VersionHistory';
import './components/DashboardSettings';
import './components/SubMenu';
import './components/UnsavedChangesModal';
import './components/AdHocFilters';
import './components/RowOptions';
......
......@@ -5,6 +5,8 @@ import { ContextSrv } from 'app/core/services/context_srv';
import { GrafanaRootScope } from 'app/routes/GrafanaCtrl';
import { AppEventConsumer, CoreEvents } from 'app/types';
import { appEvents } from 'app/core/app_events';
import { UnsavedChangesModal } from '../components/SaveDashboard/UnsavedChangesModal';
import { getLocationSrv } from '@grafana/runtime';
export class ChangeTracker {
current: any;
......@@ -159,17 +161,23 @@ export class ChangeTracker {
return currentJson !== originalJson;
}
discardChanges() {
discardChanges = () => {
this.original = null;
this.gotoNext();
}
};
open_modal() {
this.$rootScope.appEvent(CoreEvents.showModal, {
templateHtml: '<unsaved-changes-modal dismiss="dismiss()"></unsaved-changes-modal>',
modalClass: 'modal--narrow confirm-modal',
open_modal = () => {
this.$rootScope.appEvent(CoreEvents.showModalReact, {
component: UnsavedChangesModal,
props: {
dashboard: this.current,
onSaveSuccess: this.onSaveSuccess,
onDiscard: () => {
this.discardChanges();
},
},
});
}
};
onSaveSuccess = () => {
this.$timeout(() => {
......@@ -177,9 +185,11 @@ export class ChangeTracker {
});
};
gotoNext() {
gotoNext = () => {
const baseLen = this.$location.absUrl().length - this.$location.url().length;
const nextUrl = this.next.substring(baseLen);
this.$location.url(nextUrl);
}
getLocationSrv().update({
path: nextUrl,
});
};
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment