Show empty chart and summary numbers when search returns no results (https://github.com/woocommerce/woocommerce-admin/pull/1491)
* Show empty chart and summary numbers when search returns no results * Add changelog message * Preserve chart mode in empty searches * Add tests * Fix CSS Lint error * Make sure the legend updates when there is no data and it's not requesting * Rename 'emptySearch' to 'emptySearchResults' * Fix tests
This commit is contained in:
parent
e2b10bdb49
commit
b3e028bc81
|
@ -183,7 +183,7 @@ ReportChart.propTypes = {
|
|||
/**
|
||||
* Primary data to display in the chart.
|
||||
*/
|
||||
primaryData: PropTypes.object.isRequired,
|
||||
primaryData: PropTypes.object,
|
||||
/**
|
||||
* The query string represented in object form.
|
||||
*/
|
||||
|
@ -198,11 +198,34 @@ ReportChart.propTypes = {
|
|||
selectedChart: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
ReportChart.defaultProps = {
|
||||
primaryData: {
|
||||
data: {
|
||||
intervals: [],
|
||||
},
|
||||
isError: false,
|
||||
isRequesting: false,
|
||||
},
|
||||
secondaryData: {
|
||||
data: {
|
||||
intervals: [],
|
||||
},
|
||||
isError: false,
|
||||
isRequesting: false,
|
||||
},
|
||||
};
|
||||
|
||||
export default compose(
|
||||
withSelect( ( select, props ) => {
|
||||
const { query, endpoint, filters } = props;
|
||||
const chartMode = props.mode || getChartMode( filters, query ) || 'time-comparison';
|
||||
|
||||
if ( query.search && ! ( query[ endpoint ] && query[ endpoint ].length ) ) {
|
||||
return {
|
||||
mode: chartMode,
|
||||
};
|
||||
}
|
||||
|
||||
if ( 'item-comparison' === chartMode ) {
|
||||
const primaryData = getReportChartData( endpoint, 'primary', query, select );
|
||||
return {
|
||||
|
|
|
@ -27,9 +27,27 @@ import withSelect from 'wc-api/with-select';
|
|||
* Component to render summary numbers in reports.
|
||||
*/
|
||||
export class ReportSummary extends Component {
|
||||
formatVal( val, type ) {
|
||||
return 'currency' === type ? formatCurrency( val ) : formatValue( type, val );
|
||||
}
|
||||
|
||||
getValues( key, type ) {
|
||||
const { emptySearchResults, summaryData } = this.props;
|
||||
const { totals } = summaryData;
|
||||
|
||||
const primaryValue = emptySearchResults ? 0 : totals.primary[ key ];
|
||||
const secondaryValue = emptySearchResults ? 0 : totals.secondary[ key ];
|
||||
|
||||
return {
|
||||
delta: calculateDelta( primaryValue, secondaryValue ),
|
||||
prevValue: this.formatVal( secondaryValue, type ),
|
||||
value: this.formatVal( primaryValue, type ),
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
const { charts, query, selectedChart, summaryData } = this.props;
|
||||
const { totals, isError, isRequesting } = summaryData;
|
||||
const { isError, isRequesting } = summaryData;
|
||||
|
||||
if ( isError ) {
|
||||
return <ReportError isError />;
|
||||
|
@ -39,23 +57,14 @@ export class ReportSummary extends Component {
|
|||
return <SummaryListPlaceholder numberOfItems={ charts.length } />;
|
||||
}
|
||||
|
||||
const primaryTotals = totals.primary || {};
|
||||
const secondaryTotals = totals.secondary || {};
|
||||
const { compare } = getDateParamsFromQuery( query );
|
||||
|
||||
const renderSummaryNumbers = ( { onToggle } ) =>
|
||||
charts.map( chart => {
|
||||
const { key, label, type } = chart;
|
||||
const isCurrency = 'currency' === type;
|
||||
const delta = calculateDelta( primaryTotals[ key ], secondaryTotals[ key ] );
|
||||
const href = getNewPath( { chart: key } );
|
||||
const prevValue = isCurrency
|
||||
? formatCurrency( secondaryTotals[ key ] )
|
||||
: formatValue( type, secondaryTotals[ key ] );
|
||||
const isSelected = selectedChart.key === key;
|
||||
const value = isCurrency
|
||||
? formatCurrency( primaryTotals[ key ] )
|
||||
: formatValue( type, primaryTotals[ key ] );
|
||||
const { delta, prevValue, value } = this.getValues( key, type );
|
||||
|
||||
return (
|
||||
<SummaryNumber
|
||||
|
@ -105,11 +114,31 @@ ReportSummary.propTypes = {
|
|||
*/
|
||||
key: PropTypes.string.isRequired,
|
||||
} ).isRequired,
|
||||
/**
|
||||
* Data to display in the SummaryNumbers.
|
||||
*/
|
||||
summaryData: PropTypes.object,
|
||||
};
|
||||
|
||||
ReportSummary.defaultProps = {
|
||||
summaryData: {
|
||||
totals: {
|
||||
primary: {},
|
||||
secondary: {},
|
||||
},
|
||||
isError: false,
|
||||
isRequesting: false,
|
||||
},
|
||||
};
|
||||
|
||||
export default compose(
|
||||
withSelect( ( select, props ) => {
|
||||
const { query, endpoint } = props;
|
||||
if ( query.search && ! ( query[ endpoint ] && query[ endpoint ].length ) ) {
|
||||
return {
|
||||
emptySearchResults: true,
|
||||
};
|
||||
}
|
||||
const summaryData = getSummaryNumbers( endpoint, query, select );
|
||||
|
||||
return {
|
||||
|
|
|
@ -15,7 +15,8 @@ describe( 'ReportSummary', () => {
|
|||
primaryValue,
|
||||
secondaryValue,
|
||||
isError = false,
|
||||
isRequesting = false
|
||||
isRequesting = false,
|
||||
props
|
||||
) {
|
||||
const selectedChart = {
|
||||
key: 'gross_revenue',
|
||||
|
@ -44,6 +45,7 @@ describe( 'ReportSummary', () => {
|
|||
query={ query }
|
||||
selectedChart={ selectedChart }
|
||||
summaryData={ summaryData }
|
||||
{ ...props }
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -93,6 +95,17 @@ describe( 'ReportSummary', () => {
|
|||
expect( summaryNumber.props().delta ).toBe( null );
|
||||
} );
|
||||
|
||||
test( 'should show 0s when displaying an empty search', () => {
|
||||
const reportChart = renderChart( 'number', null, undefined, false, false, {
|
||||
emptySearchResults: true,
|
||||
} );
|
||||
const summaryNumber = reportChart.find( 'SummaryNumber' );
|
||||
|
||||
expect( summaryNumber.props().value ).toBe( '0' );
|
||||
expect( summaryNumber.props().prevValue ).toBe( '0' );
|
||||
expect( summaryNumber.props().delta ).toBe( 0 );
|
||||
} );
|
||||
|
||||
test( 'should display ReportError when isError is true', () => {
|
||||
const reportChart = renderChart( 'number', null, null, true );
|
||||
const reportError = reportChart.find( 'ReportError' );
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
# 1.5.0
|
||||
# 1.5.0 (unreleased)
|
||||
- Chart component: remove d3-array dependency.
|
||||
- Chart component: fix display when there is no data.
|
||||
- Improves display of charts where all values are 0.
|
||||
- Fix X-axis labels in hourly bar charts.
|
||||
- New `<Search>` prop named `showClearButton`, that will display a 'Clear' button when the search box contains one or more tags.
|
||||
|
|
|
@ -36,7 +36,6 @@
|
|||
"@wordpress/viewport": "^2.0.7",
|
||||
"classnames": "^2.2.5",
|
||||
"core-js": "2.6.3",
|
||||
"d3-array": "^2.0.0",
|
||||
"d3-axis": "^1.0.12",
|
||||
"d3-format": "^1.3.2",
|
||||
"d3-scale": "^2.1.2",
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { isEmpty } from 'lodash';
|
||||
import { Component, createRef } from '@wordpress/element';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
|
@ -161,9 +160,6 @@ class D3Chart extends Component {
|
|||
|
||||
render() {
|
||||
const { className, data, height, type } = this.props;
|
||||
if ( isEmpty( data ) ) {
|
||||
return null; // @todo Improve messaging
|
||||
}
|
||||
const computedWidth = this.getWidth();
|
||||
return (
|
||||
<div
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
@import './legend.scss';
|
||||
|
||||
.woocommerce-chart__body-row .d3-chart__container {
|
||||
width: calc( 100% - 320px );
|
||||
width: calc(100% - 320px);
|
||||
}
|
||||
|
||||
.d3-chart__container {
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { max as d3Max } from 'd3-array';
|
||||
import {
|
||||
scaleBand as d3ScaleBand,
|
||||
scaleLinear as d3ScaleLinear,
|
||||
|
@ -59,7 +58,11 @@ export const getXLineScale = ( uniqueDates, width ) =>
|
|||
* @returns {number} the maximum value in the timeseries multiplied by 4/3
|
||||
*/
|
||||
export const getYMax = lineData => {
|
||||
const yMax = 4 / 3 * d3Max( lineData, d => d3Max( d.values.map( date => date.value ) ) );
|
||||
const maxValue = Math.max( ...lineData.map( d => Math.max( ...d.values.map( date => date.value ) ) ) );
|
||||
if ( ! Number.isFinite( maxValue ) || maxValue <= 0 ) {
|
||||
return 0;
|
||||
}
|
||||
const yMax = 4 / 3 * maxValue;
|
||||
const pow3Y = Math.pow( 10, ( ( Math.log( yMax ) * Math.LOG10E + 1 ) | 0 ) - 2 ) * 3;
|
||||
return Math.ceil( Math.ceil( yMax / pow3Y ) * pow3Y );
|
||||
};
|
||||
|
|
|
@ -98,6 +98,10 @@ describe( 'Y scales', () => {
|
|||
it( 'calculate the correct maximum y value', () => {
|
||||
expect( getYMax( testLineData ) ).toEqual( 15000000 );
|
||||
} );
|
||||
|
||||
it( 'return 0 if there is no line data', () => {
|
||||
expect( getYMax( [] ) ).toEqual( 0 );
|
||||
} );
|
||||
} );
|
||||
|
||||
describe( 'getYScale', () => {
|
||||
|
|
|
@ -106,15 +106,15 @@ class Chart extends Component {
|
|||
}
|
||||
|
||||
componentDidUpdate( prevProps ) {
|
||||
const { data, query, mode } = this.props;
|
||||
const { data, query, isRequesting, mode } = this.props;
|
||||
if ( ! isEqual( [ ...data ].sort(), [ ...prevProps.data ].sort() ) ) {
|
||||
/**
|
||||
* Only update the orderedKeys when data is present so that
|
||||
* selection may persist while requesting new data.
|
||||
*/
|
||||
const orderedKeys = data.length
|
||||
? getOrderedKeys( this.props, this.state.orderedKeys )
|
||||
: this.state.orderedKeys;
|
||||
const orderedKeys = isRequesting && ! data.length
|
||||
? this.state.orderedKeys
|
||||
: getOrderedKeys( this.props, this.state.orderedKeys );
|
||||
/* eslint-disable react/no-did-update-set-state */
|
||||
this.setState( {
|
||||
orderedKeys,
|
||||
|
|
|
@ -80,7 +80,9 @@ const SummaryNumber = ( {
|
|||
<span className="woocommerce-summary__item-label">{ label }</span>
|
||||
|
||||
<span className="woocommerce-summary__item-data">
|
||||
<span className="woocommerce-summary__item-value">{ value }</span>
|
||||
<span className="woocommerce-summary__item-value">
|
||||
{ ! isNil( value ) ? value : __( 'N/A', 'wc-admin' ) }
|
||||
</span>
|
||||
<div
|
||||
className="woocommerce-summary__item-delta"
|
||||
role="presentation"
|
||||
|
|
Loading…
Reference in New Issue