Merge branch 'add/chart-second-x' of github.com:woocommerce/wc-admin into add/chart-second-x

This commit is contained in:
Robert Elliott 2018-09-17 10:51:44 +02:00
commit dbca60a660
9 changed files with 195 additions and 66 deletions

View File

@ -338,18 +338,16 @@ export class RevenueReport extends Component {
} ); } );
return ( return (
<Card title=""> <Chart
<Chart data={ chartData }
data={ chartData } title={ selectedChart.label }
title={ selectedChart.label } interval={ currentInterval }
interval={ currentInterval } allowedIntervals={ allowedIntervals }
allowedIntervals={ allowedIntervals } tooltipFormat={ formats.tooltipFormat }
tooltipFormat={ formats.tooltipFormat } xFormat={ formats.xFormat }
xFormat={ formats.xFormat } x2Format={ formats.x2Format }
x2Format={ formats.x2Format } dateParser={ '%Y-%m-%dT%H:%M:%S' }
dateParser={ '%Y-%m-%dT%H:%M:%S' } />
/>
</Card>
); );
} }

View File

@ -79,6 +79,7 @@
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
grid-column-start: 2;
} }
.woocommerce-calendar__input { .woocommerce-calendar__input {
@ -95,6 +96,14 @@
} }
} }
&:first-child {
grid-column-start: 1;
}
&:last-child {
grid-column-start: 3;
}
&.is-empty { &.is-empty {
.dashicons-calendar path { .dashicons-calendar path {
fill: $core-grey-dark-300; fill: $core-grey-dark-300;

View File

@ -23,6 +23,7 @@ import {
getOrderedKeys, getOrderedKeys,
getLine, getLine,
getLineData, getLineData,
getXTicks,
getUniqueKeys, getUniqueKeys,
getUniqueDates, getUniqueDates,
getXScale, getXScale,
@ -97,6 +98,7 @@ class D3Chart extends Component {
data, data,
dateParser, dateParser,
height, height,
layout,
margin, margin,
orderedKeys, orderedKeys,
tooltipFormat, tooltipFormat,
@ -120,6 +122,7 @@ class D3Chart extends Component {
const uniqueDates = getUniqueDates( lineData, parseDate ); const uniqueDates = getUniqueDates( lineData, parseDate );
const xLineScale = getXLineScale( uniqueDates, adjWidth ); const xLineScale = getXLineScale( uniqueDates, adjWidth );
const xScale = getXScale( uniqueDates, adjWidth ); const xScale = getXScale( uniqueDates, adjWidth );
const xTicks = getXTicks( uniqueDates, adjWidth, layout );
return { return {
colorScheme, colorScheme,
dateSpaces: getDateSpaces( uniqueDates, adjWidth, xLineScale ), dateSpaces: getDateSpaces( uniqueDates, adjWidth, xLineScale ),
@ -139,6 +142,7 @@ class D3Chart extends Component {
x2Format: d3TimeFormat( x2Format ), x2Format: d3TimeFormat( x2Format ),
xGroupScale: getXGroupScale( orderedKeys, xScale ), xGroupScale: getXGroupScale( orderedKeys, xScale ),
xLineScale, xLineScale,
xTicks,
xScale, xScale,
yMax, yMax,
yScale, yScale,
@ -195,6 +199,11 @@ D3Chart.propTypes = {
* Interval specification (hourly, daily, weekly etc.) * Interval specification (hourly, daily, weekly etc.)
*/ */
interval: PropTypes.oneOf( [ 'hour', 'day', 'week', 'month', 'quarter', 'year' ] ), interval: PropTypes.oneOf( [ 'hour', 'day', 'week', 'month', 'quarter', 'year' ] ),
/**
* `standard` (default) legend layout in the header or `comparison` moves legend layout
* to the left or 'compact' has the legend below
*/
layout: PropTypes.oneOf( [ 'standard', 'comparison', 'compact' ] ),
/** /**
* Margins for axis and chart padding. * Margins for axis and chart padding.
*/ */
@ -244,6 +253,7 @@ D3Chart.defaultProps = {
right: 0, right: 0,
top: 20, top: 20,
}, },
layout: 'standard',
tooltipFormat: '%Y-%m-%d', tooltipFormat: '%Y-%m-%d',
type: 'line', type: 'line',
width: 600, width: 600,

View File

@ -291,9 +291,10 @@ Chart.propTypes = {
*/ */
yFormat: PropTypes.string, yFormat: PropTypes.string,
/** /**
* `standard` (default) legend layout in the header or `comparison` moves legend layout to the left * `standard` (default) legend layout in the header or `comparison` moves legend layout
* to the left or 'compact' has the legend below
*/ */
layout: PropTypes.oneOf( [ 'standard', 'comparison' ] ), layout: PropTypes.oneOf( [ 'standard', 'comparison', 'compact' ] ),
/** /**
* A title describing this chart. * A title describing this chart.
*/ */

View File

@ -1,15 +1,22 @@
/** @format */ /** @format */
.woocommerce-chart { .woocommerce-chart {
display: flex; margin-top: -$gap;
flex-direction: column; margin-bottom: $gap-large;
justify-content: flex-start; background: white;
align-items: flex-start; border: 1px solid $core-grey-light-700;
margin: -$gap; border-top: 0;
border-top: 1px solid $core-grey-light-700;
@include breakpoint( '<782px' ) {
margin-left: -16px;
margin-right: -16px;
margin-bottom: $gap-small;
border-left: none;
border-right: none;
width: auto;
}
.woocommerce-chart__header { .woocommerce-chart__header {
min-height: 50px; min-height: 50px;
border-top: 1px solid $core-grey-light-700;
border-bottom: 1px solid $core-grey-light-700; border-bottom: 1px solid $core-grey-light-700;
display: flex; display: flex;
flex-wrap: nowrap; flex-wrap: nowrap;
@ -181,13 +188,9 @@
&.is-placeholder { &.is-placeholder {
@include placeholder(); @include placeholder();
display: inline-block;
height: 200px; height: 200px;
width: 100%; width: 100%;
margin: 0;
padding: 0; padding: 0;
margin-bottom: $gap;
border: 1px solid $core-grey-light-700;
} }
} }

View File

@ -13,13 +13,30 @@ import {
scaleLinear as d3ScaleLinear, scaleLinear as d3ScaleLinear,
scaleTime as d3ScaleTime, scaleTime as d3ScaleTime,
} from 'd3-scale'; } from 'd3-scale';
import { mouse as d3Mouse, select as d3Select } from 'd3-selection'; import { event as d3Event, mouse as d3Mouse, select as d3Select } from 'd3-selection';
import { line as d3Line } from 'd3-shape'; import { line as d3Line } from 'd3-shape';
/** /**
* Internal dependencies * Internal dependencies
*/ */
import { formatCurrency } from 'lib/currency'; import { formatCurrency } from 'lib/currency';
/**
* Describes `smallestFactor`
* @param {number} inputNum - any double or integer
* @returns {integer} smallest factor of num
*/
export const getFactors = inputNum => {
const num_factors = [];
for ( let i = 1; i <= Math.floor( Math.sqrt( inputNum ) ); i += 1 ) {
if ( inputNum % i === 0 ) {
num_factors.push( i );
inputNum / i !== i && num_factors.push( inputNum / i );
}
}
num_factors.sort( ( x, y ) => x - y ); // numeric sort
return num_factors;
};
/** /**
* Describes `getUniqueKeys` * Describes `getUniqueKeys`
* @param {array} data - The chart component's `data` prop. * @param {array} data - The chart component's `data` prop.
@ -187,6 +204,74 @@ export const getLine = ( xLineScale, yScale ) =>
.x( d => xLineScale( new Date( d.date ) ) ) .x( d => xLineScale( new Date( d.date ) ) )
.y( d => yScale( d.value ) ); .y( d => yScale( d.value ) );
/**
* Describes getXTicks
* @param {array} uniqueDates - all the unique dates from the input data for the chart
* @param {integer} width - calculated page width
* @param {string} layout - standard, comparison or compact chart types
* @returns {integer} number of x-axis ticks based on width and chart layout
*/
export const getXTicks = ( uniqueDates, width, layout ) => {
// caluclate the maximum number of ticks allowed in the x-axis based on the width
// and layout of the chart
let ticks = 16;
if ( width < 783 ) {
ticks = 7;
} else if ( width >= 783 && width < 1129 ) {
ticks = 12;
} else if ( width >= 1130 && width < 1365 ) {
if ( layout === 'standard' ) {
ticks = 16;
} else if ( layout === 'comparison' ) {
ticks = 12;
} else if ( layout === 'compact' ) {
ticks = 7;
}
} else if ( width >= 1365 ) {
if ( layout === 'standard' ) {
ticks = 31;
} else if ( layout === 'comparison' ) {
ticks = 16;
} else if ( layout === 'compact' ) {
ticks = 12;
}
}
if ( uniqueDates.length <= ticks ) {
return uniqueDates;
}
let factors = [];
let i = 0;
// first we get all the factors of the length of the uniqieDates array
// if the number is a prime number or near prime (with 3 factors) then we
// step down by 1 integer and try again
while ( factors.length <= 3 ) {
factors = getFactors( uniqueDates.length - ( 1 + i ) );
i += 1;
}
let newTicks = [];
let factorIndex = 0;
// newTicks is the first tick plus the smallest factor (initiallY) etc.
// however, if we still end up with too many ticks we look at the next factor
// and try again unttil we have fewer ticks than the max
while ( newTicks.length > ticks || newTicks.length === 0 ) {
if ( newTicks.length > ticks ) {
factorIndex += 1;
newTicks = [];
}
for ( let idx = 0; idx < uniqueDates.length; idx = idx + factors[ factorIndex ] ) {
newTicks.push( uniqueDates[ idx ] );
}
}
// if, for some reason, the first or last date is missing from the newTicks array, add it back in
if ( newTicks[ 0 ] !== uniqueDates[ 0 ] ) {
newTicks.unshift( uniqueDates[ 0 ] );
}
if ( newTicks[ newTicks.length - 1 ] !== uniqueDates[ uniqueDates.length - 1 ] ) {
newTicks.push( uniqueDates[ uniqueDates.length - 1 ] );
}
return newTicks;
};
/** /**
* Describes getDateSpaces * Describes getDateSpaces
* @param {array} uniqueDates - from `getUniqueDates` * @param {array} uniqueDates - from `getUniqueDates`
@ -223,13 +308,15 @@ export const drawAxis = ( node, params ) => {
yGrids.push( i / 3 * params.yMax ); yGrids.push( i / 3 * params.yMax );
} }
const ticks = params.xTicks.map( d => ( params.type === 'line' ? new Date( d ) : d ) );
node node
.append( 'g' ) .append( 'g' )
.attr( 'class', 'axis' ) .attr( 'class', 'axis' )
.attr( 'transform', `translate(0,${ params.height })` ) .attr( 'transform', `translate(0,${ params.height })` )
.call( .call(
d3AxisBottom( xScale ) d3AxisBottom( xScale )
.tickValues( params.uniqueDates.map( d => ( params.type === 'line' ? new Date( d ) : d ) ) ) .tickValues( ticks )
.tickFormat( d => params.xFormat( d instanceof Date ? d : new Date( d ) ) ) .tickFormat( d => params.xFormat( d instanceof Date ? d : new Date( d ) ) )
); );
@ -239,7 +326,7 @@ export const drawAxis = ( node, params ) => {
.attr( 'transform', `translate(3, ${ params.height + 20 })` ) .attr( 'transform', `translate(3, ${ params.height + 20 })` )
.call( .call(
d3AxisBottom( xScale ) d3AxisBottom( xScale )
.tickValues( params.uniqueDates.map( d => ( params.type === 'line' ? new Date( d ) : d ) ) ) .tickValues( ticks )
.tickFormat( ( d, i ) => { .tickFormat( ( d, i ) => {
const monthDate = d instanceof Date ? d : new Date( d ); const monthDate = d instanceof Date ? d : new Date( d );
let prevMonth = i !== 0 ? params.uniqueDates[ i - 1 ] : params.uniqueDates[ i ]; let prevMonth = i !== 0 ? params.uniqueDates[ i - 1 ] : params.uniqueDates[ i ];
@ -263,7 +350,7 @@ export const drawAxis = ( node, params ) => {
.attr( 'transform', `translate(0, ${ params.height })` ) .attr( 'transform', `translate(0, ${ params.height })` )
.call( .call(
d3AxisBottom( xScale ) d3AxisBottom( xScale )
.tickValues( params.uniqueDates.map( d => ( params.type === 'line' ? new Date( d ) : d ) ) ) .tickValues( ticks )
.tickSize( 5 ) .tickSize( 5 )
.tickFormat( '' ) .tickFormat( '' )
); );
@ -303,9 +390,9 @@ export const drawAxis = ( node, params ) => {
.remove(); .remove();
}; };
const showTooltip = ( node, params, d ) => { const showTooltip = ( node, params, d, position ) => {
const chartCoords = node.node().getBoundingClientRect(); const chartCoords = node.node().getBoundingClientRect();
let [ xPosition, yPosition ] = d3Mouse( node.node() ); let [ xPosition, yPosition ] = position ? position : d3Mouse( node.node() );
xPosition = xPosition > chartCoords.width - 340 ? xPosition - 340 : xPosition + 100; xPosition = xPosition > chartCoords.width - 340 ? xPosition - 340 : xPosition + 100;
yPosition = yPosition > chartCoords.height - 150 ? yPosition - 200 : yPosition + 20; yPosition = yPosition > chartCoords.height - 150 ? yPosition - 200 : yPosition + 20;
const keys = params.orderedKeys.filter( row => row.visible ).map( const keys = params.orderedKeys.filter( row => row.visible ).map(
@ -333,25 +420,25 @@ const showTooltip = ( node, params, d ) => {
` ); ` );
}; };
const handleMouseOverBarChart = ( d, i, nodes, node, data, params ) => { const handleMouseOverBarChart = ( d, i, nodes, node, data, params, position ) => {
d3Select( nodes[ i ].parentNode ) d3Select( nodes[ i ].parentNode )
.select( '.barfocus' ) .select( '.barfocus' )
.attr( 'opacity', '0.1' ); .attr( 'opacity', '0.1' );
showTooltip( node, params, d ); showTooltip( node, params, d, position );
}; };
const handleMouseOutBarChart = ( d, i, nodes, params ) => { const handleMouseOutBarChart = ( d, i, nodes, params ) => {
d3Select( nodes[ i ].parentNode ) d3Select( nodes[ i ].parentNode )
.select( '.barfocus' ) .select( '.barfocus' )
.attr( 'opacity', '0' ); .attr( 'opacity', '0' );
params.tooltip.style( 'display', 'flex' ); params.tooltip.style( 'display', 'none' );
}; };
const handleMouseOverLineChart = ( d, i, nodes, node, data, params ) => { const handleMouseOverLineChart = ( d, i, nodes, node, data, params, position ) => {
d3Select( nodes[ i ].parentNode ) d3Select( nodes[ i ].parentNode )
.select( '.focus-grid' ) .select( '.focus-grid' )
.attr( 'opacity', '1' ); .attr( 'opacity', '1' );
showTooltip( node, params, data.find( e => e.date === d.date ) ); showTooltip( node, params, data.find( e => e.date === d.date ), position );
}; };
const handleMouseOutLineChart = ( d, i, nodes, params ) => { const handleMouseOutLineChart = ( d, i, nodes, params ) => {
@ -361,6 +448,12 @@ const handleMouseOutLineChart = ( d, i, nodes, params ) => {
params.tooltip.style( 'display', 'none' ); params.tooltip.style( 'display', 'none' );
}; };
const calculatePositionInChart = ( element, chart ) => {
const elementCoords = element.getBoundingClientRect();
const chartCoords = chart.getBoundingClientRect();
return [ elementCoords.x - chartCoords.x, elementCoords.y - chartCoords.y ];
};
export const drawLines = ( node, data, params ) => { export const drawLines = ( node, data, params ) => {
const series = node const series = node
.append( 'g' ) .append( 'g' )
@ -384,25 +477,26 @@ export const drawLines = ( node, data, params ) => {
} ) } )
.attr( 'd', d => params.line( d.values ) ); .attr( 'd', d => params.line( d.values ) );
series params.uniqueDates.length < 50 &&
.selectAll( 'circle' ) series
.data( ( d, i ) => d.values.map( row => ( { ...row, i, visible: d.visible, key: d.key } ) ) ) .selectAll( 'circle' )
.enter() .data( ( d, i ) => d.values.map( row => ( { ...row, i, visible: d.visible, key: d.key } ) ) )
.append( 'circle' ) .enter()
.attr( 'r', 6 ) .append( 'circle' )
.attr( 'fill', d => getColor( d.key, params ) ) .attr( 'r', 6 )
.attr( 'stroke', '#fff' ) .attr( 'fill', d => getColor( d.key, params ) )
.attr( 'stroke-width', 3 ) .attr( 'stroke', '#fff' )
.style( 'opacity', d => { .attr( 'stroke-width', 3 )
const opacity = d.focus ? 1 : 0.1; .style( 'opacity', d => {
return d.visible ? opacity : 0; const opacity = d.focus ? 1 : 0.1;
} ) return d.visible ? opacity : 0;
.attr( 'cx', d => params.xLineScale( new Date( d.date ) ) ) } )
.attr( 'cy', d => params.yScale( d.value ) ) .attr( 'cx', d => params.xLineScale( new Date( d.date ) ) )
.on( 'mouseover', ( d, i, nodes ) => .attr( 'cy', d => params.yScale( d.value ) )
handleMouseOverLineChart( d, i, nodes, node, data, params ) .on( 'mouseover', ( d, i, nodes ) =>
) handleMouseOverLineChart( d, i, nodes, node, data, params )
.on( 'mouseout', ( d, i, nodes ) => handleMouseOutLineChart( d, i, nodes, params ) ); )
.on( 'mouseout', ( d, i, nodes ) => handleMouseOutLineChart( d, i, nodes, params ) );
const focus = node const focus = node
.append( 'g' ) .append( 'g' )
@ -430,10 +524,15 @@ export const drawLines = ( node, data, params ) => {
.attr( 'width', d => d.width ) .attr( 'width', d => d.width )
.attr( 'height', params.height ) .attr( 'height', params.height )
.attr( 'opacity', 0 ) .attr( 'opacity', 0 )
.attr( 'tabindex', '0' )
.on( 'mouseover', ( d, i, nodes ) => .on( 'mouseover', ( d, i, nodes ) =>
handleMouseOverLineChart( d, i, nodes, node, data, params ) handleMouseOverLineChart( d, i, nodes, node, data, params )
) )
.on( 'mouseout', ( d, i, nodes ) => handleMouseOutLineChart( d, i, nodes, params ) ); .on( 'focus', ( d, i, nodes ) => {
const position = calculatePositionInChart( d3Event.target, node.node() );
handleMouseOverLineChart( d, i, nodes, node, data, params, position );
} )
.on( 'mouseout blur', ( d, i, nodes ) => handleMouseOutLineChart( d, i, nodes, params ) );
}; };
export const drawBars = ( node, data, params ) => { export const drawBars = ( node, data, params ) => {
@ -491,8 +590,13 @@ export const drawBars = ( node, data, params ) => {
.attr( 'width', params.xGroupScale.range()[ 1 ] ) .attr( 'width', params.xGroupScale.range()[ 1 ] )
.attr( 'height', params.height ) .attr( 'height', params.height )
.attr( 'opacity', '0' ) .attr( 'opacity', '0' )
.attr( 'tabindex', '0' )
.on( 'mouseover', ( d, i, nodes ) => .on( 'mouseover', ( d, i, nodes ) =>
handleMouseOverBarChart( d, i, nodes, node, data, params ) handleMouseOverBarChart( d, i, nodes, node, data, params )
) )
.on( 'mouseout', ( d, i, nodes ) => handleMouseOutBarChart( d, i, nodes, params ) ); .on( 'focus', ( d, i, nodes ) => {
const position = calculatePositionInChart( d3Event.target, node.node() );
handleMouseOverBarChart( d, i, nodes, node, data, params, position );
} )
.on( 'mouseout blur', ( d, i, nodes ) => handleMouseOutBarChart( d, i, nodes, params ) );
}; };

View File

@ -9,7 +9,8 @@ And as such will require data layer logic for products to fully build the table
"product_id": 20, "product_id": 20,
"items_sold": 100, "items_sold": 100,
"gross_revenue": 999.99, "gross_revenue": 999.99,
"orders_count": 54, "orders_count": 54,
"name": 'Product name',
"_links": { "_links": {
"product": [ "product": [
{ {
@ -27,6 +28,7 @@ export default [
items_sold: 1000, items_sold: 1000,
gross_revenue: 999.99, gross_revenue: 999.99,
orders_count: 54, orders_count: 54,
name: 'awesome shirt',
_links: { _links: {
product: [ product: [
{ {
@ -40,6 +42,7 @@ export default [
items_sold: 90, items_sold: 90,
gross_revenue: 875, gross_revenue: 875,
orders_count: 41, orders_count: 41,
name: 'awesome pants',
_links: { _links: {
product: [ product: [
{ {
@ -53,6 +56,7 @@ export default [
items_sold: 55, items_sold: 55,
gross_revenue: 75.75, gross_revenue: 75.75,
orders_count: 28, orders_count: 28,
name: 'awesome hat',
_links: { _links: {
product: [ product: [
{ {
@ -66,6 +70,7 @@ export default [
items_sold: 10, items_sold: 10,
gross_revenue: 24.5, gross_revenue: 24.5,
orders_count: 14, orders_count: 14,
name: 'awesome sticker',
_links: { _links: {
product: [ product: [
{ {
@ -79,6 +84,7 @@ export default [
items_sold: 1, items_sold: 1,
gross_revenue: 0.99, gross_revenue: 0.99,
orders_count: 1, orders_count: 1,
name: 'awesome button',
_links: { _links: {
product: [ product: [
{ {

View File

@ -53,17 +53,15 @@ export class TopSellingProducts extends Component {
getRowsContent( data ) { getRowsContent( data ) {
return map( data, row => { return map( data, row => {
const { product_id, items_sold, gross_revenue, orders_count } = row; const { product_id, items_sold, gross_revenue, orders_count, name } = row;
// @TODO We also will need to have product data to properly display the product name here
const productName = `Product ${ product_id }`;
const productLink = ( const productLink = (
<a href={ getAdminLink( `/post.php?post=${ product_id }&action=edit` ) }>{ productName }</a> <a href={ getAdminLink( `/post.php?post=${ product_id }&action=edit` ) }>{ name }</a>
); );
return [ return [
{ {
display: productLink, display: productLink,
value: productName, value: name,
}, },
{ {
display: numberFormat( items_sold ), display: numberFormat( items_sold ),
@ -123,7 +121,7 @@ export default compose(
const endpoint = NAMESPACE + 'reports/products'; const endpoint = NAMESPACE + 'reports/products';
// @TODO We will need to add the date parameters from the Date Picker // @TODO We will need to add the date parameters from the Date Picker
// { after: '2018-04-22', before: '2018-05-06' } // { after: '2018-04-22', before: '2018-05-06' }
const query = { orderby: 'items_sold', per_page: 5 }; const query = { orderby: 'items_sold', per_page: 5, extended_product_info: 1 };
const stats = getReportStats( endpoint, query ); const stats = getReportStats( endpoint, query );
const isRequesting = isReportStatsRequesting( endpoint, query ); const isRequesting = isReportStatsRequesting( endpoint, query );

View File

@ -43,7 +43,7 @@ describe( 'TopSellingProducts', () => {
const table = topSellingProducts.find( 'Table' ); const table = topSellingProducts.find( 'Table' );
const firstRow = table.props().rows[ 0 ]; const firstRow = table.props().rows[ 0 ];
expect( firstRow[ 0 ].value ).toBe( `Product ${ mockData[ 0 ].product_id }` ); expect( firstRow[ 0 ].value ).toBe( mockData[ 0 ].name );
expect( firstRow[ 1 ].display ).toBe( numberFormat( mockData[ 0 ].items_sold ) ); expect( firstRow[ 1 ].display ).toBe( numberFormat( mockData[ 0 ].items_sold ) );
expect( firstRow[ 1 ].value ).toBe( mockData[ 0 ].items_sold ); expect( firstRow[ 1 ].value ).toBe( mockData[ 0 ].items_sold );
expect( firstRow[ 2 ].display ).toBe( numberFormat( mockData[ 0 ].orders_count ) ); expect( firstRow[ 2 ].display ).toBe( numberFormat( mockData[ 0 ].orders_count ) );
@ -73,7 +73,7 @@ describe( 'TopSellingProducts', () => {
const topSellingProducts = topSellingProductsWrapper.root.findByType( TopSellingProducts ); const topSellingProducts = topSellingProductsWrapper.root.findByType( TopSellingProducts );
const endpoint = '/wc/v3/reports/products'; const endpoint = '/wc/v3/reports/products';
const query = { orderby: 'items_sold', per_page: 5 }; const query = { orderby: 'items_sold', per_page: 5, extended_product_info: 1 };
expect( getReportStatsMock.mock.calls[ 0 ][ 1 ] ).toBe( endpoint ); expect( getReportStatsMock.mock.calls[ 0 ][ 1 ] ).toBe( endpoint );
expect( getReportStatsMock.mock.calls[ 0 ][ 2 ] ).toEqual( query ); expect( getReportStatsMock.mock.calls[ 0 ][ 2 ] ).toEqual( query );