Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix #2031 Trigger notification if a map is an old one asking for updating it #2039

Merged
merged 8 commits into from
Jul 27, 2017
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docma-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@
"web/client/reducers/search.js",

"web/client/epics/index.jsdoc",
"web/client/epics/automapupdate.js",
"web/client/epics/cookies.js",
"web/client/epics/fullscreen.js",
"web/client/epics/globeswitcher.js",
Expand All @@ -157,6 +158,7 @@
"jsapi": "web/client/jsapi/MapStore2.js",
"plugins": [
"web/client/plugins/index.jsdoc",
"web/client/plugins/AutoMapUpdate.jsx",
"web/client/plugins/BackgroundSelector.jsx",
"web/client/plugins/BackgroundSwitcher.jsx",
"web/client/plugins/DrawerMenu.jsx",
Expand Down
12 changes: 11 additions & 1 deletion web/client/actions/__tests__/notifications-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ var {
error,
info,
hide,
clear
clear,
dispatchAction
} = require('../notifications');

describe('Test correctness of the notifications actions', () => {
Expand Down Expand Up @@ -66,6 +67,15 @@ describe('Test correctness of the notifications actions', () => {
expect(action.level).toBe('info');
expect(action.uid).toExist();
});
it('dispatchAction', () => {
const customAction = () => {
return {
type: 'CUSTOM_ACTION'
};
};
const action = dispatchAction(customAction);
expect(action().type).toBe('CUSTOM_ACTION');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pass simple object. The thunks will be removed in the next future.

});


});
2 changes: 1 addition & 1 deletion web/client/actions/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,4 +93,4 @@ function loadMapInfo(url, mapId) {
}
module.exports = {MAP_CONFIG_LOADED, MAP_CONFIG_LOAD_ERROR,
MAP_INFO_LOAD_START, MAP_INFO_LOADED, MAP_INFO_LOAD_ERROR,
loadMapConfig, loadMapInfo, configureMap, configureError};
loadMapConfig, loadMapInfo, configureMap, configureError, mapInfoLoaded};
12 changes: 11 additions & 1 deletion web/client/actions/notifications.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,15 @@ function clear() {
};
}

/**
* Dispatch a custom action on callback
* @memberof actions.notifications
* @returns {object} action
*/
function dispatchAction(action) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure this is needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it's needed because we need to send custom action when the notification button has been toggled

return action;
}

/**
* actions for notifications
* @name notifications
Expand All @@ -105,5 +114,6 @@ module.exports = {
error,
info,
hide,
clear
clear,
dispatchAction
};
10 changes: 8 additions & 2 deletions web/client/actions/security.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const AuthenticationAPI = require('../api/GeoStoreDAO');
const SecurityUtils = require('../utils/SecurityUtils');

const {loadMaps} = require('./maps');
const {loadMapInfo} = require('./config');
const ConfigUtils = require('../utils/ConfigUtils');

const LOGIN_SUBMIT = 'LOGIN_SUBMIT';
Expand Down Expand Up @@ -67,10 +68,15 @@ function logoutWithReload() {
}

function login(username, password) {
return (dispatch) => {
AuthenticationAPI.login(username, password).then((response) => {
return (dispatch, state) => {
return AuthenticationAPI.login(username, password).then((response) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This refreshes can go easily in an epic. Doing it here can cause unwanted side effects.

You should monitor the login/logout actions to reload MapInfo, until you change page.

dispatch(loginSuccess(response, username, password, AuthenticationAPI.authProviderName));
dispatch(loadMaps(false, ConfigUtils.getDefaults().initialMapFilter || "*"));
let s = state();
let id = s.map && s.map.present && s.map.present.mapId;
if (id) {
dispatch(loadMapInfo(ConfigUtils.getConfigProp("geoStoreUrl") + "extjs/resource/" + id, id));
}
}).catch((e) => {
dispatch(loginFail(e));
});
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
const PropTypes = require('prop-types');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For new files, let's move this in the right place, please.

/*
* Copyright 2017, 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 {ProgressBar, Col, Row} = require('react-bootstrap');
const Message = require('../../../../components/I18N/Message');
require('./css/overlayprogressbar.css');

class OverlayProgressBar extends React.Component {
static propTypes = {
loading: PropTypes.bool,
count: PropTypes.number,
length: PropTypes.number,
label: PropTypes.string,
unit: PropTypes.string
};

static defaultProps = {
loading: false,
count: 0,
length: 0,
label: 'autorefresh.updating',
unit: 'autorefresh.layers'
};

render() {
return this.props.loading ? (
<div className="overlay-spinner-container">
<div>
<Row>
<Col xs="12" className="text-center overlay-spinner-label"><h3><Message msgId={this.props.label}/></h3></Col>
</Row>
<Row>
<Col xs="12"><ProgressBar active now={100 * this.props.count / this.props.length} /></Col>
</Row>
<Row>
<Col xs="12" className="text-center overlay-spinner-label"><h3>{this.props.count + ' '} <Message msgId="autorefresh.of"/>{ ' ' + this.props.length + ' '}<Message msgId={this.props.unit}/></h3></Col>
</Row>
</div>
</div>
) : null;
}
}

module.exports = OverlayProgressBar;
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Copyright 2017, 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 expect = require('expect');

const React = require('react');
const ReactDOM = require('react-dom');
const OverlayProgressBar = require('../OverlayProgressBar');

describe("test the OverlayProgressBar", () => {
beforeEach((done) => {
document.body.innerHTML = '<div id="container"></div>';
setTimeout(done);
});

afterEach((done) => {
ReactDOM.unmountComponentAtNode(document.getElementById("container"));
document.body.innerHTML = '';
setTimeout(done);
});

it('test OverlayProgressBar starts loading', () => {
const overlayProgressBar = ReactDOM.render(<OverlayProgressBar loading/>, document.getElementById("container"));
expect(overlayProgressBar).toExist();
const node = ReactDOM.findDOMNode(overlayProgressBar);
expect(node.children.length).toBe(1);
});

it('test OverlayProgressBar stops loading', () => {
const overlayProgressBar = ReactDOM.render(<OverlayProgressBar loading={false}/>, document.getElementById("container"));
expect(overlayProgressBar).toExist();
const node = ReactDOM.findDOMNode(overlayProgressBar);
expect(node).toBe(null);
});

});
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
.overlay-spinner-container {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 9998;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
}

.overlay-spinner-container > div {
width: 80%;
}

.overlay-spinner-label {
color: #fff;
}
9 changes: 6 additions & 3 deletions web/client/components/notifications/NotificationContainer.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ var LocaleUtils = require('../../utils/LocaleUtils');
class NotificationContainer extends React.Component {
static propTypes = {
notifications: PropTypes.array,
onRemove: PropTypes.func
onRemove: PropTypes.func,
onDispatch: PropTypes.func
};

static contextTypes = {
Expand All @@ -43,7 +44,8 @@ class NotificationContainer extends React.Component {

static defaultProps = {
notifications: [],
onRemove: () => {}
onRemove: () => {},
onDispatch: () => {}
};

componentDidMount() {
Expand Down Expand Up @@ -81,7 +83,8 @@ class NotificationContainer extends React.Component {
title: LocaleUtils.getMessageById(this.context.messages, notification.title) || notification.title,
message: LocaleUtils.getMessageById(this.context.messages, notification.message) || notification.message,
action: notification.action && {
label: LocaleUtils.getMessageById(this.context.messages, notification.action.label) || notification.action.label
label: LocaleUtils.getMessageById(this.context.messages, notification.action.label) || notification.action.label,
callback: notification.action.dispatch ? () => { this.props.onDispatch(notification.action.dispatch); } : notification.action.callback
},
onRemove: () => {
this.props.onRemove(notification.uid);
Expand Down
79 changes: 79 additions & 0 deletions web/client/epics/__tests__/automapupdate-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* Copyright 2017, 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.
*/

var expect = require('expect');

const configureMockStore = require('redux-mock-store').default;
const {createEpicMiddleware, combineEpics } = require('redux-observable');
const {configureMap, mapInfoLoaded} = require('../../actions/config');
const {SHOW_NOTIFICATION} = require('../../actions/notifications');

const {manageAutoMapUpdate} = require('../automapupdate');
const rootEpic = combineEpics(manageAutoMapUpdate);
const epicMiddleware = createEpicMiddleware(rootEpic);
const mockStore = configureMockStore([epicMiddleware]);

describe('autorefresh Epics', () => {
let store;
beforeEach(() => {
store = mockStore();
});

afterEach(() => {
epicMiddleware.replaceEpic(rootEpic);
});

it('refreshes map', (done) => {

let configuration = configureMap({}, "id");

let information = mapInfoLoaded({
canEdit: true
}, "id");

store.dispatch(configuration);
store.dispatch(information);

setTimeout( () => {
try {
const actions = store.getActions();
expect(actions.length).toBe(3);
expect(actions[2].type).toBe(SHOW_NOTIFICATION);
} catch (e) {
return done(e);
}
done();
}, 500);

});

it('refreshes map without login', (done) => {

let configuration = configureMap({
version: 2
}, "id");

let information = mapInfoLoaded({
canEdit: true
}, "id");

store.dispatch(configuration);
store.dispatch(information);

setTimeout( () => {
try {
const actions = store.getActions();
expect(actions.length).toBe(2);
} catch (e) {
return done(e);
}
done();
}, 500);

});
});
72 changes: 72 additions & 0 deletions web/client/epics/automapupdate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* Copyright 2017, 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 Rx = require('rxjs');
const {refreshLayers, LAYERS_REFRESHED, LAYERS_REFRESH_ERROR} = require('../actions/layers');
const {MAP_CONFIG_LOADED, MAP_INFO_LOADED} = require('../actions/config');
const {warning, success} = require('../actions/notifications');
const {toggleControl} = require('../actions/controls');

/**
* When map has been loaded, it sends a notification if the version is less than 2 and users has write permission.
* @param {external:Observable} action$ manages `MAP_CONFIG_LOADED` and `MAP_INFO_LOADED`.
* @memberof epics.automapupdate
* @return {external:Observable}
*/

const manageAutoMapUpdate = action$ =>
action$.ofType(MAP_CONFIG_LOADED)
.switchMap((mapConfigLoaded) =>
action$.ofType(MAP_INFO_LOADED)
.switchMap((mapInfoLoaded) => {
const version = mapConfigLoaded.config && mapConfigLoaded.config.version || 1;
const canEdit = mapInfoLoaded.info && mapInfoLoaded.info.canEdit || false;
let layers = mapConfigLoaded.config && mapConfigLoaded.config.map && mapConfigLoaded.config.map.layers && mapConfigLoaded.config.map.layers.filter((l) => l.type === 'wms' && l.group !== 'background') || [];
const options = {bbox: true, search: true, dimensions: true, title: true};
return version < 2 && canEdit ?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

change title to false

Rx.Observable.of(warning({
title: "notification.warning",
message: "notification.updateOldMap",
action: {
label: "notification.update",
dispatch: refreshLayers(layers, options)
},
autoDismiss: 12,
position: "tc"
})).concat(
action$.ofType(LAYERS_REFRESHED, LAYERS_REFRESH_ERROR)
.bufferCount(layers.length)
.switchMap((refreshed) => {
const errors = refreshed.filter((l) => l.type === LAYERS_REFRESH_ERROR);
const notification = errors.length > 0 ?
warning({
title: "notification.warning",
message: "notification.warningSaveUpdatedMap",
autoDismiss: 6,
position: "tc"
})
:
success({
title: "notification.success",
message: "notification.saveUpdatedMap",
autoDismiss: 6,
position: "tc"
});
return Rx.Observable.of(notification, toggleControl('save'));
}))
: Rx.Observable.empty();
}));

/**
* Epics for update old map
* @name epics.automapupdate
* @type {Object}
*/

module.exports = {
manageAutoMapUpdate
};
Loading