diff --git a/src/components/Visualization/StartScreen.js b/src/components/Visualization/StartScreen.js index 1d2801e4dc..091ef63830 100644 --- a/src/components/Visualization/StartScreen.js +++ b/src/components/Visualization/StartScreen.js @@ -7,13 +7,12 @@ import React, { useEffect, useState } from 'react' import { connect } from 'react-redux' import { apiFetchMostViewedVisualizations } from '../../api/mostViewedVisualizations.js' import { apiFetchVisualizations } from '../../api/visualization.js' -import { GenericError } from '../../assets/ErrorIcons.js' -import { VisualizationError, genericErrorTitle } from '../../modules/error.js' import history from '../../modules/history.js' import { sGetLoadError } from '../../reducers/loader.js' import { sGetUsername } from '../../reducers/user.js' import styles from './styles/StartScreen.module.css' import { matchVisualizationWithType } from './utils.js' +import { VisualizationErrorInfo } from './VisualizationErrorInfo.js' const StartScreen = ({ error, username }) => { const [mostViewedVisualizations, setMostViewedVisualizations] = useState([]) @@ -44,7 +43,7 @@ const StartScreen = ({ error, username }) => { const getContent = () => error ? ( - getErrorContent() + ) : (
{
) - const getErrorContent = () => ( -
- {error instanceof VisualizationError ? ( - <> -
{error.icon()}
-

{error.title}

-

- {error.description} -

- - ) : ( - <> -
{GenericError()}
-

{genericErrorTitle}

-

- {error.message || error} -

- - )} -
- ) - return (
{getContent()}
diff --git a/src/components/Visualization/Visualization.js b/src/components/Visualization/Visualization.js index f25e90ee08..1c9f233526 100644 --- a/src/components/Visualization/Visualization.js +++ b/src/components/Visualization/Visualization.js @@ -1,8 +1,4 @@ -import { - DIMENSION_ID_DATA, - VIS_TYPE_OUTLIER_TABLE, - VIS_TYPE_PIVOT_TABLE, -} from '@dhis2/analytics' +import { DIMENSION_ID_DATA, VIS_TYPE_PIVOT_TABLE } from '@dhis2/analytics' import debounce from 'lodash-es/debounce' import PropTypes from 'prop-types' import React, { Component, Fragment } from 'react' @@ -16,10 +12,10 @@ import { acSetUiDataSorting, acAddParentGraphMap, } from '../../actions/ui.js' +import { ensureAnalyticsResponsesContainData } from '../../modules/analytics.js' import { AssignedCategoriesDataElementsError, GenericServerError, - EmptyResponseError, AssignedCategoriesAsFilterError, MultipleIndicatorAsFilterError, NoDataOrDataElementGroupSetError, @@ -29,7 +25,6 @@ import { ValueTypeError, AnalyticsGenerationError, AnalyticsRequestError, - NoOutliersError, } from '../../modules/error.js' import { removeLastPathSegment } from '../../modules/orgUnit.js' import { sGetCurrent } from '../../reducers/current.js' @@ -144,15 +139,10 @@ export class UnconnectedVisualization extends Component { this.props.addMetadata(forMetadata) - if ( - !responses.some((response) => response.rows && response.rows.length) - ) { - if (this.props.visualization.type === VIS_TYPE_OUTLIER_TABLE) { - throw new NoOutliersError() - } - - throw new EmptyResponseError() - } + ensureAnalyticsResponsesContainData( + responses, + this.props.visualization.type + ) } onDrill = (drillData) => { diff --git a/src/components/Visualization/VisualizationErrorInfo.js b/src/components/Visualization/VisualizationErrorInfo.js new file mode 100644 index 0000000000..442fb4df28 --- /dev/null +++ b/src/components/Visualization/VisualizationErrorInfo.js @@ -0,0 +1,35 @@ +import PropTypes from 'prop-types' +import React from 'react' +import { GenericError } from '../../assets/ErrorIcons.js' +import { VisualizationError, genericErrorTitle } from '../../modules/error.js' +import styles from './styles/VisualizationErrorInfo.module.css' + +export const VisualizationErrorInfo = ({ error }) => ( +
+ {error instanceof VisualizationError ? ( + <> +
{error.icon()}
+

{error.title}

+

{error.description}

+ + ) : ( + <> +
{GenericError()}
+

{genericErrorTitle}

+

+ {error.message || error} +

+ + )} +
+) + +VisualizationErrorInfo.propTypes = { + error: PropTypes.oneOfType([ + PropTypes.instanceOf(Error), + PropTypes.instanceOf(VisualizationError), + ]), +} diff --git a/src/components/Visualization/styles/StartScreen.module.css b/src/components/Visualization/styles/StartScreen.module.css index 7b4f7331bc..60e075a384 100644 --- a/src/components/Visualization/styles/StartScreen.module.css +++ b/src/components/Visualization/styles/StartScreen.module.css @@ -13,34 +13,6 @@ display: flex; align-items: center; } -.errorContainer { - display: flex; - flex-direction: column; - align-items: center; - text-align: center; -} -.errorIcon { - width: 136px; - height: 136px; - margin: 0 auto var(--spacers-dp24); -} -.errorTitle { - font-weight: 500; - font-size: 20px; - color: var(--colors-grey800); - letter-spacing: 0.15px; - line-height: 24px; - width: 360px; - margin: 0 auto var(--spacers-dp12); -} -.errorDescription { - font-weight: 400; - font-size: 14px; - color: var(--colors-grey700); - line-height: 19px; - width: 360px; - margin: 0 auto; -} .title { font-weight: 500; font-size: 17px; diff --git a/src/components/Visualization/styles/VisualizationErrorInfo.module.css b/src/components/Visualization/styles/VisualizationErrorInfo.module.css new file mode 100644 index 0000000000..b7c21e84ae --- /dev/null +++ b/src/components/Visualization/styles/VisualizationErrorInfo.module.css @@ -0,0 +1,30 @@ +.errorContainer { + display: flex; + flex-direction: column; + align-items: center; + text-align: center; + justify-content: center; + width: 100%; +} +.errorIcon { + width: 136px; + height: 136px; + margin: 0 auto var(--spacers-dp24); +} +.errorTitle { + font-weight: 500; + font-size: 20px; + color: var(--colors-grey800); + letter-spacing: 0.15px; + line-height: 24px; + width: 360px; + margin: 0 auto var(--spacers-dp12); +} +.errorDescription { + font-weight: 400; + font-size: 14px; + color: var(--colors-grey700); + line-height: 19px; + width: 360px; + margin: 0 auto; +} diff --git a/src/components/VisualizationPlugin/VisualizationPluginWrapper.js b/src/components/VisualizationPlugin/VisualizationPluginWrapper.js index 3d4a1d10e4..18a1e4157f 100644 --- a/src/components/VisualizationPlugin/VisualizationPluginWrapper.js +++ b/src/components/VisualizationPlugin/VisualizationPluginWrapper.js @@ -1,12 +1,15 @@ import { CenteredContent, CircularLoader, ComponentCover } from '@dhis2/ui' import PropTypes from 'prop-types' import React, { useCallback, useEffect, useState } from 'react' +import { ensureAnalyticsResponsesContainData } from '../../modules/analytics.js' +import { VisualizationErrorInfo } from '../Visualization/VisualizationErrorInfo.js' import { VisualizationPlugin } from '../VisualizationPlugin/VisualizationPlugin.js' // handle internal state for features that need to work without the app's Redux store const VisualizationPluginWrapper = (props) => { const [pluginProps, setPluginProps] = useState(props) const [isLoading, setIsLoading] = useState(true) + const [error, setError] = useState(null) const onDataSorted = useCallback( (sorting) => { @@ -64,6 +67,23 @@ const VisualizationPluginWrapper = (props) => { const onLoadingComplete = () => setIsLoading(false) + const onResponsesReceived = useCallback( + (responses) => { + try { + ensureAnalyticsResponsesContainData( + responses, + props.visualization.type + ) + } catch (error) { + setError(error) + } + }, + [props.visualization.type] + ) + + if (error) { + return + } return ( <> {isLoading && ( @@ -77,6 +97,7 @@ const VisualizationPluginWrapper = (props) => { {...pluginProps} onDataSorted={onDataSorted} onLoadingComplete={onLoadingComplete} + onResponsesReceived={onResponsesReceived} /> ) diff --git a/src/modules/analytics.js b/src/modules/analytics.js index 7773060f33..57dcb317a3 100644 --- a/src/modules/analytics.js +++ b/src/modules/analytics.js @@ -6,8 +6,10 @@ import { DIMENSION_ID_PERIOD, WEEKLY, DAILY, + VIS_TYPE_OUTLIER_TABLE, } from '@dhis2/analytics' import i18n from '@dhis2/d2-i18n' +import { EmptyResponseError, NoOutliersError } from './error.js' export const outlierTableHeadersMap = { [DIMENSION_ID_DATA]: 'dxname', @@ -281,3 +283,19 @@ export const getRelativePeriodTypeUsed = (periodItems) => { return DAILY } } + +export const ensureAnalyticsResponsesContainData = ( + responses, + visualizationType +) => { + const hasPopulatedRows = responses.some( + (response) => response.rows && response.rows.length > 0 + ) + if (!hasPopulatedRows) { + if (visualizationType === VIS_TYPE_OUTLIER_TABLE) { + throw new NoOutliersError() + } + + throw new EmptyResponseError() + } +}