From d37ae46a1cf09c84678c9d04e6dda2400d4646df Mon Sep 17 00:00:00 2001 From: Lorenzo Natali Date: Mon, 14 May 2018 18:48:58 +0200 Subject: [PATCH] Fix #2663. Dashboard edit and view mode --- web/client/components/dashboard/Dashboard.jsx | 4 +- .../components/widgets/view/WidgetsView.jsx | 6 +- .../components/widgets/widget/ChartWidget.jsx | 8 +- .../widgets/widget/CounterWidget.jsx | 7 +- .../widgets/widget/LegendWidget.jsx | 5 +- .../components/widgets/widget/MapWidget.jsx | 5 +- .../components/widgets/widget/TableWidget.jsx | 5 +- .../components/widgets/widget/TextWidget.jsx | 5 +- web/client/epics/dashboard.js | 17 +- web/client/plugins/Dashboard.jsx | 36 +++-- web/client/plugins/DashboardEditor.jsx | 153 ++++++++++-------- 11 files changed, 140 insertions(+), 111 deletions(-) diff --git a/web/client/components/dashboard/Dashboard.jsx b/web/client/components/dashboard/Dashboard.jsx index 5494d4fca9..e8c9805ceb 100644 --- a/web/client/components/dashboard/Dashboard.jsx +++ b/web/client/components/dashboard/Dashboard.jsx @@ -22,9 +22,9 @@ module.exports = widthProvider({ overrideWidthProvider: true}), emptyState( ({widgets = []} = {}) => widgets.length === 0, - () => ({ + ({loading}) => ({ glyph: "dashboard", - title: + title: loading ? : }) ), defaultProps({ diff --git a/web/client/components/widgets/view/WidgetsView.jsx b/web/client/components/widgets/view/WidgetsView.jsx index db96f8d009..8847c82eec 100644 --- a/web/client/components/widgets/view/WidgetsView.jsx +++ b/web/client/components/widgets/view/WidgetsView.jsx @@ -40,6 +40,7 @@ module.exports = pure(({ width, showGroupColor, groups = [], + canEdit = true, getWidgetClass = () => { }, onWidgetClick = () => { }, updateWidgetProperty = () => { }, @@ -49,10 +50,12 @@ module.exports = pure(({ ...actions } = {}) => ( updateWidgetProperty(w.id, ...args)} onDelete={() => deleteWidget(w)} onEdit={() => editWidget(w)} />)) diff --git a/web/client/components/widgets/widget/ChartWidget.jsx b/web/client/components/widgets/widget/ChartWidget.jsx index 2924101213..09886ae6fd 100644 --- a/web/client/components/widgets/widget/ChartWidget.jsx +++ b/web/client/components/widgets/widget/ChartWidget.jsx @@ -36,6 +36,7 @@ module.exports = ({ series = [], loading, showTable, + canEdit = true, confirmDelete= false, toggleTableView= () => {}, toggleDeleteConfirm= () => {}, @@ -56,8 +57,11 @@ module.exports = ({ ? null : } noCaret id="dropdown-no-caret"> toggleTableView()} eventKey="1">  - onEdit()} eventKey="3">  - toggleDeleteConfirm(true)} eventKey="2">  + {canEdit + ? [ + onEdit()} eventKey="3"> , + toggleDeleteConfirm(true)} eventKey="2"> ] + : null} exportCSV({data, title})} eventKey="4">  exportImage({widgetDivId: `widget-chart-${id}`, title})} eventKey="4">  diff --git a/web/client/components/widgets/widget/CounterWidget.jsx b/web/client/components/widgets/widget/CounterWidget.jsx index c21c127a66..766d752461 100644 --- a/web/client/components/widgets/widget/CounterWidget.jsx +++ b/web/client/components/widgets/widget/CounterWidget.jsx @@ -36,6 +36,7 @@ module.exports = ({ showTable, confirmDelete= false, headerStyle, + canEdit = true, toggleTableView= () => {}, toggleDeleteConfirm= () => {}, onEdit= () => {}, @@ -50,12 +51,12 @@ module.exports = ({ onDelete={onDelete} toggleDeleteConfirm = {toggleDeleteConfirm} headerStyle={headerStyle} - topRightItems={showTable - ? null : + topRightItems={canEdit + ? ( } noCaret id="dropdown-no-caret"> onEdit()} eventKey="3">  toggleDeleteConfirm(true)} eventKey="2">  - }> + ) : null}> ); diff --git a/web/client/components/widgets/widget/LegendWidget.jsx b/web/client/components/widgets/widget/LegendWidget.jsx index ead43ffce0..063ecf3906 100644 --- a/web/client/components/widgets/widget/LegendWidget.jsx +++ b/web/client/components/widgets/widget/LegendWidget.jsx @@ -28,6 +28,7 @@ module.exports = ({ id, title, headerStyle, confirmDelete= false, + canEdit = true, onDelete=() => {}, loading, description, @@ -36,12 +37,12 @@ module.exports = ({ ( + topRightItems={canEdit ? ( } noCaret id="dropdown-no-caret"> onEdit()} eventKey="3">  toggleDeleteConfirm(true)} eventKey="2">  - } + ) : null} > diff --git a/web/client/components/widgets/widget/MapWidget.jsx b/web/client/components/widgets/widget/MapWidget.jsx index fb326d79f7..a31122e937 100644 --- a/web/client/components/widgets/widget/MapWidget.jsx +++ b/web/client/components/widgets/widget/MapWidget.jsx @@ -34,18 +34,19 @@ module.exports = ({ id, title, loading, description, map, mapStateSource, + canEdit = true, confirmDelete = false, onDelete = () => {}, headerStyle } = {}) => ( + topRightItems={canEdit ? ( } noCaret id="dropdown-no-caret"> onEdit()} eventKey="3">  toggleDeleteConfirm(true)} eventKey="2">  - } + ) : null} > ); diff --git a/web/client/components/widgets/widget/TableWidget.jsx b/web/client/components/widgets/widget/TableWidget.jsx index 83a7e69572..3258dddaa5 100644 --- a/web/client/components/widgets/widget/TableWidget.jsx +++ b/web/client/components/widgets/widget/TableWidget.jsx @@ -36,6 +36,7 @@ module.exports = ({ loading, confirmDelete = false, headerStyle, + canEdit = true, toggleTableView = () => { }, toggleDeleteConfirm = () => { }, onEdit = () => { }, @@ -62,10 +63,10 @@ module.exports = ({ onDelete={onDelete} toggleDeleteConfirm={toggleDeleteConfirm} topRightItems={ - } noCaret id="dropdown-no-caret"> + {canEdit ? (} noCaret id="dropdown-no-caret"> onEdit()} eventKey="3">  toggleDeleteConfirm(true)} eventKey="2">  - + ) : null} }> {}, id, title, text, headerStyle, + canEdit = true, confirmDelete= false, onDelete=() => {} } = {}) => ( - } noCaret id="dropdown-no-caret"> + {canEdit ? (} noCaret id="dropdown-no-caret"> onEdit()} eventKey="3">  toggleDeleteConfirm(true)} eventKey="2">  - + ) : null} } > diff --git a/web/client/epics/dashboard.js b/web/client/epics/dashboard.js index d8da36ad1f..e49ad82372 100644 --- a/web/client/epics/dashboard.js +++ b/web/client/epics/dashboard.js @@ -37,7 +37,8 @@ const { QUERY_FORM_SEARCH } = require('../actions/queryform'); const { - LOGIN_SUCCESS + LOGIN_SUCCESS, + LOGOUT } = require('../actions/security'); const { isDashboardEditing, @@ -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", @@ -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) diff --git a/web/client/plugins/Dashboard.jsx b/web/client/plugins/Dashboard.jsx index 2ae0be8a7a..6949d8b7f4 100644 --- a/web/client/plugins/Dashboard.jsx +++ b/web/client/plugins/Dashboard.jsx @@ -7,21 +7,20 @@ */ const React = require('react'); -const {get} = require('lodash'); -const {connect} = require('react-redux'); +const { get } = require('lodash'); +const { connect } = require('react-redux'); const { compose, withProps, withHandlers } = require('recompose'); -const {createSelector} = require('reselect'); -const {mapIdSelector} = require('../selectors/map'); -const { getDashboardWidgets, dependenciesSelector, getDashboardWidgetsLayout, isWidgetSelectionActive, getEditingWidget, getWidgetsDependenciesGroups} = require('../selectors/widgets'); -const { editWidget, updateWidgetProperty, deleteWidget, changeLayout, exportCSV, exportImage, selectWidget} = require('../actions/widgets'); -const {showConnectionsSelector} = require('../selectors/dashboard'); +const { createSelector } = require('reselect'); +const { getDashboardWidgets, dependenciesSelector, getDashboardWidgetsLayout, isWidgetSelectionActive, getEditingWidget, getWidgetsDependenciesGroups } = require('../selectors/widgets'); +const { editWidget, updateWidgetProperty, deleteWidget, changeLayout, exportCSV, exportImage, selectWidget } = require('../actions/widgets'); +const { showConnectionsSelector, dashboardResource, isDashboardLoading } = require('../selectors/dashboard'); const ContainerDimensions = require('react-container-dimensions').default; const PropTypes = require('prop-types'); const WidgetsView = compose( connect( createSelector( - mapIdSelector, + dashboardResource, getDashboardWidgets, getDashboardWidgetsLayout, dependenciesSelector, @@ -29,8 +28,11 @@ const WidgetsView = compose( (state) => get(getEditingWidget(state), "id"), getWidgetsDependenciesGroups, showConnectionsSelector, - (id, widgets, layouts, dependencies, selectionActive, editingWidgetId, groups, showGroupColor) => ({ - id, + isDashboardLoading, + (resource, widgets, layouts, dependencies, selectionActive, editingWidgetId, groups, showGroupColor, loading) => ({ + resource, + loading, + canEdit: (resource ? !!resource.canEdit : true), widgets, layouts, dependencies, @@ -63,14 +65,14 @@ const WidgetsView = compose( class Widgets extends React.Component { - static propTypes = { - enabled: PropTypes.bool - }; - static defaultProps = { - enabled: true - }; + static propTypes = { + enabled: PropTypes.bool + }; + static defaultProps = { + enabled: true + }; render() { - return this.props.enabled ? ({({width, height}) => } ) : null; + return this.props.enabled ? ({({ width, height }) => }) : null; } } diff --git a/web/client/plugins/DashboardEditor.jsx b/web/client/plugins/DashboardEditor.jsx index f2cdb5996f..04903396dc 100644 --- a/web/client/plugins/DashboardEditor.jsx +++ b/web/client/plugins/DashboardEditor.jsx @@ -7,26 +7,27 @@ */ const React = require('react'); -const {withProps, compose} = require('recompose'); -const {createSelector} = require('reselect'); -const {connect} = require('react-redux'); +const { withProps, compose } = require('recompose'); +const { createSelector } = require('reselect'); +const { connect } = require('react-redux'); const PropTypes = require('prop-types'); -const { isDashboardEditing} = require('../selectors/dashboard'); +const { isDashboardEditing } = require('../selectors/dashboard'); const { isLoggedIn } = require('../selectors/security'); -const { dashboardHasWidgets } = require('../selectors/widgets'); -const { showConnectionsSelector, dashboardResource } = require('../selectors/dashboard'); -const {dashboardSelector} = require('./widgetbuilder/commons'); +const { dashboardHasWidgets, getWidgetsDependenciesGroups } = require('../selectors/widgets'); +const { showConnectionsSelector, dashboardResource, isDashboardLoading } = require('../selectors/dashboard'); +const { dashboardSelector } = require('./widgetbuilder/commons'); const { createWidget, toggleConnection } = require('../actions/widgets'); const { triggerShowConnections, triggerSave } = require('../actions/dashboard'); const withDashboardExitButton = require('./widgetbuilder/enhancers/withDashboardExitButton'); +const LoadingSpinner = require('../components/misc/LoadingSpinner'); const Builder = compose( - connect(dashboardSelector, { toggleConnection, triggerShowConnections}), - withProps(({ availableDependencies = []}) => ({ + connect(dashboardSelector, { toggleConnection, triggerShowConnections }), + withProps(({ availableDependencies = [] }) => ({ availableDependencies: availableDependencies.filter(d => d !== "map") })), withDashboardExitButton @@ -39,11 +40,14 @@ const Toolbar = compose( isLoggedIn, dashboardResource, dashboardHasWidgets, - (showConnections, logged, resource, hasWidgets) => ({ + getWidgetsDependenciesGroups, + (showConnections, logged, resource, hasWidgets, groups = []) => ({ showConnections, + hasConnections: groups.length > 0, hasWidgets, + canEdit: (resource ? resource.canEdit : true), canSave: logged && hasWidgets && (resource ? resource.canEdit : true) - }) + }) ), { onShowConnections: triggerShowConnections, @@ -52,69 +56,74 @@ const Toolbar = compose( } ), withProps(({ - onAddWidget = () => {}, - onToggleSave = () => {}, + onAddWidget = () => { }, + onToggleSave = () => { }, hasWidgets, canSave, - showConnections, onShowConnections = () => { } - }) => ({ - buttons: [{ - glyph: 'plus', - tooltipId: 'dashboard.editor.addACardToTheDashboard', - bsStyle: 'primary', - visible: true, - onClick: () => onAddWidget() - }, { - glyph: 'floppy-disk', - tooltipId: 'dashboard.editor.save', - bsStyle: 'primary', - tooltipPosition: 'right', - visible: !!canSave, - onClick: () => onToggleSave(true) - }, { - glyph: showConnections ? 'bulb-on' : 'bulb-off', - tooltipId: showConnections ? 'dashboard.editor.hideConnections' : 'dashboard.editor.showConnections', - bsStyle: showConnections ? 'success' : 'primary', - visible: !!hasWidgets, - onClick: () => onShowConnections(!showConnections) - }] - })) + canEdit, + hasConnections, + showConnections, + onShowConnections = () => { } + }) => ({ + buttons: [{ + glyph: 'plus', + tooltipId: 'dashboard.editor.addACardToTheDashboard', + bsStyle: 'primary', + visible: canEdit, + onClick: () => onAddWidget() + }, { + glyph: 'floppy-disk', + tooltipId: 'dashboard.editor.save', + bsStyle: 'primary', + tooltipPosition: 'right', + visible: !!canSave, + onClick: () => onToggleSave(true) + }, { + glyph: showConnections ? 'bulb-on' : 'bulb-off', + tooltipId: showConnections ? 'dashboard.editor.hideConnections' : 'dashboard.editor.showConnections', + bsStyle: showConnections ? 'success' : 'primary', + visible: !!hasWidgets && !!hasConnections, + onClick: () => onShowConnections(!showConnections) + }] + })) )(require('../components/misc/toolbar/Toolbar')); const SaveDialog = require('./dashboard/SaveDialog'); -const {setEditing, setEditorAvailable} = require('../actions/dashboard'); +const { setEditing, setEditorAvailable } = require('../actions/dashboard'); class DashboardEditorComponent extends React.Component { - static propTypes = { - id: PropTypes.string, - editing: PropTypes.bool, - limitDockHeight: PropTypes.bool, - fluid: PropTypes.bool, - zIndex: PropTypes.number, - dockSize: PropTypes.number, - position: PropTypes.string, - onMount: PropTypes.func, - onUnmount: PropTypes.func, - setEditing: PropTypes.func, - dimMode: PropTypes.string, - src: PropTypes.string, - style: PropTypes.object - }; - static defaultProps = { - id: "dashboard-editor", - editing: false, - dockSize: 500, - limitDockHeight: true, - zIndex: 10000, - fluid: false, - dimMode: "none", - position: "left", - onMount: () => {}, - onUnmount: () => {}, - setEditing: () => {} - }; + static propTypes = { + id: PropTypes.string, + editing: PropTypes.bool, + loading: PropTypes.bool, + limitDockHeight: PropTypes.bool, + fluid: PropTypes.bool, + zIndex: PropTypes.number, + dockSize: PropTypes.number, + position: PropTypes.string, + onMount: PropTypes.func, + onUnmount: PropTypes.func, + setEditing: PropTypes.func, + dimMode: PropTypes.string, + src: PropTypes.string, + style: PropTypes.object + }; + static defaultProps = { + id: "dashboard-editor", + editing: false, + dockSize: 500, + loading: true, + limitDockHeight: true, + zIndex: 10000, + fluid: false, + dimMode: "none", + position: "left", + onMount: () => { }, + onUnmount: () => { }, + setEditing: () => { } + }; componentDidMount() { this.props.onMount(); } @@ -124,18 +133,20 @@ class DashboardEditorComponent extends React.Component { } render() { return this.props.editing - ?
this.props.setEditing(false)} catalog={this.props.catalog}/>
- : (
- - -
); + ?
this.props.setEditing(false)} catalog={this.props.catalog} />
+ : (
+ + + {this.props.loading ? : null} +
); } } const Plugin = connect( createSelector( isDashboardEditing, - (editing) => ({ editing }), + isDashboardLoading, + (editing, loading) => ({ editing, loading }), ), { setEditing, onMount: () => setEditorAvailable(true),