mirror of
https://github.com/grafana/grafana.git
synced 2025-02-10 23:55:47 -06:00
NewPanelEditor: Save dashboard from edit mode now works, and other fixes (#23668)
This commit is contained in:
parent
165a0471ad
commit
f30074c70a
@ -67,13 +67,14 @@ const getPropertiesForVariant = (theme: GrafanaTheme, variant: ButtonVariant) =>
|
||||
export interface StyleProps {
|
||||
theme: GrafanaTheme;
|
||||
size: ComponentSize;
|
||||
icon?: IconName;
|
||||
variant: ButtonVariant;
|
||||
textAndIcon?: boolean;
|
||||
hasIcon: boolean;
|
||||
hasText: boolean;
|
||||
}
|
||||
|
||||
export const getButtonStyles = stylesFactory(({ theme, size, variant, icon }: StyleProps) => {
|
||||
const { padding, fontSize, height } = getPropertiesForButtonSize(theme, size, icon);
|
||||
export const getButtonStyles = stylesFactory((props: StyleProps) => {
|
||||
const { theme, variant } = props;
|
||||
const { padding, fontSize, height } = getPropertiesForButtonSize(props);
|
||||
const { background, borderColor, variantStyles } = getPropertiesForVariant(theme, variant);
|
||||
|
||||
return {
|
||||
@ -105,9 +106,6 @@ export const getButtonStyles = stylesFactory(({ theme, size, variant, icon }: St
|
||||
${variantStyles}
|
||||
`
|
||||
),
|
||||
buttonWithIcon: css`
|
||||
padding-left: ${theme.spacing.sm};
|
||||
`,
|
||||
// used for buttons with icon only
|
||||
iconButton: css`
|
||||
padding-right: 0;
|
||||
@ -139,7 +137,8 @@ export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
theme,
|
||||
size: otherProps.size || 'md',
|
||||
variant: variant || 'primary',
|
||||
icon,
|
||||
hasText: children !== undefined,
|
||||
hasIcon: icon !== undefined,
|
||||
});
|
||||
|
||||
return (
|
||||
@ -162,7 +161,8 @@ export const LinkButton = React.forwardRef<HTMLAnchorElement, ButtonLinkProps>(
|
||||
theme,
|
||||
size: otherProps.size || 'md',
|
||||
variant: variant || 'primary',
|
||||
icon,
|
||||
hasText: children !== undefined,
|
||||
hasIcon: icon !== undefined,
|
||||
});
|
||||
|
||||
return (
|
||||
|
@ -17,10 +17,16 @@ export interface RadioButtonProps {
|
||||
}
|
||||
|
||||
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 c = theme.palette;
|
||||
|
||||
const textColor = theme.colors.textSemiWeak;
|
||||
const textColorHover = theme.colors.text;
|
||||
const textColorActive = theme.isLight ? c.blue77 : c.blue95;
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { css } from 'emotion';
|
||||
import { GrafanaTheme } from '@grafana/data';
|
||||
import { ComponentSize } from '../../types/size';
|
||||
import { IconName } from '../../types';
|
||||
import { StyleProps } from '../Button';
|
||||
|
||||
export const getFocusCss = (theme: GrafanaTheme) => `
|
||||
outline: 2px dotted transparent;
|
||||
@ -91,8 +90,9 @@ export const inputSizesPixels = (size: string) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const getPropertiesForButtonSize = (theme: GrafanaTheme, size: ComponentSize, icon?: IconName) => {
|
||||
const { spacing, typography, height } = theme;
|
||||
export const getPropertiesForButtonSize = (props: StyleProps) => {
|
||||
const { hasText, hasIcon, size } = props;
|
||||
const { spacing, typography, height } = props.theme;
|
||||
|
||||
switch (size) {
|
||||
case 'sm':
|
||||
@ -104,14 +104,14 @@ export const getPropertiesForButtonSize = (theme: GrafanaTheme, size: ComponentS
|
||||
|
||||
case 'lg':
|
||||
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,
|
||||
height: height.lg,
|
||||
};
|
||||
case 'md':
|
||||
default:
|
||||
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,
|
||||
height: height.md,
|
||||
};
|
||||
|
@ -19,6 +19,8 @@ export const getFormStyles = stylesFactory(
|
||||
theme,
|
||||
variant: options.variant,
|
||||
size: options.size,
|
||||
hasIcon: false,
|
||||
hasText: true,
|
||||
}),
|
||||
input: getInputStyles({ theme, invalid: options.invalid }),
|
||||
switch: getSwitchStyles(theme),
|
||||
|
@ -32,6 +32,7 @@ export const getModalStyles = stylesFactory((theme: GrafanaTheme) => {
|
||||
opacity: 0.7;
|
||||
`,
|
||||
modalHeader: css`
|
||||
label: modalHeader;
|
||||
background: ${theme.colors.bg2};
|
||||
border-bottom: 1px solid ${theme.colors.pageHeaderBorder};
|
||||
display: flex;
|
||||
@ -42,6 +43,7 @@ export const getModalStyles = stylesFactory((theme: GrafanaTheme) => {
|
||||
margin: 0 ${theme.spacing.md};
|
||||
display: flex;
|
||||
align-items: center;
|
||||
line-height: 42px;
|
||||
`,
|
||||
modalHeaderIcon: css`
|
||||
margin-right: ${theme.spacing.md};
|
||||
|
@ -48,7 +48,7 @@ export class DashboardSettings extends PureComponent<Props> {
|
||||
|
||||
return (
|
||||
<div className="dashboard-settings">
|
||||
<div className="navbar navbar--shadow">
|
||||
<div className="navbar navbar--edit">
|
||||
<div className="navbar-edit">
|
||||
<BackButton surface="body" onClick={this.onClose} />
|
||||
</div>
|
||||
|
@ -17,7 +17,7 @@ import { Unsubscribable } from 'rxjs';
|
||||
import { DisplayMode, displayModes, PanelEditorTab } from './types';
|
||||
import { PanelEditorTabs } from './PanelEditorTabs';
|
||||
import { DashNavTimeControls } from '../DashNav/DashNavTimeControls';
|
||||
import { LocationState } from 'app/types';
|
||||
import { LocationState, CoreEvents } from 'app/types';
|
||||
import { calculatePanelSize } from './utils';
|
||||
import { initPanelEditor, panelEditorCleanUp, updatePanelEditorUIState } from './state/actions';
|
||||
import { PanelEditorUIState, setDiscardChanges } from './state/reducers';
|
||||
@ -29,6 +29,8 @@ import { VariableModel } from 'app/features/templating/types';
|
||||
import { getVariables } from 'app/features/variables/state/selectors';
|
||||
import { SubMenuItems } from 'app/features/dashboard/components/SubMenu/SubMenuItems';
|
||||
import { BackButton } from 'app/core/components/BackButton/BackButton';
|
||||
import { appEvents } from 'app/core/core';
|
||||
import { SaveDashboardModalProxy } from '../SaveDashboard/SaveDashboardModalProxy';
|
||||
|
||||
interface OwnProps {
|
||||
dashboard: DashboardModel;
|
||||
@ -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) => {
|
||||
this.props.updateLocation({ query: { tab: tab.id }, partial: true });
|
||||
};
|
||||
@ -107,8 +120,14 @@ export class PanelEditorUnconnected extends PureComponent<Props> {
|
||||
this.forceUpdate();
|
||||
};
|
||||
|
||||
onDragFinished = (pane: Pane, size: number) => {
|
||||
onDragFinished = (pane: Pane, size?: number) => {
|
||||
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 { updatePanelEditorUIState } = this.props;
|
||||
updatePanelEditorUIState({
|
||||
@ -228,12 +247,27 @@ export class PanelEditorUnconnected extends PureComponent<Props> {
|
||||
</div>
|
||||
<div className={styles.toolbarLeft}>
|
||||
<div className={styles.toolbarItem}>
|
||||
<Button onClick={this.onDiscard} variant="secondary">
|
||||
Discard changes
|
||||
<Button
|
||||
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>
|
||||
</div>
|
||||
<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>
|
||||
@ -335,7 +369,7 @@ enum Pane {
|
||||
/*
|
||||
* Styles
|
||||
*/
|
||||
const getStyles = stylesFactory((theme: GrafanaTheme, props: Props) => {
|
||||
export const getStyles = stylesFactory((theme: GrafanaTheme, props: Props) => {
|
||||
const { uiState } = props;
|
||||
const handleColor = theme.palette.blue95;
|
||||
const paneSpaceing = theme.spacing.md;
|
||||
@ -361,7 +395,7 @@ const getStyles = stylesFactory((theme: GrafanaTheme, props: Props) => {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: fixed;
|
||||
z-index: ${theme.zIndex.modal};
|
||||
z-index: ${theme.zIndex.sidemenu};
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
|
@ -73,6 +73,7 @@ const pluginsSlice = createSlice({
|
||||
state.querySubscription = action.payload.querySubscription;
|
||||
state.initDone = true;
|
||||
state.isOpen = true;
|
||||
state.shouldDiscardChanges = false;
|
||||
},
|
||||
setEditorPanelData: (state, action: PayloadAction<PanelData>) => {
|
||||
state.getData = () => action.payload;
|
||||
|
@ -80,6 +80,7 @@ const getStyles = stylesFactory((theme: GrafanaTheme) => {
|
||||
background: ${theme.isLight ? theme.palette.gray7 : theme.palette.black};
|
||||
padding: ${theme.spacing.sm} 0 ${theme.spacing.sm} ${theme.spacing.md};
|
||||
height: 400px;
|
||||
width: 100%;
|
||||
`,
|
||||
};
|
||||
});
|
||||
|
@ -71,6 +71,20 @@ describe('DashboardModel', () => {
|
||||
|
||||
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', () => {
|
||||
|
@ -180,10 +180,20 @@ export class DashboardModel {
|
||||
}
|
||||
|
||||
// get panel save models
|
||||
copy.panels = _.chain(this.panels)
|
||||
copy.panels = this.panels
|
||||
.filter((panel: PanelModel) => panel.type !== 'add-panel')
|
||||
.map((panel: PanelModel) => panel.getSaveModel())
|
||||
.value();
|
||||
.map((panel: PanelModel) => {
|
||||
// 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
|
||||
copy = sortByKeys(copy);
|
||||
|
@ -15,8 +15,9 @@
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
&--shadow {
|
||||
box-shadow: $side-menu-shadow;
|
||||
&--edit {
|
||||
background: $panel-bg;
|
||||
border-bottom: $panel-border;
|
||||
}
|
||||
}
|
||||
|
||||
@ -43,11 +44,11 @@
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
display: block;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 0;
|
||||
font-size: $font-size-lg;
|
||||
min-height: $navbarHeight;
|
||||
line-height: $navbarHeight;
|
||||
|
||||
.gicon {
|
||||
top: -2px;
|
||||
@ -204,5 +205,5 @@ i.navbar-page-btn__search {
|
||||
display: flex;
|
||||
height: $navbarHeight;
|
||||
align-items: center;
|
||||
padding-right: 13px;
|
||||
padding-right: 16px;
|
||||
}
|
||||
|
@ -30,6 +30,10 @@
|
||||
font-size: 75%;
|
||||
padding-left: 8px;
|
||||
}
|
||||
|
||||
.gf-form {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.variable-value-link {
|
||||
|
Loading…
Reference in New Issue
Block a user