diff --git a/public/app/core/components/ScrollBar/ScrollBar.tsx b/public/app/core/components/ScrollBar/ScrollBar.tsx index a358dc1926a..24d17f67367 100644 --- a/public/app/core/components/ScrollBar/ScrollBar.tsx +++ b/public/app/core/components/ScrollBar/ScrollBar.tsx @@ -54,6 +54,10 @@ export default class ScrollBar extends React.Component { return false; } + update() { + this.scrollbar.update(); + } + handleRef = ref => { this.container = ref; }; diff --git a/public/app/features/dashboard/dashgrid/AddPanelPanel.tsx b/public/app/features/dashboard/dashgrid/AddPanelPanel.tsx index 98d1657f4bd..094bc49b708 100644 --- a/public/app/features/dashboard/dashgrid/AddPanelPanel.tsx +++ b/public/app/features/dashboard/dashgrid/AddPanelPanel.tsx @@ -1,12 +1,13 @@ 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 { + 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 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 { @@ -92,28 +128,117 @@ export class AddPanelPanel extends React.Component; + } + renderPanelItem(panel, index) { return (
this.onAddPanel(panel)} title={panel.name}> -
{panel.name}
+
{this.renderText(panel.name)}
); } + noCopiedPanelPlugins() { + return
No copied panels yet.
; + } + + 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 (
New Panel - Select a visualization +
    +
  • +
    + Add +
    +
  • +
  • +
    + Paste +
    +
  • +
- {this.state.panelPlugins.map(this.renderPanelItem)} + (this.scrollbar = element)} className="add-panel__items"> +
+ +
+ {panelTab} +
); diff --git a/public/app/features/dashboard/specs/AddPanelPanel.jest.tsx b/public/app/features/dashboard/specs/AddPanelPanel.jest.tsx new file mode 100644 index 00000000000..872d9296d12 --- /dev/null +++ b/public/app/features/dashboard/specs/AddPanelPanel.jest.tsx @@ -0,0 +1,102 @@ +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(); + }); + + 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'); + }); +}); diff --git a/public/app/features/panel/panel_ctrl.ts b/public/app/features/panel/panel_ctrl.ts index f54877c2c37..da8adc4f908 100644 --- a/public/app/features/panel/panel_ctrl.ts +++ b/public/app/features/panel/panel_ctrl.ts @@ -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) { diff --git a/public/sass/components/_panel_add_panel.scss b/public/sass/components/_panel_add_panel.scss index 4a17438ffeb..5bfff31a108 100644 --- a/public/sass/components/_panel_add_panel.scss +++ b/public/sass/components/_panel_add_panel.scss @@ -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; +} diff --git a/public/sass/components/_tabs.scss b/public/sass/components/_tabs.scss index 197d5892652..eb3c8ce13f5 100644 --- a/public/sass/components/_tabs.scss +++ b/public/sass/components/_tabs.scss @@ -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; + } }