Commit 40114e8c by Dominik Prokop Committed by GitHub

NewPanelEditor: UI update of add panel widget (#23715)

* Small update proposal to add panel widget

* Fix smoketest

* Minor tweaks

* update snaps god damn it
parent 7e429250
......@@ -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);
......
// 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,25 +27,12 @@ export interface OwnProps {
export interface DispatchProps {
addPanel: typeof addPanel;
updateLocation: typeof updateLocation;
}
export type Props = OwnProps & DispatchProps;
export interface State {
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 getCopiedPanelPlugins = () => {
const panels = _.chain(config.panels)
.filter({ hideFromList: false })
.map(item => item)
......@@ -64,16 +53,18 @@ export class AddPanelWidgetUnconnected extends React.Component<Props, State> {
}
return _.sortBy(copiedPanels, 'sort');
}
};
export const AddPanelWidgetUnconnected: React.FC<Props> = ({ panel, dashboard, updateLocation, addPanel }) => {
const theme = useTheme();
handleCloseAddPanel(evt: any) {
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) => {
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>
<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" />
<HorizontalGroup justify="center">
<Button onClick={onCreateNewRow} variant="secondary" size="sm">
Convert to row
</Button>
</HorizontalGroup>
</div>
</div>
<span>{text}</span>
</a>
</div>
);
};
};
render() {
const { copiedPanelPlugins } = this.state;
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="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 className={cx(styles.handle, 'grid-drag-handle')}>
<IconButton name="times" onClick={onCancel} surface="header" className="add-panel-widget__close" />
</div>
<div className="add-panel-widget__actions">
<button className="btn btn-inverse add-panel-widget__action" onClick={this.onCreateNewRow}>
Convert to row
</button>
);
};
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
className="btn btn-inverse add-panel-widget__action"
onClick={() => this.onPasteCopiedPanel(copiedPanelPlugins[0])}
>
<Button variant="secondary" size="md" onClick={() => onPasteCopiedPanel(copiedPanelPlugins[0])}>
Paste copied panel
</button>
</Button>
)}
</div>
</div>
</div>
</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 mapDispatchToProps: MapDispatchToProps<DispatchProps, OwnProps> = { addPanel };
export const AddPanelWidget = connect(null, mapDispatchToProps)(AddPanelWidgetUnconnected);
`,
};
});
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"
>
<div
className="add-panel-widget"
>
<div
className="add-panel-widget__header grid-drag-handle"
>
<Icon
name="panel-add"
size="xl"
style={
Object {
"margin": "4px",
"marginRight": "8px",
}
}
type="mono"
<AddPanelWidgetHandle
onCancel={[Function]}
/>
<span
className="add-panel-widget__title"
>
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]}
className="css-1wtsk46"
>
<div
className="add-panel-widget__icon"
>
<Icon
name="database"
size="xl"
<AddPanelWidgetCreate
onCreate={[Function]}
onPasteCopiedPanel={[Function]}
/>
</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"
<Component
justify="center"
>
<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>
......
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