Skip to content

Commit fc4a2b9

Browse files
committed
Closes #3534 switch content tab when 0 results (maps/dashboards) (#3733)
(cherry picked from commit 5c8f7e9)
1 parent 25df74b commit fc4a2b9

9 files changed

Lines changed: 227 additions & 22 deletions

File tree

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* Copyright 2018, GeoSolutions Sas.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
const expect = require('expect');
10+
11+
const {
12+
onTabSelected,
13+
ON_TAB_SELECTED
14+
} = require('../contenttabs');
15+
16+
describe('Test contenttabs actions', () => {
17+
18+
it('Test onTabSelected action creator', () => {
19+
const id = 'layer_001';
20+
const retval = onTabSelected(id);
21+
expect(retval).toExist();
22+
expect(retval.id).toBe(id);
23+
expect(retval.type).toBe(ON_TAB_SELECTED);
24+
});
25+
});

web/client/actions/contenttabs.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* Copyright 2019, GeoSolutions Sas.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
const ON_TAB_SELECTED = 'CONTENT_TABS:ON_TAB_SELECTED';
10+
11+
/**
12+
* Select Tab
13+
* @memberof actions.contenttabs
14+
* @param {string} id tab id
15+
*
16+
* @return {object} of type `ON_TAB_SELECTED` with tab id
17+
*/
18+
19+
const onTabSelected = (id) => {
20+
return {
21+
type: ON_TAB_SELECTED,
22+
id
23+
};
24+
};
25+
26+
module.exports = {onTabSelected, ON_TAB_SELECTED};
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* Copyright 2019, GeoSolutions Sas.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
var expect = require('expect');
10+
const {testEpic} = require('./epicTestUtils');
11+
12+
const {MAPS_LOAD_MAP, MAPS_LIST_LOADED} = require("../../actions/maps");
13+
const {DASHBOARDS_LIST_LOADED} = require("../../actions/dashboards");
14+
const {updateMapsDashboardTabs} = require("../contenttabs");
15+
16+
describe('Test Maps Dashboard Content Tabs', () => {
17+
it('test updateMapsDashboardTabs flow ', done => {
18+
const act = [
19+
{type: MAPS_LOAD_MAP},
20+
{type: MAPS_LIST_LOADED, maps: {totalCount: 0}},
21+
{type: DASHBOARDS_LIST_LOADED, totalCount: 1}
22+
];
23+
testEpic(updateMapsDashboardTabs, 1, act, (res) => {
24+
const action = res[0];
25+
expect(action).toExist();
26+
expect(action.id).toBe("dashboards");
27+
done();
28+
}, {contenttabs: {selected: "maps"}});
29+
});
30+
});

web/client/epics/contenttabs.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Copyright 2019, GeoSolutions Sas.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
const Rx = require('rxjs');
10+
const {findKey} = require('lodash');
11+
const {MAPS_LOAD_MAP, MAPS_LIST_LOADED} = require("../actions/maps");
12+
const {
13+
DASHBOARDS_LIST_LOADED
14+
} = require('../actions/dashboards');
15+
const {onTabSelected} = require("../actions/contenttabs");
16+
/**
17+
* Update Maps and Dashboards counts to select contenttabs each tab has to have a key in its ContentTab configuration
18+
* @param {object} action
19+
*/
20+
const updateMapsDashboardTabs = (action$, {getState = () => {}}) =>
21+
action$.ofType(MAPS_LOAD_MAP)
22+
.switchMap(() => {
23+
return Rx.Observable.forkJoin(action$.ofType(MAPS_LIST_LOADED).take(1), action$.ofType(DASHBOARDS_LIST_LOADED).take(1))
24+
.switchMap((r) => {
25+
const results = {maps: r[0].maps, dashboards: r[1] };
26+
const {contenttabs = {}} = getState() || {};
27+
const {selected} = contenttabs;
28+
if (results[selected] && results[selected].totalCount === 0) {
29+
const id = findKey(results, ({totalCount}) => totalCount > 0);
30+
if (id) {
31+
return Rx.Observable.of(onTabSelected(id));
32+
}
33+
}
34+
return Rx.Observable.empty();
35+
});
36+
});
37+
38+
39+
module.exports = {updateMapsDashboardTabs};

web/client/plugins/ContentTabs.jsx

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,37 @@ const { Row, Col, Grid, Nav, NavItem} = require('react-bootstrap');
1111
const ToolsContainer = require('./containers/ToolsContainer');
1212
const Message = require('../components/I18N/Message');
1313

14-
const {withState} = require('recompose');
14+
const {connect} = require('react-redux');
1515
const assign = require('object-assign');
16+
const {createSelector} = require('reselect');
17+
const {onTabSelected} = require('../actions/contenttabs');
18+
19+
const selectedSelector = createSelector(
20+
state => state && state.contenttabs && state.contenttabs.selected,
21+
selected => ({ selected })
22+
);
23+
1624
const DefaultTitle = ({ item = {}, index }) => <span>{ item.title || `Tab ${index}` }</span>;
25+
26+
/**
27+
* @name ContentTabs
28+
* @memberof plugins
29+
* @class
30+
* @classdesc
31+
* ContentTabs plugin is used in home page allowing to switch between contained plugins (i.e. Maps and Dashboards plugins).
32+
* <br/>Each contained plugin has to have the contenttabs configuration property in its plugin configuration.
33+
* The key property is mandatory following and position property is used to order give tabs order.
34+
* An example of the contenttabs config in Maps plugin
35+
* @example
36+
* ContentTabs: {
37+
* name: 'maps',
38+
* key: 'maps',
39+
* TitleComponent:
40+
* connect(mapsCountSelector)(({ count = "" }) => <Message msgId="resources.maps.title" msgParams={{ count: count + "" }} />),
41+
* position: 1,
42+
* tool: true
43+
* }
44+
*/
1745
class ContentTabs extends React.Component {
1846
static propTypes = {
1947
selected: PropTypes.number,
@@ -22,6 +50,7 @@ class ContentTabs extends React.Component {
2250
items: PropTypes.array,
2351
id: PropTypes.string,
2452
onSelect: PropTypes.func
53+
2554
};
2655
static defaultProps = {
2756
selected: 0,
@@ -44,21 +73,21 @@ class ContentTabs extends React.Component {
4473
container={(props) => <div {...props}>
4574
<div style={{marginTop: "10px"}}>
4675
<Nav bsStyle="tabs" activeKey="1" onSelect={k => this.props.onSelect(k)}>
47-
{this.props.items.map(
76+
{[...this.props.items].sort((a, b) => a.position - b.position).map(
4877
({ TitleComponent = DefaultTitle, ...item }, idx) =>
4978
(<NavItem
50-
active={idx === this.props.selected}
79+
active={(item.key || idx) === this.props.selected}
5180
eventKey={item.key || idx} >
5281
<TitleComponent index={idx} item={item} />
53-
</NavItem>))}
82+
</NavItem>))}
5483
</Nav>
5584
</div>
5685
{props.children}
5786
</div>}
5887
toolStyle="primary"
5988
stateSelector="contentTabs"
6089
activeStyle="default"
61-
tools={[...this.props.items].sort((a, b) => a.position - b.position).filter( (e, i) => i === this.props.selected)}
90+
tools={[...this.props.items].sort((a, b) => a.position - b.position).filter( ({key}, i) => (key || i) === this.props.selected)}
6291
panels={[]}
6392
/></Col>
6493
</Row>
@@ -69,13 +98,16 @@ class ContentTabs extends React.Component {
6998
}
7099

71100
module.exports = {
72-
ContentTabsPlugin: assign(withState('selected', 'onSelect', 0)(ContentTabs), {
101+
ContentTabsPlugin: assign(connect(selectedSelector, {
102+
onSelect: onTabSelected
103+
})(ContentTabs), {
73104
NavMenu: {
74105
position: 2,
75106
label: <Message msgId="resources.contents.title" />,
76107
linkId: '#content-tabs',
77108
glyph: 'dashboard'
78109
}
79110
}),
80-
reducers: {}
111+
reducers: {contenttabs: require('../reducers/contenttabs')},
112+
epics: require('../epics/contenttabs')
81113
};

web/client/plugins/Dashboards.jsx

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@ const DashboardGrid = require('./dashboard/DashboardsGrid');
2424
const PaginationToolbar = require('./dashboard/PaginationToolbar');
2525
const EmptyDashboardsView = require('./dashboard/EmptyDashboardsView');
2626

27+
const dashboardsCountSelector = createSelector(
28+
totalCountSelector,
29+
count => ({ count })
30+
);
31+
32+
2733
class Dashboards extends React.Component {
2834
static propTypes = {
2935
mapType: PropTypes.string,
@@ -110,12 +116,10 @@ module.exports = {
110116
glyph: 'dashboard'
111117
},
112118
ContentTabs: {
113-
name: 'maps',
119+
name: 'dashboards',
120+
key: 'dashboards',
114121
TitleComponent:
115-
connect(createSelector(
116-
totalCountSelector,
117-
count => ({count})
118-
))(({ count = ""}) => <Message msgId="resources.dashboards.title" msgParams={{ count: count + "" }} />),
122+
connect(dashboardsCountSelector)(({ count = ""}) => <Message msgId="resources.dashboards.title" msgParams={{ count: count + "" }} />),
119123
position: 2,
120124
tool: true,
121125
priority: 1

web/client/plugins/Maps.jsx

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ const MetadataModal = require('./maps/MetadataModal');
2525

2626
const {loadMaps, setShowMapDetails} = require('../actions/maps');
2727

28+
const mapsCountSelector = createSelector(
29+
totalCountSelector,
30+
count => ({ count })
31+
);
32+
2833
const PaginationToolbar = connect((state) => {
2934
if (!state.maps ) {
3035
return {};
@@ -89,12 +94,6 @@ class Maps extends React.Component {
8994
maps: []
9095
};
9196

92-
componentDidMount() {
93-
// if there is a change in the search text it uses that before the initialMapFilter
94-
this.props.loadMaps(ConfigUtils.getDefaults().geoStoreUrl, this.props.searchText || ConfigUtils.getDefaults().initialMapFilter || "*", this.props.mapsOptions);
95-
this.props.setShowMapDetails(this.props.showMapDetails);
96-
}
97-
9897
render() {
9998
return (<MapsGrid
10099
maps={this.props.maps}
@@ -134,11 +133,9 @@ module.exports = {
134133
},
135134
ContentTabs: {
136135
name: 'maps',
136+
key: 'maps',
137137
TitleComponent:
138-
connect(createSelector(
139-
totalCountSelector,
140-
count => ({ count })
141-
))(({ count = "" }) => <Message msgId="resources.maps.title" msgParams={{ count: count + "" }} />),
138+
connect(mapsCountSelector)(({ count = "" }) => <Message msgId="resources.maps.title" msgParams={{ count: count + "" }} />),
142139
position: 1,
143140
tool: true,
144141
priority: 1
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
* Copyright 2019, GeoSolutions Sas.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
const expect = require('expect');
10+
11+
const {
12+
onTabSelected
13+
} = require('../../actions/contenttabs');
14+
15+
const contenttabs = require('../contenttabs');
16+
17+
describe('Test contenttabs reducer', () => {
18+
19+
it('select correct tab', () => {
20+
const id = 'dashboard';
21+
const state = contenttabs({selected: "maps"}, onTabSelected(id));
22+
expect(state).toEqual(
23+
{
24+
selected: id
25+
}
26+
);
27+
});
28+
29+
});

web/client/reducers/contenttabs.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/*
2+
* Copyright 2019, GeoSolutions Sas.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
const {
10+
ON_TAB_SELECTED
11+
} = require('../actions/contenttabs');
12+
13+
function contenttabs(state = {selected: "maps"}, action) {
14+
switch (action.type) {
15+
case ON_TAB_SELECTED: {
16+
return {selected: action.id || "maps"};
17+
}
18+
default:
19+
return state;
20+
}
21+
}
22+
23+
module.exports = contenttabs;

0 commit comments

Comments
 (0)