Commit f30074c7 by Torkel Ödegaard Committed by GitHub

NewPanelEditor: Save dashboard from edit mode now works, and other fixes (#23668)

parent 165a0471
...@@ -67,13 +67,14 @@ const getPropertiesForVariant = (theme: GrafanaTheme, variant: ButtonVariant) => ...@@ -67,13 +67,14 @@ const getPropertiesForVariant = (theme: GrafanaTheme, variant: ButtonVariant) =>
export interface StyleProps { export interface StyleProps {
theme: GrafanaTheme; theme: GrafanaTheme;
size: ComponentSize; size: ComponentSize;
icon?: IconName;
variant: ButtonVariant; variant: ButtonVariant;
textAndIcon?: boolean; hasIcon: boolean;
hasText: boolean;
} }
export const getButtonStyles = stylesFactory(({ theme, size, variant, icon }: StyleProps) => { export const getButtonStyles = stylesFactory((props: StyleProps) => {
const { padding, fontSize, height } = getPropertiesForButtonSize(theme, size, icon); const { theme, variant } = props;
const { padding, fontSize, height } = getPropertiesForButtonSize(props);
const { background, borderColor, variantStyles } = getPropertiesForVariant(theme, variant); const { background, borderColor, variantStyles } = getPropertiesForVariant(theme, variant);
return { return {
...@@ -105,9 +106,6 @@ export const getButtonStyles = stylesFactory(({ theme, size, variant, icon }: St ...@@ -105,9 +106,6 @@ export const getButtonStyles = stylesFactory(({ theme, size, variant, icon }: St
${variantStyles} ${variantStyles}
` `
), ),
buttonWithIcon: css`
padding-left: ${theme.spacing.sm};
`,
// used for buttons with icon only // used for buttons with icon only
iconButton: css` iconButton: css`
padding-right: 0; padding-right: 0;
...@@ -139,7 +137,8 @@ export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>( ...@@ -139,7 +137,8 @@ export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
theme, theme,
size: otherProps.size || 'md', size: otherProps.size || 'md',
variant: variant || 'primary', variant: variant || 'primary',
icon, hasText: children !== undefined,
hasIcon: icon !== undefined,
}); });
return ( return (
...@@ -162,7 +161,8 @@ export const LinkButton = React.forwardRef<HTMLAnchorElement, ButtonLinkProps>( ...@@ -162,7 +161,8 @@ export const LinkButton = React.forwardRef<HTMLAnchorElement, ButtonLinkProps>(
theme, theme,
size: otherProps.size || 'md', size: otherProps.size || 'md',
variant: variant || 'primary', variant: variant || 'primary',
icon, hasText: children !== undefined,
hasIcon: icon !== undefined,
}); });
return ( return (
......
...@@ -17,10 +17,16 @@ export interface RadioButtonProps { ...@@ -17,10 +17,16 @@ export interface RadioButtonProps {
} }
const getRadioButtonStyles = stylesFactory((theme: GrafanaTheme, size: RadioButtonSize, fullWidth?: boolean) => { const getRadioButtonStyles = stylesFactory((theme: GrafanaTheme, size: RadioButtonSize, fullWidth?: boolean) => {
const { fontSize, height } = getPropertiesForButtonSize(theme, size); const { fontSize, height } = getPropertiesForButtonSize({
theme,
size,
hasIcon: false,
hasText: true,
variant: 'secondary',
});
const horizontalPadding = theme.spacing[size] ?? theme.spacing.md; const horizontalPadding = theme.spacing[size] ?? theme.spacing.md;
const c = theme.palette; const c = theme.palette;
const textColor = theme.colors.textSemiWeak; const textColor = theme.colors.textSemiWeak;
const textColorHover = theme.colors.text; const textColorHover = theme.colors.text;
const textColorActive = theme.isLight ? c.blue77 : c.blue95; const textColorActive = theme.isLight ? c.blue77 : c.blue95;
......
import { css } from 'emotion'; import { css } from 'emotion';
import { GrafanaTheme } from '@grafana/data'; import { GrafanaTheme } from '@grafana/data';
import { ComponentSize } from '../../types/size'; import { StyleProps } from '../Button';
import { IconName } from '../../types';
export const getFocusCss = (theme: GrafanaTheme) => ` export const getFocusCss = (theme: GrafanaTheme) => `
outline: 2px dotted transparent; outline: 2px dotted transparent;
...@@ -91,8 +90,9 @@ export const inputSizesPixels = (size: string) => { ...@@ -91,8 +90,9 @@ export const inputSizesPixels = (size: string) => {
} }
}; };
export const getPropertiesForButtonSize = (theme: GrafanaTheme, size: ComponentSize, icon?: IconName) => { export const getPropertiesForButtonSize = (props: StyleProps) => {
const { spacing, typography, height } = theme; const { hasText, hasIcon, size } = props;
const { spacing, typography, height } = props.theme;
switch (size) { switch (size) {
case 'sm': case 'sm':
...@@ -104,14 +104,14 @@ export const getPropertiesForButtonSize = (theme: GrafanaTheme, size: ComponentS ...@@ -104,14 +104,14 @@ export const getPropertiesForButtonSize = (theme: GrafanaTheme, size: ComponentS
case 'lg': case 'lg':
return { return {
padding: `0 ${spacing.lg} 0 ${icon ? spacing.md : spacing.lg}`, padding: `0 ${hasText ? spacing.lg : spacing.md} 0 ${hasIcon ? spacing.md : spacing.lg}`,
fontSize: typography.size.lg, fontSize: typography.size.lg,
height: height.lg, height: height.lg,
}; };
case 'md': case 'md':
default: default:
return { return {
padding: `0 ${spacing.md} 0 ${icon ? spacing.sm : spacing.md}`, padding: `0 ${hasText ? spacing.md : spacing.sm} 0 ${hasIcon ? spacing.sm : spacing.md}`,
fontSize: typography.size.md, fontSize: typography.size.md,
height: height.md, height: height.md,
}; };
......
...@@ -19,6 +19,8 @@ export const getFormStyles = stylesFactory( ...@@ -19,6 +19,8 @@ export const getFormStyles = stylesFactory(
theme, theme,
variant: options.variant, variant: options.variant,
size: options.size, size: options.size,
hasIcon: false,
hasText: true,
}), }),
input: getInputStyles({ theme, invalid: options.invalid }), input: getInputStyles({ theme, invalid: options.invalid }),
switch: getSwitchStyles(theme), switch: getSwitchStyles(theme),
......
...@@ -32,6 +32,7 @@ export const getModalStyles = stylesFactory((theme: GrafanaTheme) => { ...@@ -32,6 +32,7 @@ export const getModalStyles = stylesFactory((theme: GrafanaTheme) => {
opacity: 0.7; opacity: 0.7;
`, `,
modalHeader: css` modalHeader: css`
label: modalHeader;
background: ${theme.colors.bg2}; background: ${theme.colors.bg2};
border-bottom: 1px solid ${theme.colors.pageHeaderBorder}; border-bottom: 1px solid ${theme.colors.pageHeaderBorder};
display: flex; display: flex;
...@@ -42,6 +43,7 @@ export const getModalStyles = stylesFactory((theme: GrafanaTheme) => { ...@@ -42,6 +43,7 @@ export const getModalStyles = stylesFactory((theme: GrafanaTheme) => {
margin: 0 ${theme.spacing.md}; margin: 0 ${theme.spacing.md};
display: flex; display: flex;
align-items: center; align-items: center;
line-height: 42px;
`, `,
modalHeaderIcon: css` modalHeaderIcon: css`
margin-right: ${theme.spacing.md}; margin-right: ${theme.spacing.md};
......
...@@ -48,7 +48,7 @@ export class DashboardSettings extends PureComponent<Props> { ...@@ -48,7 +48,7 @@ export class DashboardSettings extends PureComponent<Props> {
return ( return (
<div className="dashboard-settings"> <div className="dashboard-settings">
<div className="navbar navbar--shadow"> <div className="navbar navbar--edit">
<div className="navbar-edit"> <div className="navbar-edit">
<BackButton surface="body" onClick={this.onClose} /> <BackButton surface="body" onClick={this.onClose} />
</div> </div>
......
...@@ -17,7 +17,7 @@ import { Unsubscribable } from 'rxjs'; ...@@ -17,7 +17,7 @@ import { Unsubscribable } from 'rxjs';
import { DisplayMode, displayModes, PanelEditorTab } from './types'; import { DisplayMode, displayModes, PanelEditorTab } from './types';
import { PanelEditorTabs } from './PanelEditorTabs'; import { PanelEditorTabs } from './PanelEditorTabs';
import { DashNavTimeControls } from '../DashNav/DashNavTimeControls'; import { DashNavTimeControls } from '../DashNav/DashNavTimeControls';
import { LocationState } from 'app/types'; import { LocationState, CoreEvents } from 'app/types';
import { calculatePanelSize } from './utils'; import { calculatePanelSize } from './utils';
import { initPanelEditor, panelEditorCleanUp, updatePanelEditorUIState } from './state/actions'; import { initPanelEditor, panelEditorCleanUp, updatePanelEditorUIState } from './state/actions';
import { PanelEditorUIState, setDiscardChanges } from './state/reducers'; import { PanelEditorUIState, setDiscardChanges } from './state/reducers';
...@@ -29,6 +29,8 @@ import { VariableModel } from 'app/features/templating/types'; ...@@ -29,6 +29,8 @@ import { VariableModel } from 'app/features/templating/types';
import { getVariables } from 'app/features/variables/state/selectors'; import { getVariables } from 'app/features/variables/state/selectors';
import { SubMenuItems } from 'app/features/dashboard/components/SubMenu/SubMenuItems'; import { SubMenuItems } from 'app/features/dashboard/components/SubMenu/SubMenuItems';
import { BackButton } from 'app/core/components/BackButton/BackButton'; import { BackButton } from 'app/core/components/BackButton/BackButton';
import { appEvents } from 'app/core/core';
import { SaveDashboardModalProxy } from '../SaveDashboard/SaveDashboardModalProxy';
interface OwnProps { interface OwnProps {
dashboard: DashboardModel; dashboard: DashboardModel;
...@@ -82,6 +84,17 @@ export class PanelEditorUnconnected extends PureComponent<Props> { ...@@ -82,6 +84,17 @@ export class PanelEditorUnconnected extends PureComponent<Props> {
}); });
}; };
onOpenDashboardSettings = () => {
this.props.updateLocation({ query: { editview: 'settings' }, partial: true });
};
onSaveDashboard = () => {
appEvents.emit(CoreEvents.showModalReact, {
component: SaveDashboardModalProxy,
props: { dashboard: this.props.dashboard },
});
};
onChangeTab = (tab: PanelEditorTab) => { onChangeTab = (tab: PanelEditorTab) => {
this.props.updateLocation({ query: { tab: tab.id }, partial: true }); this.props.updateLocation({ query: { tab: tab.id }, partial: true });
}; };
...@@ -107,8 +120,14 @@ export class PanelEditorUnconnected extends PureComponent<Props> { ...@@ -107,8 +120,14 @@ export class PanelEditorUnconnected extends PureComponent<Props> {
this.forceUpdate(); this.forceUpdate();
}; };
onDragFinished = (pane: Pane, size: number) => { onDragFinished = (pane: Pane, size?: number) => {
document.body.style.cursor = 'auto'; document.body.style.cursor = 'auto';
// When the drag handle is just clicked size is undefined
if (!size) {
return;
}
const targetPane = pane === Pane.Top ? 'topPaneSize' : 'rightPaneSize'; const targetPane = pane === Pane.Top ? 'topPaneSize' : 'rightPaneSize';
const { updatePanelEditorUIState } = this.props; const { updatePanelEditorUIState } = this.props;
updatePanelEditorUIState({ updatePanelEditorUIState({
...@@ -228,12 +247,27 @@ export class PanelEditorUnconnected extends PureComponent<Props> { ...@@ -228,12 +247,27 @@ export class PanelEditorUnconnected extends PureComponent<Props> {
</div> </div>
<div className={styles.toolbarLeft}> <div className={styles.toolbarLeft}>
<div className={styles.toolbarItem}> <div className={styles.toolbarItem}>
<Button onClick={this.onDiscard} variant="secondary"> <Button
Discard changes icon="cog"
onClick={this.onOpenDashboardSettings}
variant="secondary"
title="Open dashboad settings"
/>
</div>
<div className={styles.toolbarItem}>
<Button onClick={this.onDiscard} variant="secondary" title="Undo all changes">
Discard
</Button> </Button>
</div> </div>
<div className={styles.toolbarItem}> <div className={styles.toolbarItem}>
<Button onClick={this.onPanelExit}>Apply</Button> <Button onClick={this.onSaveDashboard} variant="secondary" title="Apply changes and save dashboard">
Save
</Button>
</div>
<div className={styles.toolbarItem}>
<Button onClick={this.onPanelExit} title="Apply changes and go back to dashboard">
Apply
</Button>
</div> </div>
</div> </div>
</div> </div>
...@@ -335,7 +369,7 @@ enum Pane { ...@@ -335,7 +369,7 @@ enum Pane {
/* /*
* Styles * Styles
*/ */
const getStyles = stylesFactory((theme: GrafanaTheme, props: Props) => { export const getStyles = stylesFactory((theme: GrafanaTheme, props: Props) => {
const { uiState } = props; const { uiState } = props;
const handleColor = theme.palette.blue95; const handleColor = theme.palette.blue95;
const paneSpaceing = theme.spacing.md; const paneSpaceing = theme.spacing.md;
...@@ -361,7 +395,7 @@ const getStyles = stylesFactory((theme: GrafanaTheme, props: Props) => { ...@@ -361,7 +395,7 @@ const getStyles = stylesFactory((theme: GrafanaTheme, props: Props) => {
width: 100%; width: 100%;
height: 100%; height: 100%;
position: fixed; position: fixed;
z-index: ${theme.zIndex.modal}; z-index: ${theme.zIndex.sidemenu};
top: 0; top: 0;
left: 0; left: 0;
right: 0; right: 0;
......
...@@ -73,6 +73,7 @@ const pluginsSlice = createSlice({ ...@@ -73,6 +73,7 @@ const pluginsSlice = createSlice({
state.querySubscription = action.payload.querySubscription; state.querySubscription = action.payload.querySubscription;
state.initDone = true; state.initDone = true;
state.isOpen = true; state.isOpen = true;
state.shouldDiscardChanges = false;
}, },
setEditorPanelData: (state, action: PayloadAction<PanelData>) => { setEditorPanelData: (state, action: PayloadAction<PanelData>) => {
state.getData = () => action.payload; state.getData = () => action.payload;
......
...@@ -80,6 +80,7 @@ const getStyles = stylesFactory((theme: GrafanaTheme) => { ...@@ -80,6 +80,7 @@ const getStyles = stylesFactory((theme: GrafanaTheme) => {
background: ${theme.isLight ? theme.palette.gray7 : theme.palette.black}; background: ${theme.isLight ? theme.palette.gray7 : theme.palette.black};
padding: ${theme.spacing.sm} 0 ${theme.spacing.sm} ${theme.spacing.md}; padding: ${theme.spacing.sm} 0 ${theme.spacing.sm} ${theme.spacing.md};
height: 400px; height: 400px;
width: 100%;
`, `,
}; };
}); });
...@@ -71,6 +71,20 @@ describe('DashboardModel', () => { ...@@ -71,6 +71,20 @@ describe('DashboardModel', () => {
expect(panels.length).toBe(1); expect(panels.length).toBe(1);
}); });
it('should save model in edit mode', () => {
const model = new DashboardModel({});
model.addPanel({ type: 'graph' });
const panel = model.initEditPanel(model.panels[0]);
panel.title = 'updated';
const saveModel = model.getSaveModelClone();
const savedPanel = saveModel.panels[0];
expect(savedPanel.title).toBe('updated');
expect(savedPanel.id).toBe(model.panels[0].id);
});
}); });
describe('row and panel manipulation', () => { describe('row and panel manipulation', () => {
......
...@@ -180,10 +180,20 @@ export class DashboardModel { ...@@ -180,10 +180,20 @@ export class DashboardModel {
} }
// get panel save models // get panel save models
copy.panels = _.chain(this.panels) copy.panels = this.panels
.filter((panel: PanelModel) => panel.type !== 'add-panel') .filter((panel: PanelModel) => panel.type !== 'add-panel')
.map((panel: PanelModel) => panel.getSaveModel()) .map((panel: PanelModel) => {
.value(); // If we save while editing we should include the panel in edit mode instead of the
// unmodified source panel
if (this.panelInEdit && this.panelInEdit.editSourceId === panel.id) {
const saveModel = this.panelInEdit.getSaveModel();
// while editing a panel we modify its id, need to restore it here
saveModel.id = this.panelInEdit.editSourceId;
return saveModel;
}
return panel.getSaveModel();
});
// sort by keys // sort by keys
copy = sortByKeys(copy); copy = sortByKeys(copy);
......
...@@ -15,8 +15,9 @@ ...@@ -15,8 +15,9 @@
margin-left: 0; margin-left: 0;
} }
&--shadow { &--edit {
box-shadow: $side-menu-shadow; background: $panel-bg;
border-bottom: $panel-border;
} }
} }
...@@ -43,11 +44,11 @@ ...@@ -43,11 +44,11 @@
text-overflow: ellipsis; text-overflow: ellipsis;
overflow: hidden; overflow: hidden;
white-space: nowrap; white-space: nowrap;
display: block; display: flex;
align-items: center;
margin: 0; margin: 0;
font-size: $font-size-lg; font-size: $font-size-lg;
min-height: $navbarHeight; min-height: $navbarHeight;
line-height: $navbarHeight;
.gicon { .gicon {
top: -2px; top: -2px;
...@@ -204,5 +205,5 @@ i.navbar-page-btn__search { ...@@ -204,5 +205,5 @@ i.navbar-page-btn__search {
display: flex; display: flex;
height: $navbarHeight; height: $navbarHeight;
align-items: center; align-items: center;
padding-right: 13px; padding-right: 16px;
} }
...@@ -30,6 +30,10 @@ ...@@ -30,6 +30,10 @@
font-size: 75%; font-size: 75%;
padding-left: 8px; padding-left: 8px;
} }
.gf-form {
margin-bottom: 0;
}
} }
.variable-value-link { .variable-value-link {
......
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