Send params with Orders table API calls (https://github.com/woocommerce/woocommerce-admin/pull/519)
* Send params with Orders table API calls * Add onError case and caching for Orders calls * Remove unused actions * Load only 'processing', 'on-hold' and 'completed' orders * Use NAMESPACE constant instead of hard-coded value * Comment typos * Add tests to Orders reducer, resolvers and selectors * Typos * Fix JSDoc mismatch
This commit is contained in:
parent
43037baff2
commit
ae6652b26c
|
@ -8,7 +8,7 @@ import { compose } from '@wordpress/compose';
|
|||
import { format as formatDate } from '@wordpress/date';
|
||||
import PropTypes from 'prop-types';
|
||||
import { withSelect } from '@wordpress/data';
|
||||
import { map, find } from 'lodash';
|
||||
import { find, get, map } from 'lodash';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
|
@ -203,7 +203,8 @@ class OrdersReport extends Component {
|
|||
|
||||
render() {
|
||||
const {
|
||||
isRequesting,
|
||||
isTableDataError,
|
||||
isTableDataRequesting,
|
||||
orders,
|
||||
path,
|
||||
query,
|
||||
|
@ -212,9 +213,14 @@ class OrdersReport extends Component {
|
|||
summaryNumbers,
|
||||
} = this.props;
|
||||
|
||||
if ( primaryData.isError || secondaryData.isError || summaryNumbers.isError ) {
|
||||
if (
|
||||
primaryData.isError ||
|
||||
secondaryData.isError ||
|
||||
isTableDataError ||
|
||||
summaryNumbers.isError
|
||||
) {
|
||||
let title, actionLabel, actionURL, actionCallback;
|
||||
if ( primaryData.isError || secondaryData.isError ) {
|
||||
if ( primaryData.isError || secondaryData.isError || isTableDataError ) {
|
||||
title = __( 'There was an error getting your stats. Please try again.', 'wc-admin' );
|
||||
actionLabel = __( 'Reload', 'wc-admin' );
|
||||
actionCallback = () => {
|
||||
|
@ -250,7 +256,16 @@ class OrdersReport extends Component {
|
|||
/>
|
||||
{ this.renderChartSummaryNumbers() }
|
||||
{ this.renderChart() }
|
||||
<OrdersReportTable isRequesting={ isRequesting } orders={ orders } query={ query } />
|
||||
<OrdersReportTable
|
||||
isRequesting={ isTableDataRequesting }
|
||||
orders={ orders }
|
||||
query={ query }
|
||||
totalRows={ get(
|
||||
primaryData,
|
||||
[ 'data', 'totals', 'orders_count' ],
|
||||
Object.keys( orders ).length
|
||||
) }
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
@ -264,9 +279,6 @@ OrdersReport.propTypes = {
|
|||
|
||||
export default compose(
|
||||
withSelect( ( select, props ) => {
|
||||
const { getOrders } = select( 'wc-admin' );
|
||||
const orders = getOrders();
|
||||
const isRequesting = select( 'core/data' ).isResolving( 'wc-admin', 'getOrders' );
|
||||
const { query } = props;
|
||||
const interval = getIntervalForQuery( query );
|
||||
const datesFromQuery = getCurrentDates( query );
|
||||
|
@ -304,8 +316,24 @@ export default compose(
|
|||
},
|
||||
select
|
||||
);
|
||||
|
||||
const { getOrders, isGetOrdersError, isGetOrdersRequesting } = select( 'wc-admin' );
|
||||
const tableQuery = {
|
||||
orderby: query.orderby || 'date',
|
||||
order: query.order || 'asc',
|
||||
page: query.page || 1,
|
||||
per_page: query.per_page || 25,
|
||||
after: datesFromQuery.primary.after + 'T00:00:00+00:00',
|
||||
before: datesFromQuery.primary.before + 'T23:59:59+00:00',
|
||||
status: [ 'processing', 'on-hold', 'completed' ],
|
||||
};
|
||||
const orders = getOrders( tableQuery );
|
||||
const isTableDataError = isGetOrdersError( tableQuery );
|
||||
const isTableDataRequesting = isGetOrdersRequesting( tableQuery );
|
||||
|
||||
return {
|
||||
isRequesting,
|
||||
isTableDataError,
|
||||
isTableDataRequesting,
|
||||
orders,
|
||||
primaryData,
|
||||
secondaryData,
|
||||
|
|
|
@ -43,7 +43,7 @@ export default class OrdersReportTable extends Component {
|
|||
return [
|
||||
{
|
||||
label: __( 'Date', 'wc-admin' ),
|
||||
key: 'date_created',
|
||||
key: 'date',
|
||||
required: true,
|
||||
defaultSort: true,
|
||||
isLeftAligned: true,
|
||||
|
@ -60,13 +60,13 @@ export default class OrdersReportTable extends Component {
|
|||
label: __( 'Status', 'wc-admin' ),
|
||||
key: 'status',
|
||||
required: false,
|
||||
isSortable: true,
|
||||
isSortable: false,
|
||||
},
|
||||
{
|
||||
label: __( 'Customer', 'wc-admin' ),
|
||||
key: 'customer_id',
|
||||
required: false,
|
||||
isSortable: true,
|
||||
isSortable: false,
|
||||
},
|
||||
{
|
||||
label: __( 'Product(s)', 'wc-admin' ),
|
||||
|
@ -78,7 +78,7 @@ export default class OrdersReportTable extends Component {
|
|||
label: __( 'Items Sold', 'wc-admin' ),
|
||||
key: 'items_sold',
|
||||
required: false,
|
||||
isSortable: true,
|
||||
isSortable: false,
|
||||
isNumeric: true,
|
||||
},
|
||||
{
|
||||
|
@ -91,7 +91,7 @@ export default class OrdersReportTable extends Component {
|
|||
label: __( 'N. Revenue', 'wc-admin' ),
|
||||
key: 'net_revenue',
|
||||
required: true,
|
||||
isSortable: true,
|
||||
isSortable: false,
|
||||
isNumeric: true,
|
||||
},
|
||||
];
|
||||
|
@ -114,7 +114,7 @@ export default class OrdersReportTable extends Component {
|
|||
} = row;
|
||||
|
||||
return {
|
||||
date_created,
|
||||
date: date_created,
|
||||
id,
|
||||
status,
|
||||
customer_id,
|
||||
|
@ -136,7 +136,7 @@ export default class OrdersReportTable extends Component {
|
|||
|
||||
return map( tableData, row => {
|
||||
const {
|
||||
date_created,
|
||||
date,
|
||||
id,
|
||||
status,
|
||||
customer_id,
|
||||
|
@ -163,8 +163,8 @@ export default class OrdersReportTable extends Component {
|
|||
|
||||
return [
|
||||
{
|
||||
display: formatDate( tableFormat, date_created ),
|
||||
value: date_created,
|
||||
display: formatDate( tableFormat, date ),
|
||||
value: date,
|
||||
},
|
||||
{
|
||||
display: <a href={ getAdminLink( 'post.php?post=' + id + '&action=edit' ) }>{ id }</a>,
|
||||
|
@ -231,37 +231,32 @@ export default class OrdersReportTable extends Component {
|
|||
title={ __( 'Orders', 'wc-admin' ) }
|
||||
className="woocommerce-analytics__table-placeholder"
|
||||
>
|
||||
<TablePlaceholder caption={ __( 'Orders last week', 'wc-admin' ) } headers={ headers } />
|
||||
<TablePlaceholder caption={ __( 'Orders', 'wc-admin' ) } headers={ headers } />
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
renderTable() {
|
||||
const { orders, query } = this.props;
|
||||
const { orders, query, totalRows } = this.props;
|
||||
|
||||
const page = parseInt( query.page ) || 1;
|
||||
const rowsPerPage = parseInt( query.per_page ) || 25;
|
||||
const rows = this.getRowsContent(
|
||||
orderBy(
|
||||
this.formatTableData( orders ),
|
||||
query.orderby || 'date_created',
|
||||
query.order || 'asc'
|
||||
).slice( ( page - 1 ) * rowsPerPage, page * rowsPerPage )
|
||||
orderBy( this.formatTableData( orders ), query.orderby || 'date', query.order || 'asc' )
|
||||
);
|
||||
|
||||
const headers = this.getHeadersContent();
|
||||
|
||||
const tableQuery = {
|
||||
...query,
|
||||
orderby: query.orderby || 'date_created',
|
||||
orderby: query.orderby || 'date',
|
||||
order: query.order || 'asc',
|
||||
};
|
||||
|
||||
return (
|
||||
<TableCard
|
||||
title={ __( 'Orders last week', 'wc-admin' ) }
|
||||
title={ __( 'Orders', 'wc-admin' ) }
|
||||
rows={ rows }
|
||||
totalRows={ Object.keys( orders ).length }
|
||||
totalRows={ totalRows }
|
||||
rowsPerPage={ rowsPerPage }
|
||||
headers={ headers }
|
||||
onClickDownload={ this.onDownload( headers, rows, tableQuery ) }
|
||||
|
|
|
@ -1,42 +1,17 @@
|
|||
/** @format */
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { dispatch } from '@wordpress/data';
|
||||
import apiFetch from '@wordpress/api-fetch';
|
||||
|
||||
export default {
|
||||
setOrders( orders ) {
|
||||
setOrders( orders, query ) {
|
||||
return {
|
||||
type: 'SET_ORDERS',
|
||||
orders,
|
||||
query: query || {},
|
||||
};
|
||||
},
|
||||
updateOrder( order ) {
|
||||
setOrdersError( query ) {
|
||||
return {
|
||||
type: 'UPDATE_ORDER',
|
||||
order,
|
||||
};
|
||||
},
|
||||
requestUpdateOrder( order ) {
|
||||
return async () => {
|
||||
// Lets be optimistic
|
||||
dispatch( 'wc-admin' ).updateOrder( order );
|
||||
try {
|
||||
const updatedOrder = await apiFetch( {
|
||||
path: '/wc/v3/orders/' + order.id,
|
||||
method: 'PUT',
|
||||
data: order,
|
||||
} );
|
||||
|
||||
dispatch( 'wc-admin' ).updateOrder( updatedOrder );
|
||||
} catch ( error ) {
|
||||
if ( error && error.responseJSON ) {
|
||||
alert( error.responseJSON.message );
|
||||
} else {
|
||||
alert( error );
|
||||
}
|
||||
}
|
||||
type: 'SET_ORDERS_ERROR',
|
||||
query: query || {},
|
||||
};
|
||||
},
|
||||
};
|
||||
|
|
|
@ -1,29 +1,31 @@
|
|||
/** @format */
|
||||
|
||||
const DEFAULT_STATE = {
|
||||
orders: {},
|
||||
ids: [],
|
||||
};
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { merge } from 'lodash';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { ERROR } from 'store/constants';
|
||||
import { getJsonString } from 'store/utils';
|
||||
|
||||
const DEFAULT_STATE = {};
|
||||
|
||||
export default function ordersReducer( state = DEFAULT_STATE, action ) {
|
||||
const queryKey = getJsonString( action.query );
|
||||
|
||||
switch ( action.type ) {
|
||||
case 'SET_ORDERS':
|
||||
const { orders } = action;
|
||||
const ordersMap = orders.reduce( ( map, order ) => {
|
||||
map[ order.id ] = order;
|
||||
return map;
|
||||
}, {} );
|
||||
return {
|
||||
...state,
|
||||
orders: Object.assign( {}, state.orders, ordersMap ),
|
||||
};
|
||||
case 'UPDATE_ORDER':
|
||||
const updatedOrders = { ...state.orders };
|
||||
updatedOrders[ action.order.id ] = action.order;
|
||||
return {
|
||||
...state,
|
||||
orders: updatedOrders,
|
||||
};
|
||||
return merge( {}, state, {
|
||||
[ queryKey ]: action.orders,
|
||||
} );
|
||||
|
||||
case 'SET_ORDERS_ERROR':
|
||||
return merge( {}, state, {
|
||||
[ queryKey ]: ERROR,
|
||||
} );
|
||||
}
|
||||
|
||||
return state;
|
||||
|
|
|
@ -5,17 +5,19 @@
|
|||
import { dispatch } from '@wordpress/data';
|
||||
import apiFetch from '@wordpress/api-fetch';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { stringifyQuery } from 'lib/nav-utils';
|
||||
import { NAMESPACE } from 'store/constants';
|
||||
|
||||
export default {
|
||||
async getOrders() {
|
||||
async getOrders( state, query ) {
|
||||
try {
|
||||
const orders = await apiFetch( { path: '/wc/v3/orders' } );
|
||||
dispatch( 'wc-admin' ).setOrders( orders );
|
||||
const orders = await apiFetch( { path: NAMESPACE + 'orders' + stringifyQuery( query ) } );
|
||||
dispatch( 'wc-admin' ).setOrders( orders, query );
|
||||
} catch ( error ) {
|
||||
if ( error && error.responseJSON ) {
|
||||
alert( error.responseJSON.message );
|
||||
} else {
|
||||
alert( error );
|
||||
}
|
||||
dispatch( 'wc-admin' ).setOrdersError( query );
|
||||
}
|
||||
},
|
||||
};
|
||||
|
|
|
@ -1,7 +1,49 @@
|
|||
/** @format */
|
||||
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { get } from 'lodash';
|
||||
import { select } from '@wordpress/data';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { getJsonString } from 'store/utils';
|
||||
import { ERROR } from 'store/constants';
|
||||
|
||||
/**
|
||||
* Returns orders for a specific query.
|
||||
*
|
||||
* @param {Object} state Current state
|
||||
* @param {Object} query Report query paremters
|
||||
* @return {Array} Report details
|
||||
*/
|
||||
function getOrders( state, query = {} ) {
|
||||
return get( state, [ 'orders', getJsonString( query ) ], [] );
|
||||
}
|
||||
|
||||
export default {
|
||||
getOrders( state ) {
|
||||
return state.orders.orders;
|
||||
getOrders,
|
||||
|
||||
/**
|
||||
* Returns true if a query is pending.
|
||||
*
|
||||
* @param {Object} state Current state
|
||||
* @return {Boolean} True if the `getOrders` request is pending, false otherwise
|
||||
*/
|
||||
isGetOrdersRequesting( state, ...args ) {
|
||||
return select( 'core/data' ).isResolving( 'wc-admin', 'getOrders', args );
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns true if a get orders request has returned an error.
|
||||
*
|
||||
* @param {Object} state Current state
|
||||
* @param {Object} query Query parameters
|
||||
* @return {Boolean} True if the `getOrders` request has failed, false otherwise
|
||||
*/
|
||||
isGetOrdersError( state, query ) {
|
||||
return ERROR === getOrders( state, query );
|
||||
},
|
||||
};
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
/**
|
||||
* @format
|
||||
*/
|
||||
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import deepFreeze from 'deep-freeze';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { ERROR } from 'store/constants';
|
||||
import ordersReducer from '../reducer';
|
||||
import { getJsonString } from 'store/utils';
|
||||
|
||||
describe( 'ordersReducer()', () => {
|
||||
it( 'returns an empty data object by default', () => {
|
||||
const state = ordersReducer( undefined, {} );
|
||||
expect( state ).toEqual( {} );
|
||||
} );
|
||||
|
||||
it( 'returns with received orders data', () => {
|
||||
const originalState = deepFreeze( {} );
|
||||
const query = {
|
||||
orderby: 'date',
|
||||
};
|
||||
const orders = [ { id: 1214 }, { id: 1215 }, { id: 1216 } ];
|
||||
|
||||
const state = ordersReducer( originalState, {
|
||||
type: 'SET_ORDERS',
|
||||
query,
|
||||
orders,
|
||||
} );
|
||||
|
||||
const queryKey = getJsonString( query );
|
||||
expect( state[ queryKey ] ).toEqual( orders );
|
||||
} );
|
||||
|
||||
it( 'tracks multiple queries in orders data', () => {
|
||||
const otherQuery = {
|
||||
orderby: 'id',
|
||||
};
|
||||
const otherQueryKey = getJsonString( otherQuery );
|
||||
const otherOrders = [ { id: 1 }, { id: 2 }, { id: 3 } ];
|
||||
const otherQueryState = {
|
||||
[ otherQueryKey ]: otherOrders,
|
||||
};
|
||||
const originalState = deepFreeze( otherQueryState );
|
||||
const query = {
|
||||
orderby: 'date',
|
||||
};
|
||||
const orders = [ { id: 1214 }, { id: 1215 }, { id: 1216 } ];
|
||||
|
||||
const state = ordersReducer( originalState, {
|
||||
type: 'SET_ORDERS',
|
||||
query,
|
||||
orders,
|
||||
} );
|
||||
|
||||
const queryKey = getJsonString( query );
|
||||
expect( state[ queryKey ] ).toEqual( orders );
|
||||
expect( state[ otherQueryKey ] ).toEqual( otherOrders );
|
||||
} );
|
||||
|
||||
it( 'returns with received error data', () => {
|
||||
const originalState = deepFreeze( {} );
|
||||
const query = {
|
||||
orderby: 'date',
|
||||
};
|
||||
|
||||
const state = ordersReducer( originalState, {
|
||||
type: 'SET_ORDERS_ERROR',
|
||||
query,
|
||||
} );
|
||||
|
||||
const queryKey = getJsonString( query );
|
||||
expect( state[ queryKey ] ).toEqual( ERROR );
|
||||
} );
|
||||
} );
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* @format
|
||||
*/
|
||||
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import apiFetch from '@wordpress/api-fetch';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import resolvers from '../resolvers';
|
||||
|
||||
const { getOrders } = resolvers;
|
||||
|
||||
jest.mock( '@wordpress/api-fetch', () => jest.fn() );
|
||||
|
||||
describe( 'getOrders', () => {
|
||||
const ORDERS_1 = [ { id: 1214 }, { id: 1215 }, { id: 1216 } ];
|
||||
|
||||
const ORDERS_2 = [ { id: 1 }, { id: 2 }, { id: 3 } ];
|
||||
|
||||
beforeAll( () => {
|
||||
apiFetch.mockImplementation( options => {
|
||||
if ( options.path === '/wc/v3/orders' ) {
|
||||
return Promise.resolve( ORDERS_1 );
|
||||
}
|
||||
if ( options.path === '/wc/v3/orders&orderby=id' ) {
|
||||
return Promise.resolve( ORDERS_2 );
|
||||
}
|
||||
} );
|
||||
} );
|
||||
|
||||
it( 'returns requested report data', async () => {
|
||||
getOrders().then( data => expect( data ).toEqual( ORDERS_1 ) );
|
||||
} );
|
||||
|
||||
it( 'returns requested report data for a specific query', async () => {
|
||||
getOrders( { orderby: 'id' } ).then( data => expect( data ).toEqual( ORDERS_2 ) );
|
||||
} );
|
||||
} );
|
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
* @format
|
||||
*/
|
||||
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import deepFreeze from 'deep-freeze';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { ERROR } from 'store/constants';
|
||||
import selectors from '../selectors';
|
||||
import { select } from '@wordpress/data';
|
||||
import { getJsonString } from 'store/utils';
|
||||
|
||||
const { getOrders, isGetOrdersRequesting, isGetOrdersError } = selectors;
|
||||
jest.mock( '@wordpress/data', () => ( {
|
||||
...require.requireActual( '@wordpress/data' ),
|
||||
select: jest.fn().mockReturnValue( {} ),
|
||||
} ) );
|
||||
|
||||
const query = { orderby: 'date' };
|
||||
const queryKey = getJsonString( query );
|
||||
|
||||
describe( 'getOrders()', () => {
|
||||
it( 'returns an empty array when no orders are available', () => {
|
||||
const state = deepFreeze( {} );
|
||||
expect( getOrders( state, query ) ).toEqual( [] );
|
||||
} );
|
||||
|
||||
it( 'returns stored orders for current query', () => {
|
||||
const orders = [ { id: 1214 }, { id: 1215 }, { id: 1216 } ];
|
||||
const state = deepFreeze( {
|
||||
orders: {
|
||||
[ queryKey ]: orders,
|
||||
},
|
||||
} );
|
||||
expect( getOrders( state, query ) ).toEqual( orders );
|
||||
} );
|
||||
} );
|
||||
|
||||
describe( 'isGetOrdersRequesting()', () => {
|
||||
beforeAll( () => {
|
||||
select( 'core/data' ).isResolving = jest.fn().mockReturnValue( false );
|
||||
} );
|
||||
|
||||
afterAll( () => {
|
||||
select( 'core/data' ).isResolving.mockRestore();
|
||||
} );
|
||||
|
||||
function setIsResolving( isResolving ) {
|
||||
select( 'core/data' ).isResolving.mockImplementation(
|
||||
( reducerKey, selectorName ) =>
|
||||
isResolving && reducerKey === 'wc-admin' && selectorName === 'getOrders'
|
||||
);
|
||||
}
|
||||
|
||||
it( 'returns false if never requested', () => {
|
||||
const result = isGetOrdersRequesting( query );
|
||||
expect( result ).toBe( false );
|
||||
} );
|
||||
|
||||
it( 'returns false if request finished', () => {
|
||||
setIsResolving( false );
|
||||
const result = isGetOrdersRequesting( query );
|
||||
expect( result ).toBe( false );
|
||||
} );
|
||||
|
||||
it( 'returns true if requesting', () => {
|
||||
setIsResolving( true );
|
||||
const result = isGetOrdersRequesting( query );
|
||||
expect( result ).toBe( true );
|
||||
} );
|
||||
} );
|
||||
|
||||
describe( 'isGetOrdersError()', () => {
|
||||
it( 'returns false by default', () => {
|
||||
const state = deepFreeze( {} );
|
||||
expect( isGetOrdersError( state, query ) ).toEqual( false );
|
||||
} );
|
||||
|
||||
it( 'returns true if ERROR constant is found', () => {
|
||||
const state = deepFreeze( {
|
||||
orders: {
|
||||
[ queryKey ]: ERROR,
|
||||
},
|
||||
} );
|
||||
expect( isGetOrdersError( state, query ) ).toEqual( true );
|
||||
} );
|
||||
} );
|
|
@ -43,7 +43,7 @@ export default {
|
|||
*
|
||||
* @param {Object} state Current state
|
||||
* @param {String} endpoint Stats endpoint
|
||||
* @param {Object} query Report query paremters
|
||||
* @param {Object} query Report query parameters
|
||||
* @return {Boolean} True if the `getReportStats` request has failed, false otherwise
|
||||
*/
|
||||
isReportStatsError( state, endpoint, query ) {
|
||||
|
|
Loading…
Reference in New Issue