Skip to content

Commit c25dead

Browse files
committed
feat: add dashboard vizualisation with recharts
1 parent bada1b2 commit c25dead

15 files changed

+946
-59
lines changed

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@
116116
"react-router": "5.1.2",
117117
"react-router-dom": "5.1.2",
118118
"react-split-pane": "0.1.89",
119+
"recharts": "1.8.5",
119120
"redux": "4.0.5",
120121
"redux-devtools-extension": "2.13.8",
121122
"redux-promise": "0.6.0",

public/app/listeners/getUserFolder.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ const { VAR_FOLDER } = require('../config/config');
33
const { ERROR_GENERAL } = require('../config/errors');
44
const logger = require('../logger');
55

6-
const getUserFolder = (mainWindow) => async () => {
6+
const getUserFolder = mainWindow => async () => {
77
try {
88
mainWindow.webContents.send(GET_USER_FOLDER_CHANNEL, VAR_FOLDER);
99
} catch (e) {

src/App.js

+3-7
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import LoadSpace from './components/LoadSpace';
1717
import SpaceScreen from './components/space/SpaceScreen';
1818
import DeveloperScreen from './components/developer/DeveloperScreen';
1919
import { OnlineTheme, OfflineTheme } from './themes';
20-
import ActionDashboard from './components/actionDashboard/ActionDashboard';
20+
import Dashboard from './components/dashboard/Dashboard';
2121
import {
2222
SETTINGS_PATH,
2323
SPACE_PATH,
@@ -26,7 +26,7 @@ import {
2626
VISIT_PATH,
2727
LOAD_SPACE_PATH,
2828
DEVELOPER_PATH,
29-
ACTION_DASHBOARD_PATH,
29+
DASHBOARD_PATH,
3030
} from './config/paths';
3131
import {
3232
getGeolocation,
@@ -161,11 +161,7 @@ export class App extends Component {
161161
path={DEVELOPER_PATH}
162162
component={DeveloperScreen}
163163
/>
164-
<Route
165-
exact
166-
path={ACTION_DASHBOARD_PATH}
167-
component={ActionDashboard}
168-
/>
164+
<Route exact path={DASHBOARD_PATH} component={Dashboard} />
169165
</Switch>
170166
</div>
171167
</Router>

src/components/common/MainMenu.js

+6-5
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import CodeIcon from '@material-ui/icons/Code';
1010
import ListItemText from '@material-ui/core/ListItemText';
1111
import List from '@material-ui/core/List';
1212
import SearchIcon from '@material-ui/icons/Search';
13+
import ShowChartIcon from '@material-ui/icons/ShowChart';
1314
import Language from '@material-ui/icons/Language';
1415
import PublishIcon from '@material-ui/icons/Publish';
1516
import SettingsIcon from '@material-ui/icons/Settings';
@@ -22,7 +23,7 @@ import {
2223
SPACES_NEARBY_PATH,
2324
VISIT_PATH,
2425
DEVELOPER_PATH,
25-
ACTION_DASHBOARD_PATH,
26+
DASHBOARD_PATH,
2627
} from '../../config/paths';
2728

2829
export class MainMenu extends Component {
@@ -149,14 +150,14 @@ export class MainMenu extends Component {
149150
<ListItemText primary={t('Settings')} />
150151
</MenuItem>
151152
<MenuItem
152-
onClick={() => this.handleClick(ACTION_DASHBOARD_PATH)}
153+
onClick={() => this.handleClick(DASHBOARD_PATH)}
153154
button
154-
selected={path === ACTION_DASHBOARD_PATH}
155+
selected={path === DASHBOARD_PATH}
155156
>
156157
<ListItemIcon>
157-
<SettingsIcon />
158+
<ShowChartIcon />
158159
</ListItemIcon>
159-
<ListItemText primary={t('Action Dashboard')} />
160+
<ListItemText primary={t('Dashboard')} />
160161
</MenuItem>
161162
{this.renderDeveloperMode()}
162163
</List>

src/components/common/MainMenu.test.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212
LOAD_SPACE_PATH,
1313
SETTINGS_PATH,
1414
DEVELOPER_PATH,
15-
ACTION_DASHBOARD_PATH,
15+
DASHBOARD_PATH,
1616
} from '../../config/paths';
1717

1818
// offline: 2, online: 5
@@ -106,7 +106,7 @@ describe('<MainMenu />', () => {
106106
[true, LOAD_SPACE_PATH, 'Load'],
107107
[true, SETTINGS_PATH, 'Settings'],
108108
[true, DEVELOPER_PATH, 'Developer'],
109-
[true, ACTION_DASHBOARD_PATH, 'Action Dashboard'],
109+
[true, DASHBOARD_PATH, 'Dashboard'],
110110
])('<MainMenu /> selects one MenuItem', (developerMode, path, text) => {
111111
it(`select path=${path} (developerMode = ${developerMode})`, () => {
112112
const props = createMainMenuProps(developerMode, path);

src/components/common/__snapshots__/MainMenu.test.js.snap

+4-4
Original file line numberDiff line numberDiff line change
@@ -128,10 +128,10 @@ exports[`<MainMenu /> <MainMenu /> with developerMode = false renders correctly
128128
selected={false}
129129
>
130130
<WithStyles(ForwardRef(ListItemIcon))>
131-
<SettingsIcon />
131+
<ShowChartIcon />
132132
</WithStyles(ForwardRef(ListItemIcon))>
133133
<WithStyles(ForwardRef(ListItemText))
134-
primary="Action Dashboard"
134+
primary="Dashboard"
135135
/>
136136
</WithStyles(ForwardRef(MenuItem))>
137137
</WithStyles(ForwardRef(List))>
@@ -265,10 +265,10 @@ exports[`<MainMenu /> <MainMenu /> with developerMode = true renders correctly 1
265265
selected={false}
266266
>
267267
<WithStyles(ForwardRef(ListItemIcon))>
268-
<SettingsIcon />
268+
<ShowChartIcon />
269269
</WithStyles(ForwardRef(ListItemIcon))>
270270
<WithStyles(ForwardRef(ListItemText))
271-
primary="Action Dashboard"
271+
primary="Dashboard"
272272
/>
273273
</WithStyles(ForwardRef(MenuItem))>
274274
<WithStyles(ForwardRef(MenuItem))
+132
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
import React, { PureComponent } from 'react';
2+
import { connect } from 'react-redux';
3+
import _ from 'lodash';
4+
import PropTypes from 'prop-types';
5+
import {
6+
BarChart,
7+
Bar,
8+
XAxis,
9+
YAxis,
10+
CartesianGrid,
11+
Tooltip,
12+
Legend,
13+
ResponsiveContainer,
14+
} from 'recharts';
15+
import Typography from '@material-ui/core/Typography';
16+
import { withStyles } from '@material-ui/core';
17+
import { getDatabase, setDatabase } from '../../actions';
18+
import Loader from '../common/Loader';
19+
import Styles from '../../Styles';
20+
21+
class ActionBarChart extends PureComponent {
22+
static propTypes = {
23+
classes: PropTypes.shape({
24+
root: PropTypes.string.isRequired,
25+
appBar: PropTypes.string.isRequired,
26+
appBarShift: PropTypes.string.isRequired,
27+
menuButton: PropTypes.string.isRequired,
28+
hide: PropTypes.string.isRequired,
29+
drawer: PropTypes.string.isRequired,
30+
drawerPaper: PropTypes.string.isRequired,
31+
drawerHeader: PropTypes.string.isRequired,
32+
content: PropTypes.string.isRequired,
33+
contentShift: PropTypes.string.isRequired,
34+
developer: PropTypes.string.isRequired,
35+
screenTitle: PropTypes.string.isRequired,
36+
}).isRequired,
37+
theme: PropTypes.shape({
38+
palette: PropTypes.shape({
39+
type: PropTypes.string.isRequired,
40+
primary: PropTypes.arrayOf(PropTypes.string).isRequired,
41+
}).isRequired,
42+
}).isRequired,
43+
history: PropTypes.shape({
44+
replace: PropTypes.func.isRequired,
45+
}).isRequired,
46+
i18n: PropTypes.shape({
47+
changeLanguage: PropTypes.func.isRequired,
48+
}).isRequired,
49+
database: PropTypes.shape({
50+
user: PropTypes.object,
51+
spaces: PropTypes.array,
52+
actions: PropTypes.array,
53+
}),
54+
};
55+
56+
static defaultProps = {
57+
database: {},
58+
};
59+
60+
render() {
61+
const { database, theme } = this.props;
62+
const {
63+
palette: { primary, type },
64+
} = theme;
65+
66+
if (!database || _.isEmpty(database)) {
67+
return <Loader />;
68+
}
69+
70+
const { spaces, actions } = database;
71+
const data =
72+
// group actions by space id
73+
Object.entries(_.groupBy(actions, 'spaceId'))
74+
// map space id to corresponding space name
75+
// reduce actions by count number
76+
.map(([id, actionElements]) => ({
77+
space: _.find(spaces, ['id', id]).name,
78+
count: actionElements.length,
79+
}));
80+
81+
return (
82+
<>
83+
<Typography variant="h5">Action Count Per Space</Typography>
84+
<ResponsiveContainer width="100%" height="100%">
85+
<BarChart
86+
width="100%"
87+
height="100%"
88+
data={data}
89+
margin={{
90+
top: 5,
91+
right: 30,
92+
left: 20,
93+
bottom: 5,
94+
}}
95+
>
96+
<CartesianGrid strokeDasharray="3 3" />
97+
<XAxis dataKey="space" />
98+
<YAxis
99+
label={{
100+
value: 'Action count',
101+
angle: -90,
102+
position: 'insideLeft',
103+
}}
104+
/>
105+
<Tooltip />
106+
<Legend />
107+
<Bar name="action count" dataKey="count" fill={primary[type]} />
108+
</BarChart>
109+
</ResponsiveContainer>
110+
</>
111+
);
112+
}
113+
}
114+
115+
const mapStateToProps = ({ Developer }) => ({
116+
database: Developer.get('database'),
117+
});
118+
119+
const mapDispatchToProps = {
120+
dispatchGetDatabase: getDatabase,
121+
dispatchSetDatabase: setDatabase,
122+
};
123+
124+
const ConnectedComponent = connect(
125+
mapStateToProps,
126+
mapDispatchToProps
127+
)(ActionBarChart);
128+
129+
const StyledComponent = withStyles(Styles, { withTheme: true })(
130+
ConnectedComponent
131+
);
132+
export default StyledComponent;

src/components/actionDashboard/ActionEditor.js src/components/dashboard/ActionEditor.js

+3-20
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import _ from 'lodash';
33
import { connect } from 'react-redux';
44
import ReactJson from 'react-json-view';
55
import PropTypes from 'prop-types';
6-
import Button from '@material-ui/core/Button';
76
import Typography from '@material-ui/core/Typography';
87
import { withTranslation } from 'react-i18next';
98
import { withStyles } from '@material-ui/core';
@@ -47,32 +46,16 @@ export class ActionEditor extends Component {
4746
};
4847

4948
render() {
50-
const { database, t, classes } = this.props;
49+
const { database, t } = this.props;
5150

5251
if (!database || _.isEmpty(database)) {
5352
return <Loader />;
5453
}
5554

5655
return (
5756
<div>
58-
<Typography variant="h6">{t('Manually Edit the Database')}</Typography>
59-
<ReactJson
60-
name="actions"
61-
collapsed
62-
src={database.actions}
63-
onEdit={this.handleEdit}
64-
onAdd={this.handleEdit}
65-
onDelete={this.handleEdit}
66-
/>
67-
<br />
68-
<Button
69-
variant="contained"
70-
className={classes.button}
71-
onClick={this.handleUseSampleDatabase}
72-
color="primary"
73-
>
74-
{t('Use Sample Database')}
75-
</Button>
57+
<Typography variant="h6">{t('View Action Database')}</Typography>
58+
<ReactJson name="actions" collapsed src={database.actions} />
7659
</div>
7760
);
7861
}

0 commit comments

Comments
 (0)