From ae6652b26c4fe6f91fba9e7887c204daaf7b4ff5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albert=20Juh=C3=A9=20Lluveras?= Date: Tue, 16 Oct 2018 10:50:07 +0200 Subject: [PATCH] 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 --- .../client/analytics/report/orders/index.js | 46 ++++++++-- .../client/analytics/report/orders/table.js | 35 +++---- .../client/store/orders/actions.js | 35 +------ .../client/store/orders/reducer.js | 42 +++++---- .../client/store/orders/resolvers.js | 18 ++-- .../client/store/orders/selectors.js | 46 +++++++++- .../client/store/orders/test/reducer.js | 80 ++++++++++++++++ .../client/store/orders/test/resolvers.js | 42 +++++++++ .../client/store/orders/test/selectors.js | 92 +++++++++++++++++++ .../client/store/reports/stats/selectors.js | 2 +- 10 files changed, 348 insertions(+), 90 deletions(-) create mode 100644 plugins/woocommerce-admin/client/store/orders/test/reducer.js create mode 100644 plugins/woocommerce-admin/client/store/orders/test/resolvers.js create mode 100644 plugins/woocommerce-admin/client/store/orders/test/selectors.js diff --git a/plugins/woocommerce-admin/client/analytics/report/orders/index.js b/plugins/woocommerce-admin/client/analytics/report/orders/index.js index 59bb511243d..3df9306aaa1 100644 --- a/plugins/woocommerce-admin/client/analytics/report/orders/index.js +++ b/plugins/woocommerce-admin/client/analytics/report/orders/index.js @@ -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() } - + ); } @@ -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, diff --git a/plugins/woocommerce-admin/client/analytics/report/orders/table.js b/plugins/woocommerce-admin/client/analytics/report/orders/table.js index 7977c7334df..6fd0d9e011e 100644 --- a/plugins/woocommerce-admin/client/analytics/report/orders/table.js +++ b/plugins/woocommerce-admin/client/analytics/report/orders/table.js @@ -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: { id }, @@ -231,37 +231,32 @@ export default class OrdersReportTable extends Component { title={ __( 'Orders', 'wc-admin' ) } className="woocommerce-analytics__table-placeholder" > - + ); } 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 ( { - // 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 || {}, }; }, }; diff --git a/plugins/woocommerce-admin/client/store/orders/reducer.js b/plugins/woocommerce-admin/client/store/orders/reducer.js index 2f6996ac27b..64f28e2e541 100644 --- a/plugins/woocommerce-admin/client/store/orders/reducer.js +++ b/plugins/woocommerce-admin/client/store/orders/reducer.js @@ -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; diff --git a/plugins/woocommerce-admin/client/store/orders/resolvers.js b/plugins/woocommerce-admin/client/store/orders/resolvers.js index 7e8bb015b5b..5be6c64a206 100644 --- a/plugins/woocommerce-admin/client/store/orders/resolvers.js +++ b/plugins/woocommerce-admin/client/store/orders/resolvers.js @@ -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 ); } }, }; diff --git a/plugins/woocommerce-admin/client/store/orders/selectors.js b/plugins/woocommerce-admin/client/store/orders/selectors.js index 7dd92c42949..9fc9b3542d0 100644 --- a/plugins/woocommerce-admin/client/store/orders/selectors.js +++ b/plugins/woocommerce-admin/client/store/orders/selectors.js @@ -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 ); }, }; diff --git a/plugins/woocommerce-admin/client/store/orders/test/reducer.js b/plugins/woocommerce-admin/client/store/orders/test/reducer.js new file mode 100644 index 00000000000..0af59e78779 --- /dev/null +++ b/plugins/woocommerce-admin/client/store/orders/test/reducer.js @@ -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 ); + } ); +} ); diff --git a/plugins/woocommerce-admin/client/store/orders/test/resolvers.js b/plugins/woocommerce-admin/client/store/orders/test/resolvers.js new file mode 100644 index 00000000000..d078cfcfe56 --- /dev/null +++ b/plugins/woocommerce-admin/client/store/orders/test/resolvers.js @@ -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 ) ); + } ); +} ); diff --git a/plugins/woocommerce-admin/client/store/orders/test/selectors.js b/plugins/woocommerce-admin/client/store/orders/test/selectors.js new file mode 100644 index 00000000000..cb4649c9746 --- /dev/null +++ b/plugins/woocommerce-admin/client/store/orders/test/selectors.js @@ -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 ); + } ); +} ); diff --git a/plugins/woocommerce-admin/client/store/reports/stats/selectors.js b/plugins/woocommerce-admin/client/store/reports/stats/selectors.js index 0af4c509b82..7549aeb707a 100644 --- a/plugins/woocommerce-admin/client/store/reports/stats/selectors.js +++ b/plugins/woocommerce-admin/client/store/reports/stats/selectors.js @@ -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 ) {