mirror of
https://github.com/grafana/grafana.git
synced 2025-01-27 16:57:14 -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.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');
|
||||
|
||||
|
@ -19,7 +19,7 @@ export const addPanel = (config?: Partial<AddPanelConfig>) => {
|
||||
getScenarioContext().then(({ lastAddedDashboardUid }) => {
|
||||
e2e.flows.openDashboard(lastAddedDashboardUid);
|
||||
e2e.pages.Dashboard.Toolbar.toolbarItems('Add panel').click();
|
||||
e2e.pages.AddDashboard.ctaButtons('Add Query').click();
|
||||
e2e.pages.AddDashboard.addNewPanel().click();
|
||||
e2e()
|
||||
.get('.ds-picker')
|
||||
.click()
|
||||
|
@ -3,6 +3,6 @@ import { pageFactory } from '../support';
|
||||
export const AddDashboard = pageFactory({
|
||||
url: '/dashboard/new',
|
||||
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,
|
||||
panel: {} as PanelModel,
|
||||
addPanel: jest.fn() as any,
|
||||
updateLocation: jest.fn() as any,
|
||||
};
|
||||
|
||||
Object.assign(props, propOverrides);
|
||||
|
@ -1,20 +1,22 @@
|
||||
// Libraries
|
||||
import React from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import _ from 'lodash';
|
||||
import { LocationUpdate } from '@grafana/runtime';
|
||||
import { Icon, IconName, IconButton } from '@grafana/ui';
|
||||
import { e2e } from '@grafana/e2e';
|
||||
import { Button, HorizontalGroup, IconButton, stylesFactory, useTheme } from '@grafana/ui';
|
||||
import { connect, MapDispatchToProps } from 'react-redux';
|
||||
// Utils
|
||||
import config from 'app/core/config';
|
||||
import store from 'app/core/store';
|
||||
// Store
|
||||
import { store as reduxStore } from 'app/store/store';
|
||||
import { updateLocation } from 'app/core/actions';
|
||||
import { addPanel } from 'app/features/dashboard/state/reducers';
|
||||
// Types
|
||||
import { DashboardModel, PanelModel } from '../../state';
|
||||
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 } };
|
||||
|
||||
@ -25,55 +27,44 @@ export interface OwnProps {
|
||||
|
||||
export interface DispatchProps {
|
||||
addPanel: typeof addPanel;
|
||||
updateLocation: typeof updateLocation;
|
||||
}
|
||||
|
||||
export type Props = OwnProps & DispatchProps;
|
||||
|
||||
export interface State {
|
||||
copiedPanelPlugins: any[];
|
||||
}
|
||||
const getCopiedPanelPlugins = () => {
|
||||
const panels = _.chain(config.panels)
|
||||
.filter({ hideFromList: false })
|
||||
.map(item => item)
|
||||
.value();
|
||||
const copiedPanels = [];
|
||||
|
||||
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)
|
||||
.filter({ hideFromList: false })
|
||||
.map(item => item)
|
||||
.value();
|
||||
const copiedPanels = [];
|
||||
|
||||
const copiedPanelJson = store.get(LS_PANEL_COPY_KEY);
|
||||
if (copiedPanelJson) {
|
||||
const copiedPanel = JSON.parse(copiedPanelJson);
|
||||
const pluginInfo: any = _.find(panels, { id: copiedPanel.type });
|
||||
if (pluginInfo) {
|
||||
const pluginCopy = _.cloneDeep(pluginInfo);
|
||||
pluginCopy.name = copiedPanel.title;
|
||||
pluginCopy.sort = -1;
|
||||
pluginCopy.defaults = copiedPanel;
|
||||
copiedPanels.push(pluginCopy);
|
||||
}
|
||||
const copiedPanelJson = store.get(LS_PANEL_COPY_KEY);
|
||||
if (copiedPanelJson) {
|
||||
const copiedPanel = JSON.parse(copiedPanelJson);
|
||||
const pluginInfo: any = _.find(panels, { id: copiedPanel.type });
|
||||
if (pluginInfo) {
|
||||
const pluginCopy = _.cloneDeep(pluginInfo);
|
||||
pluginCopy.name = copiedPanel.title;
|
||||
pluginCopy.sort = -1;
|
||||
pluginCopy.defaults = copiedPanel;
|
||||
copiedPanels.push(pluginCopy);
|
||||
}
|
||||
|
||||
return _.sortBy(copiedPanels, 'sort');
|
||||
}
|
||||
|
||||
handleCloseAddPanel(evt: any) {
|
||||
return _.sortBy(copiedPanels, 'sort');
|
||||
};
|
||||
|
||||
export const AddPanelWidgetUnconnected: React.FC<Props> = ({ panel, dashboard, updateLocation, addPanel }) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const onCancelAddPanel = (evt: any) => {
|
||||
evt.preventDefault();
|
||||
this.props.dashboard.removePanel(this.props.panel);
|
||||
}
|
||||
dashboard.removePanel(panel);
|
||||
};
|
||||
|
||||
onCreateNewPanel = (tab = 'queries') => {
|
||||
const dashboard = this.props.dashboard;
|
||||
const { gridPos } = this.props.panel;
|
||||
const onCreateNewPanel = () => {
|
||||
const { gridPos } = panel;
|
||||
|
||||
const newPanel: any = {
|
||||
type: 'graph',
|
||||
@ -82,7 +73,7 @@ export class AddPanelWidgetUnconnected extends React.Component<Props, State> {
|
||||
};
|
||||
|
||||
dashboard.addPanel(newPanel);
|
||||
dashboard.removePanel(this.props.panel);
|
||||
dashboard.removePanel(panel);
|
||||
|
||||
const location: LocationUpdate = {
|
||||
query: {
|
||||
@ -91,16 +82,11 @@ export class AddPanelWidgetUnconnected extends React.Component<Props, State> {
|
||||
partial: true,
|
||||
};
|
||||
|
||||
if (tab === 'visualize') {
|
||||
location.query.tab = 'visualize';
|
||||
}
|
||||
|
||||
reduxStore.dispatch(updateLocation(location));
|
||||
updateLocation(location);
|
||||
};
|
||||
|
||||
onPasteCopiedPanel = (panelPluginInfo: PanelPluginInfo) => {
|
||||
const dashboard = this.props.dashboard;
|
||||
const { gridPos } = this.props.panel;
|
||||
const onPasteCopiedPanel = (panelPluginInfo: PanelPluginInfo) => {
|
||||
const { gridPos } = panel;
|
||||
|
||||
const newPanel: any = {
|
||||
type: panelPluginInfo.id,
|
||||
@ -121,12 +107,10 @@ export class AddPanelWidgetUnconnected extends React.Component<Props, State> {
|
||||
}
|
||||
|
||||
dashboard.addPanel(newPanel);
|
||||
dashboard.removePanel(this.props.panel);
|
||||
dashboard.removePanel(panel);
|
||||
};
|
||||
|
||||
onCreateNewRow = () => {
|
||||
const dashboard = this.props.dashboard;
|
||||
|
||||
const onCreateNewRow = () => {
|
||||
const newRow: any = {
|
||||
type: 'row',
|
||||
title: 'Row title',
|
||||
@ -134,69 +118,130 @@ export class AddPanelWidgetUnconnected extends React.Component<Props, State> {
|
||||
};
|
||||
|
||||
dashboard.addPanel(newRow);
|
||||
dashboard.removePanel(this.props.panel);
|
||||
dashboard.removePanel(panel);
|
||||
};
|
||||
|
||||
renderOptionLink = (icon: IconName, text: string, onClick: any) => {
|
||||
return (
|
||||
<div>
|
||||
<a
|
||||
href="#"
|
||||
onClick={onClick}
|
||||
className="add-panel-widget__link btn btn-inverse"
|
||||
aria-label={e2e.pages.AddDashboard.selectors.ctaButtons(text)}
|
||||
>
|
||||
<div className="add-panel-widget__icon">
|
||||
<Icon name={icon} size="xl" />
|
||||
</div>
|
||||
<span>{text}</span>
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
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>
|
||||
const styles = getStyles(theme);
|
||||
return (
|
||||
<div className={cx('panel-container', styles.wrapper)}>
|
||||
<AddPanelWidgetHandle onCancel={onCancelAddPanel} />
|
||||
<div className={styles.actionsWrapper}>
|
||||
<AddPanelWidgetCreate onCreate={onCreateNewPanel} onPasteCopiedPanel={onPasteCopiedPanel} />
|
||||
<div>
|
||||
<HorizontalGroup justify="center">
|
||||
<Button onClick={onCreateNewRow} variant="secondary" size="sm">
|
||||
Convert to row
|
||||
</Button>
|
||||
</HorizontalGroup>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const mapDispatchToProps: MapDispatchToProps<DispatchProps, OwnProps> = { addPanel };
|
||||
const mapDispatchToProps: MapDispatchToProps<DispatchProps, OwnProps> = { addPanel, updateLocation };
|
||||
|
||||
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`] = `
|
||||
<div
|
||||
className="panel-container add-panel-widget-container"
|
||||
className="panel-container css-1sl7nld"
|
||||
>
|
||||
<AddPanelWidgetHandle
|
||||
onCancel={[Function]}
|
||||
/>
|
||||
<div
|
||||
className="add-panel-widget"
|
||||
className="css-1wtsk46"
|
||||
>
|
||||
<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"
|
||||
<AddPanelWidgetCreate
|
||||
onCreate={[Function]}
|
||||
onPasteCopiedPanel={[Function]}
|
||||
/>
|
||||
<div>
|
||||
<Component
|
||||
justify="center"
|
||||
>
|
||||
New Panel
|
||||
</span>
|
||||
<div
|
||||
className="flex-grow-1"
|
||||
/>
|
||||
<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>
|
||||
<a
|
||||
aria-label="Add Panel Widget CTA Button Add Query"
|
||||
className="add-panel-widget__link btn btn-inverse"
|
||||
href="#"
|
||||
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"
|
||||
<Button
|
||||
onClick={[Function]}
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
>
|
||||
Convert to row
|
||||
</button>
|
||||
</div>
|
||||
</Button>
|
||||
</Component>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
Loading…
Reference in New Issue
Block a user