Commit fd409f11 by Marcus Efraimsson Committed by GitHub

Merge pull request #10986 from grafana/10427_addpanel_filter

add tabs and search filter to new panel control
parents 0ad63366 606257a1
......@@ -54,6 +54,10 @@ export default class ScrollBar extends React.Component<Props, any> {
return false;
}
update() {
this.scrollbar.update();
}
handleRef = ref => {
this.container = ref;
};
......
import React from 'react';
import _ from 'lodash';
import classNames from 'classnames';
import config from 'app/core/config';
import { PanelModel } from '../panel_model';
import { PanelContainer } from './PanelContainer';
import ScrollBar from 'app/core/components/ScrollBar/ScrollBar';
import store from 'app/core/store';
import { LS_PANEL_COPY_KEY } from 'app/core/constants';
import Highlighter from 'react-highlight-words';
export interface AddPanelPanelProps {
panel: PanelModel;
......@@ -16,21 +17,42 @@ export interface AddPanelPanelProps {
export interface AddPanelPanelState {
filter: string;
panelPlugins: any[];
copiedPanelPlugins: any[];
tab: string;
}
export class AddPanelPanel extends React.Component<AddPanelPanelProps, AddPanelPanelState> {
private scrollbar: ScrollBar;
constructor(props) {
super(props);
this.handleCloseAddPanel = this.handleCloseAddPanel.bind(this);
this.renderPanelItem = this.renderPanelItem.bind(this);
this.panelSizeChanged = this.panelSizeChanged.bind(this);
this.state = {
panelPlugins: this.getPanelPlugins(),
panelPlugins: this.getPanelPlugins(''),
copiedPanelPlugins: this.getCopiedPanelPlugins(''),
filter: '',
tab: 'Add',
};
}
getPanelPlugins() {
componentDidMount() {
this.props.panel.events.on('panel-size-changed', this.panelSizeChanged);
}
componentWillUnmount() {
this.props.panel.events.off('panel-size-changed', this.panelSizeChanged);
}
panelSizeChanged() {
setTimeout(() => {
this.scrollbar.update();
});
}
getPanelPlugins(filter) {
let panels = _.chain(config.panels)
.filter({ hideFromList: false })
.map(item => item)
......@@ -39,6 +61,19 @@ export class AddPanelPanel extends React.Component<AddPanelPanelProps, AddPanelP
// add special row type
panels.push({ id: 'row', name: 'Row', sort: 8, info: { logos: { small: 'public/img/icn-row.svg' } } });
panels = this.filterPanels(panels, filter);
// add sort by sort property
return _.sortBy(panels, 'sort');
}
getCopiedPanelPlugins(filter) {
let panels = _.chain(config.panels)
.filter({ hideFromList: false })
.map(item => item)
.value();
let copiedPanels = [];
let copiedPanelJson = store.get(LS_PANEL_COPY_KEY);
if (copiedPanelJson) {
let copiedPanel = JSON.parse(copiedPanelJson);
......@@ -48,12 +83,13 @@ export class AddPanelPanel extends React.Component<AddPanelPanelProps, AddPanelP
pluginCopy.name = copiedPanel.title;
pluginCopy.sort = -1;
pluginCopy.defaults = copiedPanel;
panels.push(pluginCopy);
copiedPanels.push(pluginCopy);
}
}
// add sort by sort property
return _.sortBy(panels, 'sort');
copiedPanels = this.filterPanels(copiedPanels, filter);
return _.sortBy(copiedPanels, 'sort');
}
onAddPanel = panelPluginInfo => {
......@@ -92,28 +128,117 @@ export class AddPanelPanel extends React.Component<AddPanelPanelProps, AddPanelP
dashboard.removePanel(dashboard.panels[0]);
}
renderText(text: string) {
let searchWords = this.state.filter.split('');
return <Highlighter highlightClassName="highlight-search-match" textToHighlight={text} searchWords={searchWords} />;
}
renderPanelItem(panel, index) {
return (
<div key={index} className="add-panel__item" onClick={() => this.onAddPanel(panel)} title={panel.name}>
<img className="add-panel__item-img" src={panel.info.logos.small} />
<div className="add-panel__item-name">{panel.name}</div>
<div className="add-panel__item-name">{this.renderText(panel.name)}</div>
</div>
);
}
noCopiedPanelPlugins() {
return <div className="add-panel__no-panels">No copied panels yet.</div>;
}
filterChange(evt) {
this.setState({
filter: evt.target.value,
panelPlugins: this.getPanelPlugins(evt.target.value),
copiedPanelPlugins: this.getCopiedPanelPlugins(evt.target.value),
});
}
filterPanels(panels, filter) {
let regex = new RegExp(filter, 'i');
return panels.filter(panel => {
return regex.test(panel.name);
});
}
openCopy() {
this.setState({
tab: 'Copy',
filter: '',
panelPlugins: this.getPanelPlugins(''),
copiedPanelPlugins: this.getCopiedPanelPlugins(''),
});
}
openAdd() {
this.setState({
tab: 'Add',
filter: '',
panelPlugins: this.getPanelPlugins(''),
copiedPanelPlugins: this.getCopiedPanelPlugins(''),
});
}
render() {
let addClass = classNames({
'active active--panel': this.state.tab === 'Add',
'': this.state.tab === 'Copy',
});
let copyClass = classNames({
'': this.state.tab === 'Add',
'active active--panel': this.state.tab === 'Copy',
});
let panelTab;
if (this.state.tab === 'Add') {
panelTab = this.state.panelPlugins.map(this.renderPanelItem);
} else if (this.state.tab === 'Copy') {
if (this.state.copiedPanelPlugins.length > 0) {
panelTab = this.state.copiedPanelPlugins.map(this.renderPanelItem);
} else {
panelTab = this.noCopiedPanelPlugins();
}
}
return (
<div className="panel-container add-panel-container">
<div className="add-panel">
<div className="add-panel__header">
<i className="gicon gicon-add-panel" />
<span className="add-panel__title">New Panel</span>
<span className="add-panel__sub-title">Select a visualization</span>
<ul className="gf-tabs">
<li className="gf-tabs-item">
<div className={'gf-tabs-link pointer ' + addClass} onClick={this.openAdd.bind(this)}>
Add
</div>
</li>
<li className="gf-tabs-item">
<div className={'gf-tabs-link pointer ' + copyClass} onClick={this.openCopy.bind(this)}>
Paste
</div>
</li>
</ul>
<button className="add-panel__close" onClick={this.handleCloseAddPanel}>
<i className="fa fa-close" />
</button>
</div>
<ScrollBar className="add-panel__items">{this.state.panelPlugins.map(this.renderPanelItem)}</ScrollBar>
<ScrollBar ref={element => (this.scrollbar = element)} className="add-panel__items">
<div className="add-panel__searchbar">
<label className="gf-form gf-form--grow gf-form--has-input-icon">
<input
type="text"
className="gf-form-input max-width-20"
placeholder="Panel Search Filter"
value={this.state.filter}
onChange={this.filterChange.bind(this)}
/>
<i className="gf-form-input-icon fa fa-search" />
</label>
</div>
{panelTab}
</ScrollBar>
</div>
</div>
);
......
import React from 'react';
import { AddPanelPanel } from './../dashgrid/AddPanelPanel';
import { PanelModel } from '../panel_model';
import { shallow } from 'enzyme';
import config from '../../../core/config';
jest.mock('app/core/store', () => ({
get: key => {
return null;
},
delete: key => {
return null;
},
}));
describe('AddPanelPanel', () => {
let wrapper, dashboardMock, getPanelContainer, panel;
beforeEach(() => {
config.panels = [
{
id: 'singlestat',
hideFromList: false,
name: 'Singlestat',
sort: 2,
info: {
logos: {
small: '',
},
},
},
{
id: 'hidden',
hideFromList: true,
name: 'Hidden',
sort: 100,
info: {
logos: {
small: '',
},
},
},
{
id: 'graph',
hideFromList: false,
name: 'Graph',
sort: 1,
info: {
logos: {
small: '',
},
},
},
{
id: 'alexander_zabbix',
hideFromList: false,
name: 'Zabbix',
sort: 100,
info: {
logos: {
small: '',
},
},
},
{
id: 'piechart',
hideFromList: false,
name: 'Piechart',
sort: 100,
info: {
logos: {
small: '',
},
},
},
];
dashboardMock = { toggleRow: jest.fn() };
getPanelContainer = jest.fn().mockReturnValue({
getDashboard: jest.fn().mockReturnValue(dashboardMock),
getPanelLoader: jest.fn(),
});
panel = new PanelModel({ collapsed: false });
wrapper = shallow(<AddPanelPanel panel={panel} getPanelContainer={getPanelContainer} />);
});
it('should fetch all panels sorted with core plugins first', () => {
//console.log(wrapper.debug());
//console.log(wrapper.find('.add-panel__item').get(0).props.title);
expect(wrapper.find('.add-panel__item').get(1).props.title).toBe('Singlestat');
expect(wrapper.find('.add-panel__item').get(4).props.title).toBe('Piechart');
});
it('should filter', () => {
wrapper.find('input').simulate('change', { target: { value: 'p' } });
expect(wrapper.find('.add-panel__item').get(1).props.title).toBe('Piechart');
expect(wrapper.find('.add-panel__item').get(0).props.title).toBe('Graph');
});
});
......@@ -194,8 +194,8 @@ export class PanelCtrl {
});
menu.push({
text: 'Add to Panel List',
click: 'ctrl.addToPanelList()',
text: 'Copy',
click: 'ctrl.copyPanel()',
role: 'Editor',
});
}
......@@ -260,9 +260,9 @@ export class PanelCtrl {
});
}
addToPanelList() {
copyPanel() {
store.set(LS_PANEL_COPY_KEY, JSON.stringify(this.panel.getSaveModel()));
appEvents.emit('alert-success', ['Panel temporarily added to panel list']);
appEvents.emit('alert-success', ['Panel copied. Open Add Panel to paste']);
}
replacePanel(newPanel, oldPanel) {
......
......@@ -11,9 +11,12 @@
}
.add-panel__header {
padding: 5px 15px;
padding: 0 15px;
display: flex;
align-items: center;
background: $page-header-bg;
box-shadow: $page-header-shadow;
border-bottom: 1px solid $page-header-border-color;
.gicon {
font-size: 30px;
......@@ -31,7 +34,7 @@
.add-panel__title {
font-size: $font-size-md;
margin-right: $spacer/2;
margin-right: $spacer*2;
}
.add-panel__sub-title {
......@@ -47,6 +50,7 @@
flex-direction: row;
flex-wrap: wrap;
overflow: auto;
height: 100%;
align-content: flex-start;
justify-content: space-around;
position: relative;
......@@ -84,3 +88,16 @@
.add-panel__item-icon {
padding: 2px;
}
.add-panel__searchbar {
width: 100%;
margin-bottom: 10px;
margin-top: 7px;
}
.add-panel__no-panels {
color: $text-color-weak;
font-style: italic;
width: 100%;
padding: 3px 8px;
}
......@@ -44,18 +44,16 @@
&::before {
display: block;
content: " ";
content: ' ';
position: absolute;
left: 0;
right: 0;
height: 2px;
top: 0;
background-image: linear-gradient(
to right,
#ffd500 0%,
#ff4400 99%,
#ff4400 100%
);
background-image: linear-gradient(to right, #ffd500 0%, #ff4400 99%, #ff4400 100%);
}
}
&.active--panel {
background: $panel-bg !important;
}
}
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