Skip to content

Commit

Permalink
fix: dashboards loading spinner (DHIS2-8384) + flash bug (#613)
Browse files Browse the repository at this point in the history
* Adds loading spinners to Dashboard items (https://jira.dhis2.org/browse/DHIS2-8384)
* Prevents the "flash bug" from appearing when moving items (edit) or toggling the interpretations panel
  • Loading branch information
martinkrulltott authored Mar 5, 2020
1 parent 8d8357f commit ce2535b
Show file tree
Hide file tree
Showing 2 changed files with 141 additions and 57 deletions.
165 changes: 108 additions & 57 deletions src/components/Item/VisualizationItem/Item.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { Component } from 'react';
import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { withStyles } from '@material-ui/core/styles';
Expand All @@ -18,11 +18,17 @@ import {
acAddVisualization,
acSetActiveVisualizationType,
} from '../../../actions/visualizations';
import { VISUALIZATION, MAP, CHART, REPORT_TABLE } from '../../../modules/itemTypes';
import {
VISUALIZATION,
MAP,
CHART,
REPORT_TABLE,
} from '../../../modules/itemTypes';

import { colors } from '@dhis2/ui-core';
import memoizeOne from '../../../modules/memoizeOne';
import { getVisualizationConfig } from './plugin';
import LoadingMask from './LoadingMask';
import memoizeOne from '../../../modules/memoizeOne';

const HEADER_HEIGHT = 45;

Expand All @@ -46,53 +52,22 @@ const styles = {
padding: '10px',
lineHeight: '20px',
},
};

const applyFilters = (visualization, filters) => {
if (!Object.keys(filters).length) {
return visualization;
}

// deep clone objects in filters to avoid changing the visualization in the Redux store
const visRows = visualization.rows.map(obj => ({ ...obj }));
const visColumns = visualization.columns.map(obj => ({ ...obj }));
const visFilters = visualization.filters.map(obj => ({ ...obj }));

Object.keys(filters).forEach(dimensionId => {
if (filters[dimensionId]) {
let dimensionFound = false;

[visRows, visColumns, visFilters].forEach(dimensionObjects =>
dimensionObjects
.filter(obj => obj.dimension === dimensionId)
.forEach(obj => {
dimensionFound = true;
obj.items = filters[dimensionId];
})
);

// add dimension to filters if not already present elsewhere
if (!dimensionFound) {
visFilters.push({
dimension: dimensionId,
items: filters[dimensionId],
});
}
}
});

return {
...visualization,
rows: visRows,
columns: visColumns,
filters: visFilters,
};
loadingCover: {
position: 'absolute',
height: '100%',
width: '100%',
left: 0,
top: 0,
zIndex: 100,
background: '#ffffffab',
},
};

export class Item extends Component {
state = {
showFooter: false,
configLoaded: false,
pluginIsLoaded: false,
};

constructor(props, context) {
Expand All @@ -101,6 +76,12 @@ export class Item extends Component {
this.d2 = context.d2;

this.contentRef = React.createRef();

this.memoizedApplyFilters = memoizeOne(this.applyFilters);

this.memoizedGetVisualizationConfig = memoizeOne(
getVisualizationConfig
);
}

async componentDidMount() {
Expand All @@ -114,13 +95,66 @@ export class Item extends Component {
});
}

componentDidUpdate(prevProps, prevState) {
if (
prevState.pluginIsLoaded &&
(prevProps.visualization !== this.props.visualization ||
prevProps.itemFilters !== this.props.itemFilters)
) {
this.setState({
pluginIsLoaded: false,
});
}
}

applyFilters = (visualization, filters) => {
if (!Object.keys(filters).length) {
return visualization;
}

// deep clone objects in filters to avoid changing the visualization in the Redux store
const visRows = visualization.rows.map(obj => ({ ...obj }));
const visColumns = visualization.columns.map(obj => ({ ...obj }));
const visFilters = visualization.filters.map(obj => ({ ...obj }));

Object.keys(filters).forEach(dimensionId => {
if (filters[dimensionId]) {
let dimensionFound = false;

[visRows, visColumns, visFilters].forEach(dimensionObjects =>
dimensionObjects
.filter(obj => obj.dimension === dimensionId)
.forEach(obj => {
dimensionFound = true;
obj.items = filters[dimensionId];
})
);

// add dimension to filters if not already present elsewhere
if (!dimensionFound) {
visFilters.push({
dimension: dimensionId,
items: filters[dimensionId],
});
}
}
});

return {
...visualization,
rows: visRows,
columns: visColumns,
filters: visFilters,
};
};

getUniqueKey = memoizeOne(() => uniqueId());

pluginCredentials = null;

getPluginComponent = () => {
const activeType = this.getActiveType();
const visualization = getVisualizationConfig(
const visualization = this.memoizedGetVisualizationConfig(
this.props.visualization,
this.props.item.type,
activeType
Expand All @@ -145,15 +179,23 @@ export class Item extends Component {
case CHART:
case REPORT_TABLE: {
return (
<VisualizationPlugin
d2={this.d2}
visualization={applyFilters(
visualization,
props.itemFilters
)}
forDashboard={true}
style={props.style}
/>
<Fragment>
{!this.state.pluginIsLoaded ? (
<div style={styles.loadingCover}>
<LoadingMask />
</div>
) : null}
<VisualizationPlugin
d2={this.d2}
visualization={this.memoizedApplyFilters(
visualization,
props.itemFilters
)}
onLoadingComplete={this.onLoadingComplete}
forDashboard={true}
style={props.style}
/>
</Fragment>
);
}
case MAP: {
Expand All @@ -165,7 +207,10 @@ export class Item extends Component {
obj.layer.includes('thematic') ||
obj.layer.includes('event')
) {
return applyFilters(obj, props.itemFilters);
return this.memoizedApplyFilters(
obj,
props.itemFilters
);
}

return obj;
Expand All @@ -179,7 +224,7 @@ export class Item extends Component {
// this is the case of a non map AO passed to the maps plugin
// due to a visualization type switch in dashboard item
// maps plugin takes care of converting the AO to a suitable format
props.visualization = applyFilters(
props.visualization = this.memoizedApplyFilters(
props.visualization,
props.itemFilters
);
Expand All @@ -188,7 +233,7 @@ export class Item extends Component {
return <DefaultPlugin {...props} />;
}
default: {
props.visualization = applyFilters(
props.visualization = this.memoizedApplyFilters(
props.visualization,
props.itemFilters
);
Expand All @@ -198,6 +243,12 @@ export class Item extends Component {
}
};

onLoadingComplete = () => {
this.setState({
pluginIsLoaded: true,
});
};

onToggleFooter = () => {
this.setState(
{ showFooter: !this.state.showFooter },
Expand Down
33 changes: 33 additions & 0 deletions src/components/Item/VisualizationItem/LoadingMask.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import React from 'react';
import PropTypes from 'prop-types';
import { withStyles } from '@material-ui/core/styles';
import CircularProgress from '@material-ui/core/CircularProgress';

const styles = theme => ({
progress: {
margin: theme.spacing.unit * 2,
maxWidth: 200,
textAlign: 'center',
alignSelf: 'center',
},
outer: {
display: 'flex',
justifyContent: 'center',
height: '100%',
},
});

function CircularIndeterminate(props) {
const { classes } = props;
return (
<div className={classes.outer}>
<CircularProgress className={classes.progress} />
</div>
);
}

CircularIndeterminate.propTypes = {
classes: PropTypes.object.isRequired,
};

export default withStyles(styles)(CircularIndeterminate);

0 comments on commit ce2535b

Please sign in to comment.