Skip to content

Commit

Permalink
feat: add dashboard vizualisation with recharts
Browse files Browse the repository at this point in the history
  • Loading branch information
pyphilia committed Mar 16, 2020
1 parent bada1b2 commit c25dead
Show file tree
Hide file tree
Showing 15 changed files with 946 additions and 59 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@
"react-router": "5.1.2",
"react-router-dom": "5.1.2",
"react-split-pane": "0.1.89",
"recharts": "1.8.5",
"redux": "4.0.5",
"redux-devtools-extension": "2.13.8",
"redux-promise": "0.6.0",
Expand Down
2 changes: 1 addition & 1 deletion public/app/listeners/getUserFolder.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ const { VAR_FOLDER } = require('../config/config');
const { ERROR_GENERAL } = require('../config/errors');
const logger = require('../logger');

const getUserFolder = (mainWindow) => async () => {
const getUserFolder = mainWindow => async () => {
try {
mainWindow.webContents.send(GET_USER_FOLDER_CHANNEL, VAR_FOLDER);
} catch (e) {
Expand Down
10 changes: 3 additions & 7 deletions src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import LoadSpace from './components/LoadSpace';
import SpaceScreen from './components/space/SpaceScreen';
import DeveloperScreen from './components/developer/DeveloperScreen';
import { OnlineTheme, OfflineTheme } from './themes';
import ActionDashboard from './components/actionDashboard/ActionDashboard';
import Dashboard from './components/dashboard/Dashboard';
import {
SETTINGS_PATH,
SPACE_PATH,
Expand All @@ -26,7 +26,7 @@ import {
VISIT_PATH,
LOAD_SPACE_PATH,
DEVELOPER_PATH,
ACTION_DASHBOARD_PATH,
DASHBOARD_PATH,
} from './config/paths';
import {
getGeolocation,
Expand Down Expand Up @@ -161,11 +161,7 @@ export class App extends Component {
path={DEVELOPER_PATH}
component={DeveloperScreen}
/>
<Route
exact
path={ACTION_DASHBOARD_PATH}
component={ActionDashboard}
/>
<Route exact path={DASHBOARD_PATH} component={Dashboard} />
</Switch>
</div>
</Router>
Expand Down
11 changes: 6 additions & 5 deletions src/components/common/MainMenu.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import CodeIcon from '@material-ui/icons/Code';
import ListItemText from '@material-ui/core/ListItemText';
import List from '@material-ui/core/List';
import SearchIcon from '@material-ui/icons/Search';
import ShowChartIcon from '@material-ui/icons/ShowChart';
import Language from '@material-ui/icons/Language';
import PublishIcon from '@material-ui/icons/Publish';
import SettingsIcon from '@material-ui/icons/Settings';
Expand All @@ -22,7 +23,7 @@ import {
SPACES_NEARBY_PATH,
VISIT_PATH,
DEVELOPER_PATH,
ACTION_DASHBOARD_PATH,
DASHBOARD_PATH,
} from '../../config/paths';

export class MainMenu extends Component {
Expand Down Expand Up @@ -149,14 +150,14 @@ export class MainMenu extends Component {
<ListItemText primary={t('Settings')} />
</MenuItem>
<MenuItem
onClick={() => this.handleClick(ACTION_DASHBOARD_PATH)}
onClick={() => this.handleClick(DASHBOARD_PATH)}
button
selected={path === ACTION_DASHBOARD_PATH}
selected={path === DASHBOARD_PATH}
>
<ListItemIcon>
<SettingsIcon />
<ShowChartIcon />
</ListItemIcon>
<ListItemText primary={t('Action Dashboard')} />
<ListItemText primary={t('Dashboard')} />
</MenuItem>
{this.renderDeveloperMode()}
</List>
Expand Down
4 changes: 2 additions & 2 deletions src/components/common/MainMenu.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
LOAD_SPACE_PATH,
SETTINGS_PATH,
DEVELOPER_PATH,
ACTION_DASHBOARD_PATH,
DASHBOARD_PATH,
} from '../../config/paths';

// offline: 2, online: 5
Expand Down Expand Up @@ -106,7 +106,7 @@ describe('<MainMenu />', () => {
[true, LOAD_SPACE_PATH, 'Load'],
[true, SETTINGS_PATH, 'Settings'],
[true, DEVELOPER_PATH, 'Developer'],
[true, ACTION_DASHBOARD_PATH, 'Action Dashboard'],
[true, DASHBOARD_PATH, 'Dashboard'],
])('<MainMenu /> selects one MenuItem', (developerMode, path, text) => {
it(`select path=${path} (developerMode = ${developerMode})`, () => {
const props = createMainMenuProps(developerMode, path);
Expand Down
8 changes: 4 additions & 4 deletions src/components/common/__snapshots__/MainMenu.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -128,10 +128,10 @@ exports[`<MainMenu /> <MainMenu /> with developerMode = false renders correctly
selected={false}
>
<WithStyles(ForwardRef(ListItemIcon))>
<SettingsIcon />
<ShowChartIcon />
</WithStyles(ForwardRef(ListItemIcon))>
<WithStyles(ForwardRef(ListItemText))
primary="Action Dashboard"
primary="Dashboard"
/>
</WithStyles(ForwardRef(MenuItem))>
</WithStyles(ForwardRef(List))>
Expand Down Expand Up @@ -265,10 +265,10 @@ exports[`<MainMenu /> <MainMenu /> with developerMode = true renders correctly 1
selected={false}
>
<WithStyles(ForwardRef(ListItemIcon))>
<SettingsIcon />
<ShowChartIcon />
</WithStyles(ForwardRef(ListItemIcon))>
<WithStyles(ForwardRef(ListItemText))
primary="Action Dashboard"
primary="Dashboard"
/>
</WithStyles(ForwardRef(MenuItem))>
<WithStyles(ForwardRef(MenuItem))
Expand Down
132 changes: 132 additions & 0 deletions src/components/dashboard/ActionBarChart.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import React, { PureComponent } from 'react';
import { connect } from 'react-redux';
import _ from 'lodash';
import PropTypes from 'prop-types';
import {
BarChart,
Bar,
XAxis,
YAxis,
CartesianGrid,
Tooltip,
Legend,
ResponsiveContainer,
} from 'recharts';
import Typography from '@material-ui/core/Typography';
import { withStyles } from '@material-ui/core';
import { getDatabase, setDatabase } from '../../actions';
import Loader from '../common/Loader';
import Styles from '../../Styles';

class ActionBarChart extends PureComponent {
static propTypes = {
classes: PropTypes.shape({
root: PropTypes.string.isRequired,
appBar: PropTypes.string.isRequired,
appBarShift: PropTypes.string.isRequired,
menuButton: PropTypes.string.isRequired,
hide: PropTypes.string.isRequired,
drawer: PropTypes.string.isRequired,
drawerPaper: PropTypes.string.isRequired,
drawerHeader: PropTypes.string.isRequired,
content: PropTypes.string.isRequired,
contentShift: PropTypes.string.isRequired,
developer: PropTypes.string.isRequired,
screenTitle: PropTypes.string.isRequired,
}).isRequired,
theme: PropTypes.shape({
palette: PropTypes.shape({
type: PropTypes.string.isRequired,
primary: PropTypes.arrayOf(PropTypes.string).isRequired,
}).isRequired,
}).isRequired,
history: PropTypes.shape({
replace: PropTypes.func.isRequired,
}).isRequired,
i18n: PropTypes.shape({
changeLanguage: PropTypes.func.isRequired,
}).isRequired,
database: PropTypes.shape({
user: PropTypes.object,
spaces: PropTypes.array,
actions: PropTypes.array,
}),
};

static defaultProps = {
database: {},
};

render() {
const { database, theme } = this.props;
const {
palette: { primary, type },
} = theme;

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

const { spaces, actions } = database;
const data =
// group actions by space id
Object.entries(_.groupBy(actions, 'spaceId'))
// map space id to corresponding space name
// reduce actions by count number
.map(([id, actionElements]) => ({
space: _.find(spaces, ['id', id]).name,
count: actionElements.length,
}));

return (
<>
<Typography variant="h5">Action Count Per Space</Typography>
<ResponsiveContainer width="100%" height="100%">
<BarChart
width="100%"
height="100%"
data={data}
margin={{
top: 5,
right: 30,
left: 20,
bottom: 5,
}}
>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="space" />
<YAxis
label={{
value: 'Action count',
angle: -90,
position: 'insideLeft',
}}
/>
<Tooltip />
<Legend />
<Bar name="action count" dataKey="count" fill={primary[type]} />
</BarChart>
</ResponsiveContainer>
</>
);
}
}

const mapStateToProps = ({ Developer }) => ({
database: Developer.get('database'),
});

const mapDispatchToProps = {
dispatchGetDatabase: getDatabase,
dispatchSetDatabase: setDatabase,
};

const ConnectedComponent = connect(
mapStateToProps,
mapDispatchToProps
)(ActionBarChart);

const StyledComponent = withStyles(Styles, { withTheme: true })(
ConnectedComponent
);
export default StyledComponent;
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import _ from 'lodash';
import { connect } from 'react-redux';
import ReactJson from 'react-json-view';
import PropTypes from 'prop-types';
import Button from '@material-ui/core/Button';
import Typography from '@material-ui/core/Typography';
import { withTranslation } from 'react-i18next';
import { withStyles } from '@material-ui/core';
Expand Down Expand Up @@ -47,32 +46,16 @@ export class ActionEditor extends Component {
};

render() {
const { database, t, classes } = this.props;
const { database, t } = this.props;

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

return (
<div>
<Typography variant="h6">{t('Manually Edit the Database')}</Typography>
<ReactJson
name="actions"
collapsed
src={database.actions}
onEdit={this.handleEdit}
onAdd={this.handleEdit}
onDelete={this.handleEdit}
/>
<br />
<Button
variant="contained"
className={classes.button}
onClick={this.handleUseSampleDatabase}
color="primary"
>
{t('Use Sample Database')}
</Button>
<Typography variant="h6">{t('View Action Database')}</Typography>
<ReactJson name="actions" collapsed src={database.actions} />
</div>
);
}
Expand Down
Loading

0 comments on commit c25dead

Please sign in to comment.