Order product count api requests orders store (#33063)
* Add initial Order redux store * Add unit tests for orders store * Update fetch with headers to parse error * Update action names, and consolidate request function * Add chnagelog * Update resolver to always include id and update reducer to prevent data overwrite
This commit is contained in:
parent
0f0b2d5064
commit
83686b2980
|
@ -0,0 +1,4 @@
|
||||||
|
Significance: minor
|
||||||
|
Type: add
|
||||||
|
|
||||||
|
Add new orders data store, for retrieving orders data. #33063
|
|
@ -35,7 +35,12 @@ const controls = {
|
||||||
headers,
|
headers,
|
||||||
status,
|
status,
|
||||||
data,
|
data,
|
||||||
} ) );
|
} ) )
|
||||||
|
.catch( ( response ) => {
|
||||||
|
return response.json().then( ( data: unknown ) => {
|
||||||
|
throw data;
|
||||||
|
} );
|
||||||
|
} );
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
/**
|
|
||||||
* Internal dependencies
|
|
||||||
*/
|
|
||||||
import { RestApiError } from '../types';
|
|
||||||
|
|
||||||
export type SettingProperties = {
|
export type SettingProperties = {
|
||||||
label?: string;
|
label?: string;
|
||||||
label_class?: string[];
|
label_class?: string[];
|
||||||
|
|
|
@ -17,6 +17,7 @@ export { OPTIONS_STORE_NAME } from './options';
|
||||||
export { ITEMS_STORE_NAME } from './items';
|
export { ITEMS_STORE_NAME } from './items';
|
||||||
export { PAYMENT_GATEWAYS_STORE_NAME } from './payment-gateways';
|
export { PAYMENT_GATEWAYS_STORE_NAME } from './payment-gateways';
|
||||||
export { PRODUCTS_STORE_NAME } from './products';
|
export { PRODUCTS_STORE_NAME } from './products';
|
||||||
|
export { ORDERS_STORE_NAME } from './orders';
|
||||||
export { PaymentGateway } from './payment-gateways/types';
|
export { PaymentGateway } from './payment-gateways/types';
|
||||||
|
|
||||||
// Export hooks
|
// Export hooks
|
||||||
|
@ -69,6 +70,7 @@ export * from './countries/types';
|
||||||
export * from './onboarding/types';
|
export * from './onboarding/types';
|
||||||
export * from './plugins/types';
|
export * from './plugins/types';
|
||||||
export * from './products/types';
|
export * from './products/types';
|
||||||
|
export * from './orders/types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal dependencies
|
* Internal dependencies
|
||||||
|
@ -86,6 +88,7 @@ import type { ITEMS_STORE_NAME } from './items';
|
||||||
import type { COUNTRIES_STORE_NAME } from './countries';
|
import type { COUNTRIES_STORE_NAME } from './countries';
|
||||||
import type { PAYMENT_GATEWAYS_STORE_NAME } from './payment-gateways';
|
import type { PAYMENT_GATEWAYS_STORE_NAME } from './payment-gateways';
|
||||||
import type { PRODUCTS_STORE_NAME } from './products';
|
import type { PRODUCTS_STORE_NAME } from './products';
|
||||||
|
import type { ORDERS_STORE_NAME } from './orders';
|
||||||
|
|
||||||
export type WCDataStoreName =
|
export type WCDataStoreName =
|
||||||
| typeof REVIEWS_STORE_NAME
|
| typeof REVIEWS_STORE_NAME
|
||||||
|
@ -100,7 +103,8 @@ export type WCDataStoreName =
|
||||||
| typeof ITEMS_STORE_NAME
|
| typeof ITEMS_STORE_NAME
|
||||||
| typeof COUNTRIES_STORE_NAME
|
| typeof COUNTRIES_STORE_NAME
|
||||||
| typeof PAYMENT_GATEWAYS_STORE_NAME
|
| typeof PAYMENT_GATEWAYS_STORE_NAME
|
||||||
| typeof PRODUCTS_STORE_NAME;
|
| typeof PRODUCTS_STORE_NAME
|
||||||
|
| typeof ORDERS_STORE_NAME;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal dependencies
|
* Internal dependencies
|
||||||
|
@ -111,6 +115,7 @@ import { PluginSelectors } from './plugins/selectors';
|
||||||
import { OnboardingSelectors } from './onboarding/selectors';
|
import { OnboardingSelectors } from './onboarding/selectors';
|
||||||
import { OptionsSelectors } from './options/types';
|
import { OptionsSelectors } from './options/types';
|
||||||
import { ProductsSelectors } from './products/selectors';
|
import { ProductsSelectors } from './products/selectors';
|
||||||
|
import { OrdersSelectors } from './orders/selectors';
|
||||||
|
|
||||||
// As we add types to all the package selectors we can fill out these unknown types with real ones. See one
|
// As we add types to all the package selectors we can fill out these unknown types with real ones. See one
|
||||||
// of the already typed selectors for an example of how you can do this.
|
// of the already typed selectors for an example of how you can do this.
|
||||||
|
@ -140,6 +145,8 @@ export type WCSelectorType< T > = T extends typeof REVIEWS_STORE_NAME
|
||||||
? WPDataSelectors
|
? WPDataSelectors
|
||||||
: T extends typeof PRODUCTS_STORE_NAME
|
: T extends typeof PRODUCTS_STORE_NAME
|
||||||
? ProductsSelectors
|
? ProductsSelectors
|
||||||
|
: T extends typeof ORDERS_STORE_NAME
|
||||||
|
? OrdersSelectors
|
||||||
: never;
|
: never;
|
||||||
|
|
||||||
export interface WCDataSelector {
|
export interface WCDataSelector {
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
export enum TYPES {
|
||||||
|
GET_ORDER_SUCCESS = 'GET_ORDER_SUCCESS',
|
||||||
|
GET_ORDER_ERROR = 'GET_ORDER_ERROR',
|
||||||
|
GET_ORDERS_SUCCESS = 'GET_ORDERS_SUCCESS',
|
||||||
|
GET_ORDERS_ERROR = 'GET_ORDERS_ERROR',
|
||||||
|
GET_ORDERS_TOTAL_COUNT_SUCCESS = 'GET_ORDERS_TOTAL_COUNT_SUCCESS',
|
||||||
|
GET_ORDERS_TOTAL_COUNT_ERROR = 'GET_ORDERS_TOTAL_COUNT_ERROR',
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TYPES;
|
|
@ -0,0 +1,76 @@
|
||||||
|
/**
|
||||||
|
* Internal dependencies
|
||||||
|
*/
|
||||||
|
import TYPES from './action-types';
|
||||||
|
import { PartialOrder, OrdersQuery } from './types';
|
||||||
|
|
||||||
|
export function getOrderSuccess( id: number, order: PartialOrder ) {
|
||||||
|
return {
|
||||||
|
type: TYPES.GET_ORDER_SUCCESS as const,
|
||||||
|
id,
|
||||||
|
order,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getOrderError( query: Partial< OrdersQuery >, error: unknown ) {
|
||||||
|
return {
|
||||||
|
type: TYPES.GET_ORDER_ERROR as const,
|
||||||
|
query,
|
||||||
|
error,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getOrdersSuccess(
|
||||||
|
query: Partial< OrdersQuery >,
|
||||||
|
orders: PartialOrder[],
|
||||||
|
totalCount: number
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
type: TYPES.GET_ORDERS_SUCCESS as const,
|
||||||
|
orders,
|
||||||
|
query,
|
||||||
|
totalCount,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getOrdersError(
|
||||||
|
query: Partial< OrdersQuery >,
|
||||||
|
error: unknown
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
type: TYPES.GET_ORDERS_ERROR as const,
|
||||||
|
query,
|
||||||
|
error,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getOrdersTotalCountSuccess(
|
||||||
|
query: Partial< OrdersQuery >,
|
||||||
|
totalCount: number
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
type: TYPES.GET_ORDERS_TOTAL_COUNT_SUCCESS as const,
|
||||||
|
query,
|
||||||
|
totalCount,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getOrdersTotalCountError(
|
||||||
|
query: Partial< OrdersQuery >,
|
||||||
|
error: unknown
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
type: TYPES.GET_ORDERS_TOTAL_COUNT_ERROR as const,
|
||||||
|
query,
|
||||||
|
error,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Actions = ReturnType<
|
||||||
|
| typeof getOrderSuccess
|
||||||
|
| typeof getOrderError
|
||||||
|
| typeof getOrdersSuccess
|
||||||
|
| typeof getOrdersError
|
||||||
|
| typeof getOrdersTotalCountSuccess
|
||||||
|
| typeof getOrdersTotalCountError
|
||||||
|
>;
|
|
@ -0,0 +1,3 @@
|
||||||
|
export const STORE_NAME = 'wc/admin/orders';
|
||||||
|
|
||||||
|
export const WC_ORDERS_NAMESPACE = '/wc/v3/orders';
|
|
@ -0,0 +1,26 @@
|
||||||
|
/**
|
||||||
|
* External dependencies
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { registerStore } from '@wordpress/data';
|
||||||
|
import { Reducer } from 'redux';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal dependencies
|
||||||
|
*/
|
||||||
|
import { STORE_NAME } from './constants';
|
||||||
|
import * as selectors from './selectors';
|
||||||
|
import * as actions from './actions';
|
||||||
|
import * as resolvers from './resolvers';
|
||||||
|
import reducer, { OrdersState, State } from './reducer';
|
||||||
|
import controls from '../controls';
|
||||||
|
|
||||||
|
registerStore< State >( STORE_NAME, {
|
||||||
|
reducer: reducer as Reducer< OrdersState >,
|
||||||
|
actions,
|
||||||
|
controls,
|
||||||
|
selectors,
|
||||||
|
resolvers,
|
||||||
|
} );
|
||||||
|
|
||||||
|
export const ORDERS_STORE_NAME = STORE_NAME;
|
|
@ -0,0 +1,104 @@
|
||||||
|
/**
|
||||||
|
* External dependencies
|
||||||
|
*/
|
||||||
|
import { Reducer } from 'redux';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal dependencies
|
||||||
|
*/
|
||||||
|
import TYPES from './action-types';
|
||||||
|
import { Actions } from './actions';
|
||||||
|
import { PartialOrder } from './types';
|
||||||
|
import { getOrderResourceName, getTotalOrderCountResourceName } from './utils';
|
||||||
|
|
||||||
|
export type OrdersState = {
|
||||||
|
orders: Record<
|
||||||
|
string,
|
||||||
|
{
|
||||||
|
data: number[];
|
||||||
|
}
|
||||||
|
>;
|
||||||
|
ordersCount: Record< string, number >;
|
||||||
|
errors: Record< string, unknown >;
|
||||||
|
data: Record< number, PartialOrder >;
|
||||||
|
};
|
||||||
|
|
||||||
|
const reducer: Reducer< OrdersState, Actions > = (
|
||||||
|
state = {
|
||||||
|
orders: {},
|
||||||
|
ordersCount: {},
|
||||||
|
errors: {},
|
||||||
|
data: {},
|
||||||
|
},
|
||||||
|
payload
|
||||||
|
) => {
|
||||||
|
if ( payload && 'type' in payload ) {
|
||||||
|
switch ( payload.type ) {
|
||||||
|
case TYPES.GET_ORDER_SUCCESS:
|
||||||
|
const orderData = state.data || {};
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
data: {
|
||||||
|
...orderData,
|
||||||
|
[ payload.id ]: {
|
||||||
|
...( orderData[ payload.id ] || {} ),
|
||||||
|
...payload.order,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
case TYPES.GET_ORDERS_SUCCESS:
|
||||||
|
const ids: number[] = [];
|
||||||
|
const nextOrders = payload.orders.reduce<
|
||||||
|
Record< number, PartialOrder >
|
||||||
|
>( ( result, order ) => {
|
||||||
|
ids.push( order.id );
|
||||||
|
result[ order.id ] = {
|
||||||
|
...( state.data[ order.id ] || {} ),
|
||||||
|
...order,
|
||||||
|
};
|
||||||
|
return result;
|
||||||
|
}, {} );
|
||||||
|
const resourceName = getOrderResourceName( payload.query );
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
orders: {
|
||||||
|
...state.orders,
|
||||||
|
[ resourceName ]: { data: ids },
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
...state.data,
|
||||||
|
...nextOrders,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
case TYPES.GET_ORDERS_TOTAL_COUNT_SUCCESS:
|
||||||
|
const totalResourceName = getTotalOrderCountResourceName(
|
||||||
|
payload.query
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
ordersCount: {
|
||||||
|
...state.ordersCount,
|
||||||
|
[ totalResourceName ]: payload.totalCount,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
case TYPES.GET_ORDER_ERROR:
|
||||||
|
case TYPES.GET_ORDERS_ERROR:
|
||||||
|
case TYPES.GET_ORDERS_TOTAL_COUNT_ERROR:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
errors: {
|
||||||
|
...state.errors,
|
||||||
|
[ getOrderResourceName(
|
||||||
|
payload.query
|
||||||
|
) ]: payload.error,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return state;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type State = ReturnType< typeof reducer >;
|
||||||
|
export default reducer;
|
|
@ -0,0 +1,60 @@
|
||||||
|
/**
|
||||||
|
* Internal dependencies
|
||||||
|
*/
|
||||||
|
import { WC_ORDERS_NAMESPACE } from './constants';
|
||||||
|
import { Order, OrdersQuery } from './types';
|
||||||
|
import {
|
||||||
|
getOrdersError,
|
||||||
|
getOrdersSuccess,
|
||||||
|
getOrdersTotalCountError,
|
||||||
|
getOrdersTotalCountSuccess,
|
||||||
|
} from './actions';
|
||||||
|
import { request } from '../utils';
|
||||||
|
|
||||||
|
export function* getOrders( query: Partial< OrdersQuery > ) {
|
||||||
|
// id is always required.
|
||||||
|
const ordersQuery = {
|
||||||
|
...query,
|
||||||
|
};
|
||||||
|
if (
|
||||||
|
ordersQuery &&
|
||||||
|
ordersQuery._fields &&
|
||||||
|
! ordersQuery._fields.includes( 'id' )
|
||||||
|
) {
|
||||||
|
ordersQuery._fields = [ 'id', ...ordersQuery._fields ];
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const {
|
||||||
|
items,
|
||||||
|
totalCount,
|
||||||
|
}: { items: Order[]; totalCount: number } = yield request<
|
||||||
|
OrdersQuery,
|
||||||
|
Order
|
||||||
|
>( WC_ORDERS_NAMESPACE, ordersQuery );
|
||||||
|
yield getOrdersTotalCountSuccess( query, totalCount );
|
||||||
|
yield getOrdersSuccess( query, items, totalCount );
|
||||||
|
return items;
|
||||||
|
} catch ( error ) {
|
||||||
|
yield getOrdersError( query, error );
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function* getOrdersTotalCount( query: Partial< OrdersQuery > ) {
|
||||||
|
try {
|
||||||
|
const totalsQuery = {
|
||||||
|
...query,
|
||||||
|
page: 1,
|
||||||
|
per_page: 1,
|
||||||
|
};
|
||||||
|
const { totalCount } = yield request< OrdersQuery, Order >(
|
||||||
|
WC_ORDERS_NAMESPACE,
|
||||||
|
totalsQuery
|
||||||
|
);
|
||||||
|
yield getOrdersTotalCountSuccess( query, totalCount );
|
||||||
|
return totalCount;
|
||||||
|
} catch ( error ) {
|
||||||
|
yield getOrdersTotalCountError( query, error );
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,75 @@
|
||||||
|
/**
|
||||||
|
* External dependencies
|
||||||
|
*/
|
||||||
|
import createSelector from 'rememo';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal dependencies
|
||||||
|
*/
|
||||||
|
import { getOrderResourceName, getTotalOrderCountResourceName } from './utils';
|
||||||
|
import { OrdersState } from './reducer';
|
||||||
|
import { OrdersQuery, PartialOrder } from './types';
|
||||||
|
import { WPDataSelector, WPDataSelectors } from '../types';
|
||||||
|
|
||||||
|
export const getOrders = createSelector(
|
||||||
|
( state: OrdersState, query: OrdersQuery, defaultValue = undefined ) => {
|
||||||
|
const resourceName = getOrderResourceName( query );
|
||||||
|
const ids = state.orders[ resourceName ]
|
||||||
|
? state.orders[ resourceName ].data
|
||||||
|
: undefined;
|
||||||
|
if ( ! ids ) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
if ( query._fields ) {
|
||||||
|
return ids.map( ( id ) => {
|
||||||
|
return query._fields.reduce(
|
||||||
|
( product: PartialOrder, field: keyof PartialOrder ) => {
|
||||||
|
return {
|
||||||
|
...product,
|
||||||
|
[ field ]: state.data[ id ][ field ],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
{} as PartialOrder
|
||||||
|
);
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
return ids.map( ( id ) => {
|
||||||
|
return state.data[ id ];
|
||||||
|
} );
|
||||||
|
},
|
||||||
|
( state, query ) => {
|
||||||
|
const resourceName = getOrderResourceName( query );
|
||||||
|
const ids = state.orders[ resourceName ]
|
||||||
|
? state.orders[ resourceName ].data
|
||||||
|
: [];
|
||||||
|
return [
|
||||||
|
state.orders[ resourceName ],
|
||||||
|
...ids.map( ( id: number ) => {
|
||||||
|
return state.data[ id ];
|
||||||
|
} ),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const getOrdersTotalCount = (
|
||||||
|
state: OrdersState,
|
||||||
|
query: OrdersQuery,
|
||||||
|
defaultValue = undefined
|
||||||
|
) => {
|
||||||
|
const resourceName = getTotalOrderCountResourceName( query );
|
||||||
|
const totalCount = state.ordersCount.hasOwnProperty( resourceName )
|
||||||
|
? state.ordersCount[ resourceName ]
|
||||||
|
: defaultValue;
|
||||||
|
return totalCount;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getOrdersError = ( state: OrdersState, query: OrdersQuery ) => {
|
||||||
|
const resourceName = getOrderResourceName( query );
|
||||||
|
return state.errors[ resourceName ];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type OrdersSelectors = {
|
||||||
|
getOrders: WPDataSelector< typeof getOrders >;
|
||||||
|
getOrdersTotalCount: WPDataSelector< typeof getOrdersTotalCount >;
|
||||||
|
getOrdersError: WPDataSelector< typeof getOrdersError >;
|
||||||
|
} & WPDataSelectors;
|
|
@ -0,0 +1,155 @@
|
||||||
|
/**
|
||||||
|
* Internal dependencies
|
||||||
|
*/
|
||||||
|
import reducer, { OrdersState } from '../reducer';
|
||||||
|
import TYPES from '../action-types';
|
||||||
|
import { getOrderResourceName, getTotalOrderCountResourceName } from '../utils';
|
||||||
|
import { Actions } from '../actions';
|
||||||
|
import { PartialOrder, OrdersQuery } from '../types';
|
||||||
|
|
||||||
|
const defaultState: OrdersState = {
|
||||||
|
orders: {},
|
||||||
|
ordersCount: {},
|
||||||
|
errors: {},
|
||||||
|
data: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
describe( 'orders reducer', () => {
|
||||||
|
it( 'should return a default state', () => {
|
||||||
|
const state = reducer( undefined, {} as Actions );
|
||||||
|
expect( state ).toEqual( defaultState );
|
||||||
|
expect( state ).not.toBe( defaultState );
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'should handle GET_ORDER_SUCCESS', () => {
|
||||||
|
const itemType = 'guyisms';
|
||||||
|
const initialState: OrdersState = {
|
||||||
|
orders: {
|
||||||
|
[ itemType ]: {
|
||||||
|
data: [ 1, 2 ],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ordersCount: {
|
||||||
|
'total-guyisms:{}': 2,
|
||||||
|
},
|
||||||
|
errors: {},
|
||||||
|
data: {
|
||||||
|
1: { id: 1, status: 'pending' },
|
||||||
|
2: { id: 2, status: 'completed' },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const update: PartialOrder = {
|
||||||
|
id: 2,
|
||||||
|
status: 'completed',
|
||||||
|
};
|
||||||
|
|
||||||
|
const state = reducer( initialState, {
|
||||||
|
type: TYPES.GET_ORDER_SUCCESS,
|
||||||
|
id: update.id,
|
||||||
|
order: update,
|
||||||
|
} );
|
||||||
|
|
||||||
|
expect( state.orders ).toEqual( initialState.orders );
|
||||||
|
expect( state.errors ).toEqual( initialState.errors );
|
||||||
|
|
||||||
|
expect( state.data[ 1 ] ).toEqual( initialState.data[ 1 ] );
|
||||||
|
expect( state.data[ 2 ].id ).toEqual( initialState.data[ 2 ].id );
|
||||||
|
expect( state.data[ 2 ].title ).toEqual( initialState.data[ 2 ].title );
|
||||||
|
expect( state.data[ 2 ].status ).toEqual( update.status );
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'should handle GET_ORDERS_SUCCESS', () => {
|
||||||
|
const orders: PartialOrder[] = [ { id: 1 }, { id: 2 } ];
|
||||||
|
const totalCount = 45;
|
||||||
|
const query: Partial< OrdersQuery > = { status: 'completed' };
|
||||||
|
const state = reducer( defaultState, {
|
||||||
|
type: TYPES.GET_ORDERS_SUCCESS,
|
||||||
|
orders,
|
||||||
|
query,
|
||||||
|
totalCount,
|
||||||
|
} );
|
||||||
|
|
||||||
|
const resourceName = getOrderResourceName( query );
|
||||||
|
|
||||||
|
expect( state.orders[ resourceName ].data ).toHaveLength( 2 );
|
||||||
|
expect( state.orders[ resourceName ].data.includes( 1 ) ).toBeTruthy();
|
||||||
|
expect( state.orders[ resourceName ].data.includes( 2 ) ).toBeTruthy();
|
||||||
|
|
||||||
|
expect( state.data[ 1 ] ).toEqual( orders[ 0 ] );
|
||||||
|
expect( state.data[ 2 ] ).toEqual( orders[ 1 ] );
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'GET_ORDERS_SUCCESS, should only update new order data if added', () => {
|
||||||
|
const orders: PartialOrder[] = [
|
||||||
|
{ id: 1 },
|
||||||
|
{ id: 2, total: '36.00', currency: 'CAD' },
|
||||||
|
];
|
||||||
|
const initialState = {
|
||||||
|
...defaultState,
|
||||||
|
data: {
|
||||||
|
1: { id: 1, total: '20.00' },
|
||||||
|
2: { id: 2, total: '34.00' },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const totalCount = 45;
|
||||||
|
const query: Partial< OrdersQuery > = { status: 'completed' };
|
||||||
|
const state = reducer( initialState, {
|
||||||
|
type: TYPES.GET_ORDERS_SUCCESS,
|
||||||
|
orders,
|
||||||
|
query,
|
||||||
|
totalCount,
|
||||||
|
} );
|
||||||
|
|
||||||
|
const resourceName = getOrderResourceName( query );
|
||||||
|
|
||||||
|
expect( state.data[ 1 ].total ).toEqual( initialState.data[ 1 ].total );
|
||||||
|
expect( state.data[ 2 ] ).toEqual( orders[ 1 ] );
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'should handle GET_ORDERS_TOTAL_COUNT_SUCCESS', () => {
|
||||||
|
const initialQuery: Partial< OrdersQuery > = {
|
||||||
|
status: 'completed',
|
||||||
|
page: 1,
|
||||||
|
per_page: 1,
|
||||||
|
_fields: [ 'id' ],
|
||||||
|
};
|
||||||
|
const resourceName = getTotalOrderCountResourceName( initialQuery );
|
||||||
|
const initialState: OrdersState = {
|
||||||
|
...defaultState,
|
||||||
|
ordersCount: {
|
||||||
|
[ resourceName ]: 1,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Additional coverage for getTotalCountResourceName().
|
||||||
|
const similarQueryForTotals: Partial< OrdersQuery > = {
|
||||||
|
status: 'completed',
|
||||||
|
page: 2,
|
||||||
|
per_page: 10,
|
||||||
|
_fields: [ 'id', 'title', 'status' ],
|
||||||
|
};
|
||||||
|
|
||||||
|
const state = reducer( initialState, {
|
||||||
|
type: TYPES.GET_ORDERS_TOTAL_COUNT_SUCCESS,
|
||||||
|
query: similarQueryForTotals,
|
||||||
|
totalCount: 2,
|
||||||
|
} );
|
||||||
|
|
||||||
|
expect( state.ordersCount ).toEqual( {
|
||||||
|
[ resourceName ]: 2,
|
||||||
|
} );
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'should handle GET_ORDERS_ERROR', () => {
|
||||||
|
const query: Partial< OrdersQuery > = { status: 'pending' };
|
||||||
|
const resourceName = getOrderResourceName( query );
|
||||||
|
const error = 'Baaam!';
|
||||||
|
const state = reducer( defaultState, {
|
||||||
|
type: TYPES.GET_ORDERS_ERROR,
|
||||||
|
query,
|
||||||
|
error,
|
||||||
|
} );
|
||||||
|
|
||||||
|
expect( state.errors[ resourceName ] ).toBe( error );
|
||||||
|
} );
|
||||||
|
} );
|
|
@ -0,0 +1,47 @@
|
||||||
|
/**
|
||||||
|
* Internal dependencies
|
||||||
|
*/
|
||||||
|
import { OrdersQuery } from '../types';
|
||||||
|
import { getTotalOrderCountResourceName } from '../utils';
|
||||||
|
|
||||||
|
describe( 'getTotalOrderCountResourceName()', () => {
|
||||||
|
it( "Ignores query params that don't affect total counts", () => {
|
||||||
|
const fullQuery: Partial< OrdersQuery > = {
|
||||||
|
page: 2,
|
||||||
|
per_page: 10,
|
||||||
|
_fields: [ 'id', 'status', 'total' ],
|
||||||
|
status: 'completed',
|
||||||
|
};
|
||||||
|
|
||||||
|
const slimQuery: Partial< OrdersQuery > = {
|
||||||
|
page: 1,
|
||||||
|
per_page: 1,
|
||||||
|
_fields: [ 'id' ],
|
||||||
|
status: 'completed',
|
||||||
|
};
|
||||||
|
|
||||||
|
expect( getTotalOrderCountResourceName( fullQuery ) ).toEqual(
|
||||||
|
getTotalOrderCountResourceName( slimQuery )
|
||||||
|
);
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'Accounts for query params that do affect total counts', () => {
|
||||||
|
const firstQuery: Partial< OrdersQuery > = {
|
||||||
|
page: 2,
|
||||||
|
per_page: 10,
|
||||||
|
_fields: [ 'id', 'status', 'total' ],
|
||||||
|
status: 'completed',
|
||||||
|
};
|
||||||
|
|
||||||
|
const secondQuery: Partial< OrdersQuery > = {
|
||||||
|
page: 1,
|
||||||
|
per_page: 1,
|
||||||
|
_fields: [ 'id' ],
|
||||||
|
status: 'pending',
|
||||||
|
};
|
||||||
|
|
||||||
|
expect( getTotalOrderCountResourceName( firstQuery ) ).not.toEqual(
|
||||||
|
getTotalOrderCountResourceName( secondQuery )
|
||||||
|
);
|
||||||
|
} );
|
||||||
|
} );
|
|
@ -0,0 +1,162 @@
|
||||||
|
/**
|
||||||
|
* External dependencies
|
||||||
|
*/
|
||||||
|
import { Schema } from '@wordpress/core-data';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal dependencies
|
||||||
|
*/
|
||||||
|
import { BaseQueryParams } from '../types';
|
||||||
|
|
||||||
|
export type Address = {
|
||||||
|
first_name: string;
|
||||||
|
last_name: string;
|
||||||
|
company: string;
|
||||||
|
address_1: string;
|
||||||
|
address_2: string;
|
||||||
|
city: string;
|
||||||
|
state: string;
|
||||||
|
postcode: string;
|
||||||
|
country: string;
|
||||||
|
email: string;
|
||||||
|
phone: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type OrderTax = {
|
||||||
|
id: number;
|
||||||
|
total: string;
|
||||||
|
subtotal: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type OrderTaxLine = {
|
||||||
|
id: number;
|
||||||
|
rate_code: string;
|
||||||
|
rate_id: string;
|
||||||
|
label: string;
|
||||||
|
compound: boolean;
|
||||||
|
tax_total: string;
|
||||||
|
shipping_tax_total: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type OrderFeeLine = {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
tax_class: string;
|
||||||
|
tax_status: string;
|
||||||
|
total: string;
|
||||||
|
total_tax: string;
|
||||||
|
taxes: OrderTax[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type OrderShippingLine = {
|
||||||
|
id: number;
|
||||||
|
method_title: string;
|
||||||
|
method_id: string;
|
||||||
|
total: string;
|
||||||
|
total_tax: string;
|
||||||
|
taxes: Omit< OrderTax, 'subtotal' >[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type OrderCoupon = {
|
||||||
|
id: number;
|
||||||
|
code: string;
|
||||||
|
discount: string;
|
||||||
|
discount_tax: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type OrderMetaData = {
|
||||||
|
key: string;
|
||||||
|
label: string;
|
||||||
|
value: unknown;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type OrderRefund = {
|
||||||
|
id: number;
|
||||||
|
reason: string;
|
||||||
|
total: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type OrderLineItem = {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
sku: string;
|
||||||
|
product_id: string | number;
|
||||||
|
variation_id: number;
|
||||||
|
quantity: number;
|
||||||
|
tax_class: string;
|
||||||
|
price: string;
|
||||||
|
subtotal: string;
|
||||||
|
subtotal_tax: string;
|
||||||
|
total: string;
|
||||||
|
total_tax: string;
|
||||||
|
taxes: OrderTax[];
|
||||||
|
meta_data: OrderMetaData[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type OrderStatus =
|
||||||
|
| 'processing'
|
||||||
|
| 'pending'
|
||||||
|
| 'on-hold'
|
||||||
|
| 'completed'
|
||||||
|
| 'cancelled'
|
||||||
|
| 'refunded'
|
||||||
|
| 'failed';
|
||||||
|
|
||||||
|
export type Order< Status = OrderStatus > = Omit< Schema.Post, 'status' > & {
|
||||||
|
id: number;
|
||||||
|
number: string;
|
||||||
|
order_key: string;
|
||||||
|
created_via: string;
|
||||||
|
status: Status;
|
||||||
|
currency: string;
|
||||||
|
version: number;
|
||||||
|
prices_include_tax: boolean;
|
||||||
|
customer_id: number;
|
||||||
|
discount_total: string;
|
||||||
|
discount_tax: string;
|
||||||
|
shipping_total: string;
|
||||||
|
shipping_tax: string;
|
||||||
|
cart_tax: string;
|
||||||
|
total: string;
|
||||||
|
total_tax: string;
|
||||||
|
billing: Address;
|
||||||
|
shipping: Address;
|
||||||
|
payment_method: string;
|
||||||
|
payment_method_title: string;
|
||||||
|
set_paid: boolean;
|
||||||
|
transaction_id: string;
|
||||||
|
customer_ip_address: string;
|
||||||
|
customer_user_agent: string;
|
||||||
|
customer_note: string;
|
||||||
|
date_completed: string;
|
||||||
|
date_paid: string;
|
||||||
|
cart_hash: string;
|
||||||
|
line_items: OrderLineItem[];
|
||||||
|
tax_lines: OrderTaxLine[];
|
||||||
|
shipping_lines: OrderShippingLine[];
|
||||||
|
fee_lines: OrderFeeLine[];
|
||||||
|
coupon_lines: OrderCoupon[];
|
||||||
|
refunds: OrderRefund[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type PartialOrder = Partial< Order > & Pick< Order, 'id' >;
|
||||||
|
|
||||||
|
type OrdersQueryStatus =
|
||||||
|
| 'any'
|
||||||
|
| 'pending'
|
||||||
|
| 'processing'
|
||||||
|
| 'on-hold'
|
||||||
|
| 'completed'
|
||||||
|
| 'cancelled'
|
||||||
|
| 'refunded'
|
||||||
|
| 'failed'
|
||||||
|
| 'trash';
|
||||||
|
|
||||||
|
export type OrdersQuery< Status = OrdersQueryStatus > = BaseQueryParams<
|
||||||
|
keyof Order
|
||||||
|
> & {
|
||||||
|
status: Status;
|
||||||
|
customer: number;
|
||||||
|
product: number;
|
||||||
|
dp: number;
|
||||||
|
};
|
|
@ -0,0 +1,37 @@
|
||||||
|
/**
|
||||||
|
* Internal dependencies
|
||||||
|
*/
|
||||||
|
import { getResourceName } from '../utils';
|
||||||
|
import { OrdersQuery } from './types';
|
||||||
|
|
||||||
|
const PRODUCT_PREFIX = 'order';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a resource name for orders.
|
||||||
|
*
|
||||||
|
* @param {Object} query Query for orders.
|
||||||
|
* @return {string} Resource name for orders.
|
||||||
|
*/
|
||||||
|
export function getOrderResourceName( query: Partial< OrdersQuery > ) {
|
||||||
|
return getResourceName( PRODUCT_PREFIX, query );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a resource name for order totals count.
|
||||||
|
*
|
||||||
|
* It omits query parameters from the identifier that don't affect
|
||||||
|
* totals values like pagination and response field filtering.
|
||||||
|
*
|
||||||
|
* @param {Object} query Query for order totals count.
|
||||||
|
* @return {string} Resource name for order totals.
|
||||||
|
*/
|
||||||
|
export function getTotalOrderCountResourceName(
|
||||||
|
query: Partial< OrdersQuery >
|
||||||
|
) {
|
||||||
|
// Disable eslint rule because we're using this spread to omit properties
|
||||||
|
// that don't affect item totals count results.
|
||||||
|
// eslint-disable-next-line no-unused-vars, camelcase
|
||||||
|
const { _fields, page, per_page, ...totalsQuery } = query;
|
||||||
|
|
||||||
|
return getOrderResourceName( totalsQuery );
|
||||||
|
}
|
Loading…
Reference in New Issue