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'; ...@@ -7,21 +7,6 @@ import { stylesFactory, ThemeContext } from '../../themes';
import { GrafanaTheme } from '@grafana/data'; import { GrafanaTheme } from '@grafana/data';
import { HorizontalGroup } from '..'; 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'; const defaultIcon: IconName = 'exclamation-triangle';
interface Props { interface Props {
...@@ -64,3 +49,18 @@ export const ConfirmModal: FC<Props> = ({ ...@@ -64,3 +49,18 @@ export const ConfirmModal: FC<Props> = ({
</Modal> </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'>> ...@@ -76,9 +76,10 @@ export const VerticalGroup: React.FC<Omit<LayoutProps, 'orientation' | 'wrap'>>
children, children,
spacing, spacing,
justify, justify,
align,
width, width,
}) => ( }) => (
<Layout spacing={spacing} justify={justify} orientation={Orientation.Vertical} width={width}> <Layout spacing={spacing} justify={justify} orientation={Orientation.Vertical} align={align} width={width}>
{children} {children}
</Layout> </Layout>
); );
...@@ -92,7 +93,11 @@ export const Container: React.FC<ContainerProps> = ({ children, padding, margin ...@@ -92,7 +93,11 @@ export const Container: React.FC<ContainerProps> = ({ children, padding, margin
const getStyles = stylesFactory( const getStyles = stylesFactory(
(theme: GrafanaTheme, orientation: Orientation, spacing: Spacing, justify: Justify, align, wrap) => { (theme: GrafanaTheme, orientation: Orientation, spacing: Spacing, justify: Justify, align, wrap) => {
const finalSpacing = spacing !== 'none' ? theme.spacing[spacing] : 0; 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 { return {
layout: css` layout: css`
......
...@@ -14,15 +14,9 @@ interface SaveDashboardButtonProps { ...@@ -14,15 +14,9 @@ interface SaveDashboardButtonProps {
*/ */
getDashboard?: () => DashboardModel; getDashboard?: () => DashboardModel;
onSaveSuccess?: () => void; onSaveSuccess?: () => void;
useNewForms?: boolean;
} }
export const SaveDashboardButton: React.FC<SaveDashboardButtonProps> = ({ export const SaveDashboardButton: React.FC<SaveDashboardButtonProps> = ({ dashboard, onSaveSuccess, getDashboard }) => {
dashboard,
onSaveSuccess,
getDashboard,
useNewForms,
}) => {
return ( return (
<ModalsController> <ModalsController>
{({ showModal, hideModal }) => { {({ showModal, hideModal }) => {
......
...@@ -9,6 +9,7 @@ import { SaveDashboardModalProps } from './types'; ...@@ -9,6 +9,7 @@ import { SaveDashboardModalProps } from './types';
export const SaveDashboardModal: React.FC<SaveDashboardModalProps> = ({ dashboard, onDismiss, onSaveSuccess }) => { export const SaveDashboardModal: React.FC<SaveDashboardModalProps> = ({ dashboard, onDismiss, onSaveSuccess }) => {
const { state, onDashboardSave } = useDashboardSave(dashboard); const { state, onDashboardSave } = useDashboardSave(dashboard);
const [dashboardSaveModelClone, setDashboardSaveModelClone] = useState(); const [dashboardSaveModelClone, setDashboardSaveModelClone] = useState();
return ( return (
<> <>
{state.error && ( {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'; ...@@ -10,7 +10,6 @@ import './components/DashNav';
import './components/VersionHistory'; import './components/VersionHistory';
import './components/DashboardSettings'; import './components/DashboardSettings';
import './components/SubMenu'; import './components/SubMenu';
import './components/UnsavedChangesModal';
import './components/AdHocFilters'; import './components/AdHocFilters';
import './components/RowOptions'; import './components/RowOptions';
......
...@@ -5,6 +5,8 @@ import { ContextSrv } from 'app/core/services/context_srv'; ...@@ -5,6 +5,8 @@ import { ContextSrv } from 'app/core/services/context_srv';
import { GrafanaRootScope } from 'app/routes/GrafanaCtrl'; import { GrafanaRootScope } from 'app/routes/GrafanaCtrl';
import { AppEventConsumer, CoreEvents } from 'app/types'; import { AppEventConsumer, CoreEvents } from 'app/types';
import { appEvents } from 'app/core/app_events'; import { appEvents } from 'app/core/app_events';
import { UnsavedChangesModal } from '../components/SaveDashboard/UnsavedChangesModal';
import { getLocationSrv } from '@grafana/runtime';
export class ChangeTracker { export class ChangeTracker {
current: any; current: any;
...@@ -159,17 +161,23 @@ export class ChangeTracker { ...@@ -159,17 +161,23 @@ export class ChangeTracker {
return currentJson !== originalJson; return currentJson !== originalJson;
} }
discardChanges() { discardChanges = () => {
this.original = null; this.original = null;
this.gotoNext(); this.gotoNext();
} };
open_modal() { open_modal = () => {
this.$rootScope.appEvent(CoreEvents.showModal, { this.$rootScope.appEvent(CoreEvents.showModalReact, {
templateHtml: '<unsaved-changes-modal dismiss="dismiss()"></unsaved-changes-modal>', component: UnsavedChangesModal,
modalClass: 'modal--narrow confirm-modal', props: {
dashboard: this.current,
onSaveSuccess: this.onSaveSuccess,
onDiscard: () => {
this.discardChanges();
},
},
}); });
} };
onSaveSuccess = () => { onSaveSuccess = () => {
this.$timeout(() => { this.$timeout(() => {
...@@ -177,9 +185,11 @@ export class ChangeTracker { ...@@ -177,9 +185,11 @@ export class ChangeTracker {
}); });
}; };
gotoNext() { gotoNext = () => {
const baseLen = this.$location.absUrl().length - this.$location.url().length; const baseLen = this.$location.absUrl().length - this.$location.url().length;
const nextUrl = this.next.substring(baseLen); 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