mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
NewPanelEditor: UI update of add panel widget (#23715)
* Small update proposal to add panel widget * Fix smoketest * Minor tweaks * update snaps god damn it
This commit is contained in:
parent
7e4292508f
commit
40114e8c07
@ -13,7 +13,7 @@ e2e.scenario({
|
|||||||
e2e.flows.openDashboard(lastAddedDashboardUid);
|
e2e.flows.openDashboard(lastAddedDashboardUid);
|
||||||
});
|
});
|
||||||
e2e.pages.Dashboard.Toolbar.toolbarItems('Add panel').click();
|
e2e.pages.Dashboard.Toolbar.toolbarItems('Add panel').click();
|
||||||
e2e.pages.AddDashboard.ctaButtons('Add Query').click();
|
e2e.pages.AddDashboard.addNewPanel().click();
|
||||||
|
|
||||||
e2e.components.DataSource.TestData.QueryTab.scenarioSelect().select('CSV Metric Values');
|
e2e.components.DataSource.TestData.QueryTab.scenarioSelect().select('CSV Metric Values');
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ export const addPanel = (config?: Partial<AddPanelConfig>) => {
|
|||||||
getScenarioContext().then(({ lastAddedDashboardUid }) => {
|
getScenarioContext().then(({ lastAddedDashboardUid }) => {
|
||||||
e2e.flows.openDashboard(lastAddedDashboardUid);
|
e2e.flows.openDashboard(lastAddedDashboardUid);
|
||||||
e2e.pages.Dashboard.Toolbar.toolbarItems('Add panel').click();
|
e2e.pages.Dashboard.Toolbar.toolbarItems('Add panel').click();
|
||||||
e2e.pages.AddDashboard.ctaButtons('Add Query').click();
|
e2e.pages.AddDashboard.addNewPanel().click();
|
||||||
e2e()
|
e2e()
|
||||||
.get('.ds-picker')
|
.get('.ds-picker')
|
||||||
.click()
|
.click()
|
||||||
|
@ -3,6 +3,6 @@ import { pageFactory } from '../support';
|
|||||||
export const AddDashboard = pageFactory({
|
export const AddDashboard = pageFactory({
|
||||||
url: '/dashboard/new',
|
url: '/dashboard/new',
|
||||||
selectors: {
|
selectors: {
|
||||||
ctaButtons: (text: string) => `Add Panel Widget CTA Button ${text}`,
|
addNewPanel: 'Add new panel',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -8,6 +8,7 @@ const setup = (propOverrides?: object) => {
|
|||||||
dashboard: {} as DashboardModel,
|
dashboard: {} as DashboardModel,
|
||||||
panel: {} as PanelModel,
|
panel: {} as PanelModel,
|
||||||
addPanel: jest.fn() as any,
|
addPanel: jest.fn() as any,
|
||||||
|
updateLocation: jest.fn() as any,
|
||||||
};
|
};
|
||||||
|
|
||||||
Object.assign(props, propOverrides);
|
Object.assign(props, propOverrides);
|
||||||
|
@ -1,20 +1,22 @@
|
|||||||
// Libraries
|
// Libraries
|
||||||
import React from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { LocationUpdate } from '@grafana/runtime';
|
import { LocationUpdate } from '@grafana/runtime';
|
||||||
import { Icon, IconName, IconButton } from '@grafana/ui';
|
import { Button, HorizontalGroup, IconButton, stylesFactory, useTheme } from '@grafana/ui';
|
||||||
import { e2e } from '@grafana/e2e';
|
|
||||||
import { connect, MapDispatchToProps } from 'react-redux';
|
import { connect, MapDispatchToProps } from 'react-redux';
|
||||||
// Utils
|
// Utils
|
||||||
import config from 'app/core/config';
|
import config from 'app/core/config';
|
||||||
import store from 'app/core/store';
|
import store from 'app/core/store';
|
||||||
// Store
|
// Store
|
||||||
import { store as reduxStore } from 'app/store/store';
|
|
||||||
import { updateLocation } from 'app/core/actions';
|
import { updateLocation } from 'app/core/actions';
|
||||||
import { addPanel } from 'app/features/dashboard/state/reducers';
|
import { addPanel } from 'app/features/dashboard/state/reducers';
|
||||||
// Types
|
// Types
|
||||||
import { DashboardModel, PanelModel } from '../../state';
|
import { DashboardModel, PanelModel } from '../../state';
|
||||||
import { LS_PANEL_COPY_KEY } from 'app/core/constants';
|
import { LS_PANEL_COPY_KEY } from 'app/core/constants';
|
||||||
|
import { e2e } from '@grafana/e2e';
|
||||||
|
import { css, cx, keyframes } from 'emotion';
|
||||||
|
import { GrafanaTheme } from '@grafana/data';
|
||||||
|
import tinycolor from 'tinycolor2';
|
||||||
|
|
||||||
export type PanelPluginInfo = { id: any; defaults: { gridPos: { w: any; h: any }; title: any } };
|
export type PanelPluginInfo = { id: any; defaults: { gridPos: { w: any; h: any }; title: any } };
|
||||||
|
|
||||||
@ -25,25 +27,12 @@ export interface OwnProps {
|
|||||||
|
|
||||||
export interface DispatchProps {
|
export interface DispatchProps {
|
||||||
addPanel: typeof addPanel;
|
addPanel: typeof addPanel;
|
||||||
|
updateLocation: typeof updateLocation;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Props = OwnProps & DispatchProps;
|
export type Props = OwnProps & DispatchProps;
|
||||||
|
|
||||||
export interface State {
|
const getCopiedPanelPlugins = () => {
|
||||||
copiedPanelPlugins: any[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export class AddPanelWidgetUnconnected extends React.Component<Props, State> {
|
|
||||||
constructor(props: Props) {
|
|
||||||
super(props);
|
|
||||||
this.handleCloseAddPanel = this.handleCloseAddPanel.bind(this);
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
copiedPanelPlugins: this.getCopiedPanelPlugins(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
getCopiedPanelPlugins() {
|
|
||||||
const panels = _.chain(config.panels)
|
const panels = _.chain(config.panels)
|
||||||
.filter({ hideFromList: false })
|
.filter({ hideFromList: false })
|
||||||
.map(item => item)
|
.map(item => item)
|
||||||
@ -64,16 +53,18 @@ export class AddPanelWidgetUnconnected extends React.Component<Props, State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return _.sortBy(copiedPanels, 'sort');
|
return _.sortBy(copiedPanels, 'sort');
|
||||||
}
|
};
|
||||||
|
|
||||||
handleCloseAddPanel(evt: any) {
|
export const AddPanelWidgetUnconnected: React.FC<Props> = ({ panel, dashboard, updateLocation, addPanel }) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
|
const onCancelAddPanel = (evt: any) => {
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
this.props.dashboard.removePanel(this.props.panel);
|
dashboard.removePanel(panel);
|
||||||
}
|
};
|
||||||
|
|
||||||
onCreateNewPanel = (tab = 'queries') => {
|
const onCreateNewPanel = () => {
|
||||||
const dashboard = this.props.dashboard;
|
const { gridPos } = panel;
|
||||||
const { gridPos } = this.props.panel;
|
|
||||||
|
|
||||||
const newPanel: any = {
|
const newPanel: any = {
|
||||||
type: 'graph',
|
type: 'graph',
|
||||||
@ -82,7 +73,7 @@ export class AddPanelWidgetUnconnected extends React.Component<Props, State> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
dashboard.addPanel(newPanel);
|
dashboard.addPanel(newPanel);
|
||||||
dashboard.removePanel(this.props.panel);
|
dashboard.removePanel(panel);
|
||||||
|
|
||||||
const location: LocationUpdate = {
|
const location: LocationUpdate = {
|
||||||
query: {
|
query: {
|
||||||
@ -91,16 +82,11 @@ export class AddPanelWidgetUnconnected extends React.Component<Props, State> {
|
|||||||
partial: true,
|
partial: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (tab === 'visualize') {
|
updateLocation(location);
|
||||||
location.query.tab = 'visualize';
|
|
||||||
}
|
|
||||||
|
|
||||||
reduxStore.dispatch(updateLocation(location));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
onPasteCopiedPanel = (panelPluginInfo: PanelPluginInfo) => {
|
const onPasteCopiedPanel = (panelPluginInfo: PanelPluginInfo) => {
|
||||||
const dashboard = this.props.dashboard;
|
const { gridPos } = panel;
|
||||||
const { gridPos } = this.props.panel;
|
|
||||||
|
|
||||||
const newPanel: any = {
|
const newPanel: any = {
|
||||||
type: panelPluginInfo.id,
|
type: panelPluginInfo.id,
|
||||||
@ -121,12 +107,10 @@ export class AddPanelWidgetUnconnected extends React.Component<Props, State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dashboard.addPanel(newPanel);
|
dashboard.addPanel(newPanel);
|
||||||
dashboard.removePanel(this.props.panel);
|
dashboard.removePanel(panel);
|
||||||
};
|
};
|
||||||
|
|
||||||
onCreateNewRow = () => {
|
const onCreateNewRow = () => {
|
||||||
const dashboard = this.props.dashboard;
|
|
||||||
|
|
||||||
const newRow: any = {
|
const newRow: any = {
|
||||||
type: 'row',
|
type: 'row',
|
||||||
title: 'Row title',
|
title: 'Row title',
|
||||||
@ -134,69 +118,130 @@ export class AddPanelWidgetUnconnected extends React.Component<Props, State> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
dashboard.addPanel(newRow);
|
dashboard.addPanel(newRow);
|
||||||
dashboard.removePanel(this.props.panel);
|
dashboard.removePanel(panel);
|
||||||
};
|
};
|
||||||
|
|
||||||
renderOptionLink = (icon: IconName, text: string, onClick: any) => {
|
const styles = getStyles(theme);
|
||||||
return (
|
return (
|
||||||
|
<div className={cx('panel-container', styles.wrapper)}>
|
||||||
|
<AddPanelWidgetHandle onCancel={onCancelAddPanel} />
|
||||||
|
<div className={styles.actionsWrapper}>
|
||||||
|
<AddPanelWidgetCreate onCreate={onCreateNewPanel} onPasteCopiedPanel={onPasteCopiedPanel} />
|
||||||
<div>
|
<div>
|
||||||
<a
|
<HorizontalGroup justify="center">
|
||||||
href="#"
|
<Button onClick={onCreateNewRow} variant="secondary" size="sm">
|
||||||
onClick={onClick}
|
Convert to row
|
||||||
className="add-panel-widget__link btn btn-inverse"
|
</Button>
|
||||||
aria-label={e2e.pages.AddDashboard.selectors.ctaButtons(text)}
|
</HorizontalGroup>
|
||||||
>
|
</div>
|
||||||
<div className="add-panel-widget__icon">
|
|
||||||
<Icon name={icon} size="xl" />
|
|
||||||
</div>
|
</div>
|
||||||
<span>{text}</span>
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
const mapDispatchToProps: MapDispatchToProps<DispatchProps, OwnProps> = { addPanel, updateLocation };
|
||||||
const { copiedPanelPlugins } = this.state;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="panel-container add-panel-widget-container">
|
|
||||||
<div className="add-panel-widget">
|
|
||||||
<div className="add-panel-widget__header grid-drag-handle">
|
|
||||||
<Icon name="panel-add" type="mono" size="xl" style={{ margin: '4px', marginRight: '8px' }} />
|
|
||||||
<span className="add-panel-widget__title">New Panel</span>
|
|
||||||
<div className="flex-grow-1"></div>
|
|
||||||
<IconButton
|
|
||||||
name="times"
|
|
||||||
onClick={this.handleCloseAddPanel}
|
|
||||||
surface="header"
|
|
||||||
className="add-panel-widget__close"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="add-panel-widget__btn-container">
|
|
||||||
<div className="add-panel-widget__create">
|
|
||||||
{this.renderOptionLink('database', 'Add Query', this.onCreateNewPanel)}
|
|
||||||
{this.renderOptionLink('chart-line', 'Choose Visualization', () => this.onCreateNewPanel('visualize'))}
|
|
||||||
</div>
|
|
||||||
<div className="add-panel-widget__actions">
|
|
||||||
<button className="btn btn-inverse add-panel-widget__action" onClick={this.onCreateNewRow}>
|
|
||||||
Convert to row
|
|
||||||
</button>
|
|
||||||
{copiedPanelPlugins.length === 1 && (
|
|
||||||
<button
|
|
||||||
className="btn btn-inverse add-panel-widget__action"
|
|
||||||
onClick={() => this.onPasteCopiedPanel(copiedPanelPlugins[0])}
|
|
||||||
>
|
|
||||||
Paste copied panel
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const mapDispatchToProps: MapDispatchToProps<DispatchProps, OwnProps> = { addPanel };
|
|
||||||
|
|
||||||
export const AddPanelWidget = connect(null, mapDispatchToProps)(AddPanelWidgetUnconnected);
|
export const AddPanelWidget = connect(null, mapDispatchToProps)(AddPanelWidgetUnconnected);
|
||||||
|
|
||||||
|
interface AddPanelWidgetHandleProps {
|
||||||
|
onCancel: (e: React.MouseEvent<HTMLButtonElement>) => void;
|
||||||
|
}
|
||||||
|
const AddPanelWidgetHandle: React.FC<AddPanelWidgetHandleProps> = ({ onCancel }) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const styles = getAddPanelWigetHandleStyles(theme);
|
||||||
|
return (
|
||||||
|
<div className={cx(styles.handle, 'grid-drag-handle')}>
|
||||||
|
<IconButton name="times" onClick={onCancel} surface="header" className="add-panel-widget__close" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface AddPanelWidgetCreateProps {
|
||||||
|
onCreate: () => void;
|
||||||
|
onPasteCopiedPanel: (panelPluginInfo: PanelPluginInfo) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const AddPanelWidgetCreate: React.FC<AddPanelWidgetCreateProps> = ({ onCreate, onPasteCopiedPanel }) => {
|
||||||
|
const copiedPanelPlugins = useMemo(() => getCopiedPanelPlugins(), []);
|
||||||
|
const theme = useTheme();
|
||||||
|
const styles = getAddPanelWidgetCreateStyles(theme);
|
||||||
|
return (
|
||||||
|
<div className={styles.wrapper}>
|
||||||
|
<HorizontalGroup>
|
||||||
|
<Button icon="plus" size="md" onClick={onCreate} aria-label={e2e.pages.AddDashboard.selectors.addNewPanel}>
|
||||||
|
Add new panel
|
||||||
|
</Button>
|
||||||
|
{copiedPanelPlugins.length === 1 && (
|
||||||
|
<Button variant="secondary" size="md" onClick={() => onPasteCopiedPanel(copiedPanelPlugins[0])}>
|
||||||
|
Paste copied panel
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</HorizontalGroup>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getStyles = stylesFactory((theme: GrafanaTheme) => {
|
||||||
|
const pulsate = keyframes`
|
||||||
|
0% {box-shadow: 0 0 0 2px ${theme.colors.bodyBg}, 0 0 0px 4px ${theme.colors.formFocusOutline};}
|
||||||
|
50% {box-shadow: 0 0 0 2px ${theme.colors.bodyBg}, 0 0 0px 4px ${tinycolor(theme.colors.formFocusOutline)
|
||||||
|
.darken(20)
|
||||||
|
.toHexString()};}
|
||||||
|
100% {box-shadow: 0 0 0 2px ${theme.colors.bodyBg}, 0 0 0px 4px ${theme.colors.formFocusOutline};}
|
||||||
|
`;
|
||||||
|
return {
|
||||||
|
wrapper: css`
|
||||||
|
overflow: hidden;
|
||||||
|
outline: 2px dotted transparent;
|
||||||
|
outline-offset: 2px;
|
||||||
|
box-shadow: 0 0 0 2px black, 0 0 0px 4px #1f60c4;
|
||||||
|
animation: ${pulsate} 2s ease infinite;
|
||||||
|
`,
|
||||||
|
actionsWrapper: css`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
height: 100%;
|
||||||
|
justify-content: center;
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const getAddPanelWigetHandleStyles = stylesFactory((theme: GrafanaTheme) => {
|
||||||
|
return {
|
||||||
|
handle: css`
|
||||||
|
position: absolute;
|
||||||
|
cursor: grab;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
height: 26px;
|
||||||
|
padding: 0 ${theme.spacing.xs};
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-items: center;
|
||||||
|
transition: background-color 0.1s ease-in-out;
|
||||||
|
&:hover {
|
||||||
|
background: ${theme.colors.bg2};
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const getAddPanelWidgetCreateStyles = stylesFactory((theme: GrafanaTheme) => {
|
||||||
|
return {
|
||||||
|
wrapper: css`
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: ${theme.spacing.lg};
|
||||||
|
&:hover {
|
||||||
|
background: ${theme.colors.bg2};
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
icon: css`
|
||||||
|
color: ${theme.colors.textWeak};
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
@ -2,97 +2,30 @@
|
|||||||
|
|
||||||
exports[`Render should render component 1`] = `
|
exports[`Render should render component 1`] = `
|
||||||
<div
|
<div
|
||||||
className="panel-container add-panel-widget-container"
|
className="panel-container css-1sl7nld"
|
||||||
>
|
>
|
||||||
<div
|
<AddPanelWidgetHandle
|
||||||
className="add-panel-widget"
|
onCancel={[Function]}
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="add-panel-widget__header grid-drag-handle"
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
name="panel-add"
|
|
||||||
size="xl"
|
|
||||||
style={
|
|
||||||
Object {
|
|
||||||
"margin": "4px",
|
|
||||||
"marginRight": "8px",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
type="mono"
|
|
||||||
/>
|
/>
|
||||||
<span
|
|
||||||
className="add-panel-widget__title"
|
|
||||||
>
|
|
||||||
New Panel
|
|
||||||
</span>
|
|
||||||
<div
|
<div
|
||||||
className="flex-grow-1"
|
className="css-1wtsk46"
|
||||||
|
>
|
||||||
|
<AddPanelWidgetCreate
|
||||||
|
onCreate={[Function]}
|
||||||
|
onPasteCopiedPanel={[Function]}
|
||||||
/>
|
/>
|
||||||
<IconButton
|
|
||||||
className="add-panel-widget__close"
|
|
||||||
name="times"
|
|
||||||
onClick={[Function]}
|
|
||||||
surface="header"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="add-panel-widget__btn-container"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="add-panel-widget__create"
|
|
||||||
>
|
|
||||||
<div>
|
<div>
|
||||||
<a
|
<Component
|
||||||
aria-label="Add Panel Widget CTA Button Add Query"
|
justify="center"
|
||||||
className="add-panel-widget__link btn btn-inverse"
|
>
|
||||||
href="#"
|
<Button
|
||||||
onClick={[Function]}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="add-panel-widget__icon"
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
name="database"
|
|
||||||
size="xl"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<span>
|
|
||||||
Add Query
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<a
|
|
||||||
aria-label="Add Panel Widget CTA Button Choose Visualization"
|
|
||||||
className="add-panel-widget__link btn btn-inverse"
|
|
||||||
href="#"
|
|
||||||
onClick={[Function]}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="add-panel-widget__icon"
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
name="chart-line"
|
|
||||||
size="xl"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<span>
|
|
||||||
Choose Visualization
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="add-panel-widget__actions"
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
className="btn btn-inverse add-panel-widget__action"
|
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
|
size="sm"
|
||||||
|
variant="secondary"
|
||||||
>
|
>
|
||||||
Convert to row
|
Convert to row
|
||||||
</button>
|
</Button>
|
||||||
</div>
|
</Component>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
Reference in New Issue
Block a user