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:
Albert Juhé Lluveras 2019-02-07 10:57:47 +01:00 committed by GitHub
parent e2b10bdb49
commit b3e028bc81
11 changed files with 98 additions and 27 deletions

View File

@ -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 {

View File

@ -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 {

View File

@ -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' );

View File

@ -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.

View File

@ -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",

View File

@ -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

View File

@ -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 {

View File

@ -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 );
};

View File

@ -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', () => {

View File

@ -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,

View File

@ -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"