Skip to content

Commit

Permalink
Fix #2663. Add edit and view mode to the dashboard (#2901)
Browse files Browse the repository at this point in the history
  • Loading branch information
offtherailz authored May 15, 2018
1 parent 71274a9 commit 6165bb3
Show file tree
Hide file tree
Showing 17 changed files with 239 additions and 114 deletions.
4 changes: 2 additions & 2 deletions web/client/components/dashboard/Dashboard.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@ module.exports =
widthProvider({ overrideWidthProvider: true}),
emptyState(
({widgets = []} = {}) => widgets.length === 0,
() => ({
({loading}) => ({
glyph: "dashboard",
title: <Message msgId="dashboard.emptyTitle" />
title: loading ? <Message msgId="loading" /> : <Message msgId="dashboard.emptyTitle" />
})
),
defaultProps({
Expand Down
6 changes: 5 additions & 1 deletion web/client/components/widgets/view/WidgetsView.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ module.exports = pure(({
width,
showGroupColor,
groups = [],
canEdit = true,
getWidgetClass = () => { },
onWidgetClick = () => { },
updateWidgetProperty = () => { },
Expand All @@ -49,10 +50,12 @@ module.exports = pure(({
...actions
} = {}) =>
(<ResponsiveReactGridLayout
key={id}
key={id || "widgets-view"}
useDefaultWidthProvider={useDefaultWidthProvider}
measureBeforeMount={measureBeforeMount}
width={!useDefaultWidthProvider ? width : undefined}
isResizable={canEdit}
isDraggable={canEdit}
draggableHandle={".draggableHandle"}
onLayoutChange={onLayoutChange}
preventCollision
Expand All @@ -72,6 +75,7 @@ module.exports = pure(({
groups={getWidgetGroups(groups, w)}
showGroupColor={showGroupColor}
dependencies={dependencies}
canEdit={canEdit}
updateProperty={(...args) => updateWidgetProperty(w.id, ...args)}
onDelete={() => deleteWidget(w)}
onEdit={() => editWidget(w)} /></div>))
Expand Down
8 changes: 6 additions & 2 deletions web/client/components/widgets/widget/ChartWidget.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ module.exports = ({
series = [],
loading,
showTable,
canEdit = true,
confirmDelete= false,
toggleTableView= () => {},
toggleDeleteConfirm= () => {},
Expand All @@ -56,8 +57,11 @@ module.exports = ({
? null : <ButtonToolbar>
<DropdownButton pullRight bsStyle="default" className="widget-menu" title={<Glyphicon glyph="option-vertical" />} noCaret id="dropdown-no-caret">
<MenuItem onClick={() => toggleTableView()} eventKey="1"><Glyphicon glyph="features-grid"/>&nbsp;<Message msgId="widgets.widget.menu.showChartData" /></MenuItem>
<MenuItem onClick={() => onEdit()} eventKey="3"><Glyphicon glyph="pencil"/>&nbsp;<Message msgId="widgets.widget.menu.edit" /></MenuItem>
<MenuItem onClick={() => toggleDeleteConfirm(true)} eventKey="2"><Glyphicon glyph="trash"/>&nbsp;<Message msgId="widgets.widget.menu.delete" /></MenuItem>
{canEdit
? [
<MenuItem onClick={() => onEdit()} eventKey="3"><Glyphicon glyph="pencil"/>&nbsp;<Message msgId="widgets.widget.menu.edit" /></MenuItem>,
<MenuItem onClick={() => toggleDeleteConfirm(true)} eventKey="2"><Glyphicon glyph="trash"/>&nbsp;<Message msgId="widgets.widget.menu.delete" /></MenuItem>]
: null}
<MenuItem onClick={() => exportCSV({data, title})} eventKey="4"><Glyphicon className="exportCSV" glyph="download"/>&nbsp;<Message msgId="widgets.widget.menu.downloadData" /></MenuItem>
<MenuItem onClick={() => exportImage({widgetDivId: `widget-chart-${id}`, title})} eventKey="4"><Glyphicon className="exportImage" glyph="download"/>&nbsp;<Message msgId="widgets.widget.menu.exportImage" /></MenuItem>
</DropdownButton>
Expand Down
7 changes: 4 additions & 3 deletions web/client/components/widgets/widget/CounterWidget.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ module.exports = ({
showTable,
confirmDelete= false,
headerStyle,
canEdit = true,
toggleTableView= () => {},
toggleDeleteConfirm= () => {},
onEdit= () => {},
Expand All @@ -50,12 +51,12 @@ module.exports = ({
onDelete={onDelete}
toggleDeleteConfirm = {toggleDeleteConfirm}
headerStyle={headerStyle}
topRightItems={showTable
? null : <ButtonToolbar>
topRightItems={canEdit
? (<ButtonToolbar>
<DropdownButton pullRight bsStyle="default" className="widget-menu" title={<Glyphicon glyph="option-vertical" />} noCaret id="dropdown-no-caret">
<MenuItem onClick={() => onEdit()} eventKey="3"><Glyphicon glyph="pencil"/>&nbsp;<Message msgId="widgets.widget.menu.edit" /></MenuItem>
<MenuItem onClick={() => toggleDeleteConfirm(true)} eventKey="2"><Glyphicon glyph="trash"/>&nbsp;<Message msgId="widgets.widget.menu.delete" /></MenuItem>
</DropdownButton>
</ButtonToolbar>}>
</ButtonToolbar>) : null}>
<CounterView id={id} isAnimationActive={!loading} loading={loading} data={data} series={series} iconFit {...props} />
</WidgetContainer>);
5 changes: 3 additions & 2 deletions web/client/components/widgets/widget/LegendWidget.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ module.exports = ({
id, title,
headerStyle,
confirmDelete= false,
canEdit = true,
onDelete=() => {},
loading,
description,
Expand All @@ -36,12 +37,12 @@ module.exports = ({
(<WidgetContainer id={`widget-text-${id}`} title={title} confirmDelete={confirmDelete} onDelete={onDelete} toggleDeleteConfirm={toggleDeleteConfirm} headerStyle={headerStyle}
topLeftItems={renderHeaderLeftTopItem({ loading, title, description })}

topRightItems={<ButtonToolbar>
topRightItems={canEdit ? (<ButtonToolbar>
<DropdownButton pullRight bsStyle="default" className="widget-menu" title={<Glyphicon glyph="option-vertical" />} noCaret id="dropdown-no-caret">
<MenuItem onClick={() => onEdit()} eventKey="3"><Glyphicon glyph="pencil"/>&nbsp;<Message msgId="widgets.widget.menu.edit" /></MenuItem>
<MenuItem onClick={() => toggleDeleteConfirm(true)} eventKey="2"><Glyphicon glyph="trash"/>&nbsp;<Message msgId="widgets.widget.menu.delete" /></MenuItem>
</DropdownButton>
</ButtonToolbar>}
</ButtonToolbar>) : null}
>
<LegendView {...props} />
</WidgetContainer>
Expand Down
5 changes: 3 additions & 2 deletions web/client/components/widgets/widget/MapWidget.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,18 +34,19 @@ module.exports = ({
id, title, loading, description,
map,
mapStateSource,
canEdit = true,
confirmDelete = false,
onDelete = () => {},
headerStyle
} = {}) =>
(<WidgetContainer id={`widget-text-${id}`} title={title} confirmDelete={confirmDelete} onDelete={onDelete} toggleDeleteConfirm={toggleDeleteConfirm} headerStyle={headerStyle}
topLeftItems={renderHeaderLeftTopItem({ loading, title, description })}
topRightItems={<ButtonToolbar>
topRightItems={canEdit ? (<ButtonToolbar>
<DropdownButton pullRight bsStyle="default" className="widget-menu" title={<Glyphicon glyph="option-vertical" />} noCaret id="dropdown-no-caret">
<MenuItem onClick={() => onEdit()} eventKey="3"><Glyphicon glyph="pencil" />&nbsp;<Message msgId="widgets.widget.menu.edit" /></MenuItem>
<MenuItem onClick={() => toggleDeleteConfirm(true)} eventKey="2"><Glyphicon glyph="trash" />&nbsp;<Message msgId="widgets.widget.menu.delete" /></MenuItem>
</DropdownButton>
</ButtonToolbar>}
</ButtonToolbar>) : null}
>
<MapView updateProperty={updateProperty} id={id} map={omit(map, 'mapStateSource')} mapStateSource={mapStateSource} layers={map && map.layers} options={{ style: { margin: 10, height: 'calc(100% - 20px)' }}}/>
</WidgetContainer>);
5 changes: 3 additions & 2 deletions web/client/components/widgets/widget/TableWidget.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ module.exports = ({
loading,
confirmDelete = false,
headerStyle,
canEdit = true,
toggleTableView = () => { },
toggleDeleteConfirm = () => { },
onEdit = () => { },
Expand All @@ -62,10 +63,10 @@ module.exports = ({
onDelete={onDelete}
toggleDeleteConfirm={toggleDeleteConfirm}
topRightItems={<ButtonToolbar>
<DropdownButton pullRight bsStyle="default" className="widget-menu" title={<Glyphicon glyph="option-vertical" />} noCaret id="dropdown-no-caret">
{canEdit ? (<DropdownButton pullRight bsStyle="default" className="widget-menu" title={<Glyphicon glyph="option-vertical" />} noCaret id="dropdown-no-caret">
<MenuItem onClick={() => onEdit()} eventKey="3"><Glyphicon glyph="pencil" />&nbsp;<Message msgId="widgets.widget.menu.edit" /></MenuItem>
<MenuItem onClick={() => toggleDeleteConfirm(true)} eventKey="2"><Glyphicon glyph="trash" />&nbsp;<Message msgId="widgets.widget.menu.delete" /></MenuItem>
</DropdownButton>
</DropdownButton>) : null}
</ButtonToolbar>}>
<BorderLayout
footer={pagination.totalFeatures ? (
Expand Down
5 changes: 3 additions & 2 deletions web/client/components/widgets/widget/TextWidget.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,16 @@ module.exports = ({
toggleDeleteConfirm = () => {},
id, title, text,
headerStyle,
canEdit = true,
confirmDelete= false,
onDelete=() => {}
} = {}) =>
(<WidgetContainer id={`widget-text-${id}`} title={title} confirmDelete={confirmDelete} onDelete={onDelete} toggleDeleteConfirm={toggleDeleteConfirm} headerStyle={headerStyle}
topRightItems={<ButtonToolbar>
<DropdownButton pullRight bsStyle="default" className="widget-menu" title={<Glyphicon glyph="option-vertical" />} noCaret id="dropdown-no-caret">
{canEdit ? (<DropdownButton pullRight bsStyle="default" className="widget-menu" title={<Glyphicon glyph="option-vertical" />} noCaret id="dropdown-no-caret">
<MenuItem onClick={() => onEdit()} eventKey="3"><Glyphicon glyph="pencil"/>&nbsp;<Message msgId="widgets.widget.menu.edit" /></MenuItem>
<MenuItem onClick={() => toggleDeleteConfirm(true)} eventKey="2"><Glyphicon glyph="trash"/>&nbsp;<Message msgId="widgets.widget.menu.delete" /></MenuItem>
</DropdownButton>
</DropdownButton>) : null}
</ButtonToolbar>}
>
<TextView text={text} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,16 @@ describe('ChartWidget component', () => {
ReactDOM.render(<ChartWidget />, document.getElementById("container"));
const container = document.getElementById('container');
const el = container.querySelector('.mapstore-widget-card');
expect(container.querySelector('.glyphicon-pencil')).toExist();
expect(container.querySelector('.glyphicon-trash')).toExist();
expect(el).toExist();
});
it('view only mode', () => {
ReactDOM.render(<ChartWidget canEdit={false} />, document.getElementById("container"));
const container = document.getElementById('container');
expect(container.querySelector('.glyphicon-pencil')).toNotExist();
expect(container.querySelector('.glyphicon-trash')).toNotExist();
});
it('Test ChartWidget onEdit callback', () => {
const actions = {
onEdit: () => {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,16 @@ describe('CounterWidget component', () => {
ReactDOM.render(<CounterWidget />, document.getElementById("container"));
const container = document.getElementById('container');
const el = container.querySelector('.mapstore-widget-card');
expect(container.querySelector('.glyphicon-pencil')).toExist();
expect(container.querySelector('.glyphicon-trash')).toExist();
expect(el).toExist();
});
it('view only mode', () => {
ReactDOM.render(<CounterWidget canEdit={false} />, document.getElementById("container"));
const container = document.getElementById('container');
expect(container.querySelector('.glyphicon-pencil')).toNotExist();
expect(container.querySelector('.glyphicon-trash')).toNotExist();
});
it('Test CounterWidget onEdit callback', () => {
const actions = {
onEdit: () => {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,14 @@ describe('LegendWidget component', () => {
const container = document.getElementById('container');
const el = container.querySelector('.mapstore-widget-card');
expect(el).toExist();
expect(container.querySelector('.glyphicon-pencil')).toExist();
expect(container.querySelector('.glyphicon-trash')).toExist();
});
it('view only mode', () => {
ReactDOM.render(<LegendWidget canEdit={false} />, document.getElementById("container"));
const container = document.getElementById('container');
expect(container.querySelector('.glyphicon-pencil')).toNotExist();
expect(container.querySelector('.glyphicon-trash')).toNotExist();
});
it('Test LegendWidget onEdit callback', () => {
const actions = {
Expand Down
13 changes: 10 additions & 3 deletions web/client/components/widgets/widget/__tests__/MapWidget-test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,15 @@ describe('MapWidget component', () => {
setTimeout(done);
});
it('MapWidget rendering with defaults', () => {
ReactDOM.render(<Provider store={{subscribe: () => {}, getState: () => ({maptype: {mapType: 'openlayers'}})}} ><MapWidget /></Provider>, document.getElementById("container"));
const el = document.querySelector('div');
expect(el).toExist();
ReactDOM.render(<Provider store={{subscribe: () => {}, getState: () => ({maptype: {mapType: 'openlayers'}})}} ><MapWidget map={{layers: []}}/></Provider>, document.getElementById("container"));
const container = document.getElementById('container');
expect(container.querySelector('.glyphicon-pencil')).toExist();
expect(container.querySelector('.glyphicon-trash')).toExist();
});
it('view only mode', () => {
ReactDOM.render(<Provider store={{ subscribe: () => { }, getState: () => ({ maptype: { mapType: 'openlayers' } }) }} ><MapWidget map={{ layers: [] }} canEdit={false}/></Provider>, document.getElementById("container"));
const container = document.getElementById('container');
expect(container.querySelector('.glyphicon-pencil')).toNotExist();
expect(container.querySelector('.glyphicon-trash')).toNotExist();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,14 @@ describe('TableWidget component', () => {
const container = document.getElementById('container');
const el = container.querySelector('.mapstore-widget-card');
expect(el).toExist();
expect(container.querySelector('.glyphicon-pencil')).toExist();
expect(container.querySelector('.glyphicon-trash')).toExist();
});
it('view only mode', () => {
ReactDOM.render(<TableWidget canEdit={false} />, document.getElementById("container"));
const container = document.getElementById('container');
expect(container.querySelector('.glyphicon-pencil')).toNotExist();
expect(container.querySelector('.glyphicon-trash')).toNotExist();
});
it('Test TableWidget onEdit callback', () => {
const actions = {
Expand Down
57 changes: 57 additions & 0 deletions web/client/components/widgets/widget/__tests__/TextWidget-test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* Copyright 2018, GeoSolutions Sas.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/

const React = require('react');
const ReactDOM = require('react-dom');
const ReactTestUtils = require('react-dom/test-utils');

const expect = require('expect');
const TextWidget = require('../TextWidget');

describe('TextWidget component', () => {
beforeEach((done) => {
document.body.innerHTML = '<div id="container"></div>';
setTimeout(done);
});
afterEach((done) => {
ReactDOM.unmountComponentAtNode(document.getElementById("container"));
document.body.innerHTML = '';
setTimeout(done);
});
it('rendering with defaults', () => {
ReactDOM.render(<TextWidget />, document.getElementById("container"));
const container = document.getElementById('container');
const el = container.querySelector('.mapstore-widget-card');
expect(container.querySelector('.glyphicon-pencil')).toExist();
expect(container.querySelector('.glyphicon-trash')).toExist();
expect(el).toExist();
});
it('view only mode', () => {
ReactDOM.render(<TextWidget canEdit={false} />, document.getElementById("container"));
const container = document.getElementById('container');
expect(container.querySelector('.glyphicon-pencil')).toNotExist();
expect(container.querySelector('.glyphicon-trash')).toNotExist();
});
it('onEdit callback', () => {
const actions = {
onEdit: () => {}
};
const spyonEdit = expect.spyOn(actions, 'onEdit');
ReactDOM.render(<TextWidget onEdit={actions.onEdit} />, document.getElementById("container"));
const container = document.getElementById('container');
const el = container.querySelector('.glyphicon-pencil');
ReactTestUtils.Simulate.click(el); // <-- trigger event callback
expect(spyonEdit).toHaveBeenCalled();
});
it('rendering text', () => {
const TEST_TEXT = '<div id="TEST_TEXT"> TEST </div>';
ReactDOM.render(<TextWidget text={TEST_TEXT} />, document.getElementById("container"));
const container = document.getElementById('container');
expect(container.querySelector('#TEST_TEXT')).toExist();
});
});
17 changes: 10 additions & 7 deletions web/client/epics/dashboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ const {
QUERY_FORM_SEARCH
} = require('../actions/queryform');
const {
LOGIN_SUCCESS
LOGIN_SUCCESS,
LOGOUT
} = require('../actions/security');
const {
isDashboardEditing,
Expand Down Expand Up @@ -153,12 +154,7 @@ module.exports = {
return Rx.Observable.of(error({
title: "dashboard.errors.loading.title",
message: "dashboard.errors.loading.pleaseLogin"
}))
.merge(action$
.ofType(LOGIN_SUCCESS)
.switchMap( () => Rx.Observable.of(loadDashboard(id)).delay(1000))
.filter(() => isDashboardAvailable(getState()))
.takeUntil(action$.ofType(LOCATION_CHANGE)));
}));
} if (e.status === 404) {
return Rx.Observable.of(error({
title: "dashboard.errors.loading.title",
Expand All @@ -172,6 +168,13 @@ module.exports = {
}
))
),
reloadDashboardOnLoginLogout: (action$) =>
action$.ofType(LOAD_DASHBOARD).switchMap(
({ id }) => action$
.ofType(LOGIN_SUCCESS, LOGOUT)
.switchMap(() => Rx.Observable.of(loadDashboard(id)).delay(1000))
.takeUntil(action$.ofType(LOCATION_CHANGE))
),
// saving dashboard flow (both creation and update)
saveDashboard: action$ => action$
.ofType(SAVE_DASHBOARD)
Expand Down
Loading

0 comments on commit 6165bb3

Please sign in to comment.