Migrate `@woocommerce/data` item store to TS (#33482)
Migrate item store to TS
This commit is contained in:
parent
7068c78101
commit
e412d5912b
|
@ -0,0 +1,4 @@
|
|||
Significance: patch
|
||||
Type: dev
|
||||
|
||||
Migrate item store to TS
|
|
@ -3,6 +3,6 @@ const TYPES = {
|
|||
SET_ITEMS: 'SET_ITEMS',
|
||||
SET_ITEMS_TOTAL_COUNT: 'SET_ITEMS_TOTAL_COUNT',
|
||||
SET_ERROR: 'SET_ERROR',
|
||||
};
|
||||
} as const;
|
||||
|
||||
export default TYPES;
|
|
@ -9,8 +9,9 @@ import { addQueryArgs } from '@wordpress/url';
|
|||
*/
|
||||
import TYPES from './action-types';
|
||||
import { NAMESPACE, WC_ADMIN_NAMESPACE } from '../constants';
|
||||
import { ItemType, Item, ProductItem, Query, ItemID } from './types';
|
||||
|
||||
export function setItem( itemType, id, item ) {
|
||||
export function setItem( itemType: ItemType, id: ItemID, item: Item ) {
|
||||
return {
|
||||
type: TYPES.SET_ITEM,
|
||||
id,
|
||||
|
@ -19,7 +20,12 @@ export function setItem( itemType, id, item ) {
|
|||
};
|
||||
}
|
||||
|
||||
export function setItems( itemType, query, items, totalCount ) {
|
||||
export function setItems(
|
||||
itemType: ItemType,
|
||||
query: Query,
|
||||
items: Item[],
|
||||
totalCount?: number
|
||||
) {
|
||||
return {
|
||||
type: TYPES.SET_ITEMS,
|
||||
items,
|
||||
|
@ -29,7 +35,11 @@ export function setItems( itemType, query, items, totalCount ) {
|
|||
};
|
||||
}
|
||||
|
||||
export function setItemsTotalCount( itemType, query, totalCount ) {
|
||||
export function setItemsTotalCount(
|
||||
itemType: ItemType,
|
||||
query: Query,
|
||||
totalCount: number
|
||||
) {
|
||||
return {
|
||||
type: TYPES.SET_ITEMS_TOTAL_COUNT,
|
||||
itemType,
|
||||
|
@ -38,7 +48,11 @@ export function setItemsTotalCount( itemType, query, totalCount ) {
|
|||
};
|
||||
}
|
||||
|
||||
export function setError( itemType, query, error ) {
|
||||
export function setError(
|
||||
itemType: ItemType | 'createProductFromTemplate',
|
||||
query: Record< string, unknown >,
|
||||
error: unknown
|
||||
) {
|
||||
return {
|
||||
type: TYPES.SET_ERROR,
|
||||
itemType,
|
||||
|
@ -47,7 +61,10 @@ export function setError( itemType, query, error ) {
|
|||
};
|
||||
}
|
||||
|
||||
export function* updateProductStock( product, quantity ) {
|
||||
export function* updateProductStock(
|
||||
product: Partial< ProductItem > & { id: ProductItem[ 'id' ] },
|
||||
quantity: number
|
||||
) {
|
||||
const updatedProduct = { ...product, stock_quantity: quantity };
|
||||
const { id, parent_id: parentId, type } = updatedProduct;
|
||||
|
||||
|
@ -75,18 +92,24 @@ export function* updateProductStock( product, quantity ) {
|
|||
} catch ( error ) {
|
||||
// Update failed, return product back to original state.
|
||||
yield setItem( 'products', id, product );
|
||||
yield setError( 'products', id, error );
|
||||
yield setError( 'products', { id }, error );
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function* createProductFromTemplate( itemFields, query ) {
|
||||
export function* createProductFromTemplate(
|
||||
itemFields: {
|
||||
template_name: string;
|
||||
status: string;
|
||||
},
|
||||
query: Query
|
||||
) {
|
||||
try {
|
||||
const url = addQueryArgs(
|
||||
`${ WC_ADMIN_NAMESPACE }/onboarding/tasks/create_product_from_template`,
|
||||
query || {}
|
||||
);
|
||||
const newItem = yield apiFetch( {
|
||||
const newItem: { id: ProductItem[ 'id' ] } = yield apiFetch( {
|
||||
path: url,
|
||||
method: 'POST',
|
||||
data: itemFields,
|
||||
|
@ -98,3 +121,10 @@ export function* createProductFromTemplate( itemFields, query ) {
|
|||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export type Action = ReturnType<
|
||||
| typeof setItem
|
||||
| typeof setItems
|
||||
| typeof setItemsTotalCount
|
||||
| typeof setError
|
||||
>;
|
|
@ -1 +1 @@
|
|||
export const STORE_NAME = 'wc/admin/items';
|
||||
export const STORE_NAME = 'wc/admin/items' as const;
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
|
||||
import { registerStore } from '@wordpress/data';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { STORE_NAME } from './constants';
|
||||
import * as selectors from './selectors';
|
||||
import * as actions from './actions';
|
||||
import * as resolvers from './resolvers';
|
||||
import controls from '../controls';
|
||||
import reducer from './reducer';
|
||||
|
||||
registerStore( STORE_NAME, {
|
||||
reducer,
|
||||
actions,
|
||||
controls,
|
||||
selectors,
|
||||
resolvers,
|
||||
} );
|
||||
|
||||
export const ITEMS_STORE_NAME = STORE_NAME;
|
|
@ -0,0 +1,43 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { registerStore } from '@wordpress/data';
|
||||
import { SelectFromMap, DispatchFromMap } from '@automattic/data-stores';
|
||||
import { Reducer, AnyAction } 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, { State } from './reducer';
|
||||
import controls from '../controls';
|
||||
import { WPDataActions, WPDataSelectors } from '../types';
|
||||
import { getItemsType } from './selectors';
|
||||
export * from './types';
|
||||
export type { State };
|
||||
|
||||
registerStore< State >( STORE_NAME, {
|
||||
reducer: reducer as Reducer< State, AnyAction >,
|
||||
actions,
|
||||
controls,
|
||||
selectors,
|
||||
resolvers,
|
||||
} );
|
||||
|
||||
export const ITEMS_STORE_NAME = STORE_NAME;
|
||||
|
||||
export type ItemsSelector = Omit<
|
||||
SelectFromMap< typeof selectors >,
|
||||
'getItems'
|
||||
> & {
|
||||
getItems: getItemsType;
|
||||
} & WPDataSelectors;
|
||||
|
||||
declare module '@wordpress/data' {
|
||||
function dispatch(
|
||||
key: typeof STORE_NAME
|
||||
): DispatchFromMap< typeof actions & WPDataActions >;
|
||||
function select( key: typeof STORE_NAME ): ItemsSelector;
|
||||
}
|
|
@ -1,79 +0,0 @@
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import TYPES from './action-types';
|
||||
import { getResourceName } from '../utils';
|
||||
import { getTotalCountResourceName } from './utils';
|
||||
|
||||
const reducer = (
|
||||
state = {
|
||||
items: {},
|
||||
errors: {},
|
||||
data: {},
|
||||
},
|
||||
{ type, id, itemType, query, item, items, totalCount, error }
|
||||
) => {
|
||||
switch ( type ) {
|
||||
case TYPES.SET_ITEM:
|
||||
const itemData = state.data[ itemType ] || {};
|
||||
return {
|
||||
...state,
|
||||
data: {
|
||||
...state.data,
|
||||
[ itemType ]: {
|
||||
...itemData,
|
||||
[ id ]: {
|
||||
...( itemData[ id ] || {} ),
|
||||
...item,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
case TYPES.SET_ITEMS:
|
||||
const ids = [];
|
||||
const nextItems = items.reduce( ( result, theItem ) => {
|
||||
ids.push( theItem.id );
|
||||
result[ theItem.id ] = theItem;
|
||||
return result;
|
||||
}, {} );
|
||||
const resourceName = getResourceName( itemType, query );
|
||||
return {
|
||||
...state,
|
||||
items: {
|
||||
...state.items,
|
||||
[ resourceName ]: { data: ids },
|
||||
},
|
||||
data: {
|
||||
...state.data,
|
||||
[ itemType ]: {
|
||||
...state.data[ itemType ],
|
||||
...nextItems,
|
||||
},
|
||||
},
|
||||
};
|
||||
case TYPES.SET_ITEMS_TOTAL_COUNT:
|
||||
const totalResourceName = getTotalCountResourceName(
|
||||
itemType,
|
||||
query
|
||||
);
|
||||
return {
|
||||
...state,
|
||||
items: {
|
||||
...state.items,
|
||||
[ totalResourceName ]: totalCount,
|
||||
},
|
||||
};
|
||||
case TYPES.SET_ERROR:
|
||||
return {
|
||||
...state,
|
||||
errors: {
|
||||
...state.errors,
|
||||
[ getResourceName( itemType, query ) ]: error,
|
||||
},
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default reducer;
|
|
@ -0,0 +1,97 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
|
||||
import type { Reducer } from 'redux';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import TYPES from './action-types';
|
||||
import { getResourceName } from '../utils';
|
||||
import { getTotalCountResourceName } from './utils';
|
||||
import { Action } from './actions';
|
||||
import { ItemsState, Item, ItemID } from './types';
|
||||
|
||||
const initialState: ItemsState = {
|
||||
items: {},
|
||||
errors: {},
|
||||
data: {},
|
||||
};
|
||||
|
||||
const reducer: Reducer< ItemsState, Action > = (
|
||||
state = initialState,
|
||||
action
|
||||
) => {
|
||||
switch ( action.type ) {
|
||||
case TYPES.SET_ITEM:
|
||||
const itemData = state.data[ action.itemType ] || {};
|
||||
return {
|
||||
...state,
|
||||
data: {
|
||||
...state.data,
|
||||
[ action.itemType ]: {
|
||||
...itemData,
|
||||
[ action.id ]: {
|
||||
...( itemData[ action.id ] || {} ),
|
||||
...action.item,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
case TYPES.SET_ITEMS:
|
||||
const ids: Array< ItemID > = [];
|
||||
const nextItems = action.items.reduce< Record< ItemID, Item > >(
|
||||
( result, theItem ) => {
|
||||
ids.push( theItem.id );
|
||||
result[ theItem.id ] = theItem;
|
||||
return result;
|
||||
},
|
||||
{}
|
||||
);
|
||||
const resourceName = getResourceName(
|
||||
action.itemType,
|
||||
action.query
|
||||
);
|
||||
return {
|
||||
...state,
|
||||
items: {
|
||||
...state.items,
|
||||
[ resourceName ]: { data: ids },
|
||||
},
|
||||
data: {
|
||||
...state.data,
|
||||
[ action.itemType ]: {
|
||||
...state.data[ action.itemType ],
|
||||
...nextItems,
|
||||
},
|
||||
},
|
||||
};
|
||||
case TYPES.SET_ITEMS_TOTAL_COUNT:
|
||||
const totalResourceName = getTotalCountResourceName(
|
||||
action.itemType,
|
||||
action.query
|
||||
);
|
||||
return {
|
||||
...state,
|
||||
items: {
|
||||
...state.items,
|
||||
[ totalResourceName ]: action.totalCount,
|
||||
},
|
||||
};
|
||||
case TYPES.SET_ERROR:
|
||||
return {
|
||||
...state,
|
||||
errors: {
|
||||
...state.errors,
|
||||
[ getResourceName( action.itemType, action.query ) ]:
|
||||
action.error,
|
||||
},
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export type State = ReturnType< typeof reducer >;
|
||||
export default reducer;
|
|
@ -4,8 +4,9 @@
|
|||
import { NAMESPACE } from '../constants';
|
||||
import { setError, setItems, setItemsTotalCount } from './actions';
|
||||
import { request } from '../utils';
|
||||
import { ItemType, Query } from './types';
|
||||
|
||||
export function* getItems( itemType, query ) {
|
||||
export function* getItems( itemType: ItemType, query: Query ) {
|
||||
try {
|
||||
const endpoint =
|
||||
itemType === 'categories' ? 'products/categories' : itemType;
|
||||
|
@ -13,6 +14,7 @@ export function* getItems( itemType, query ) {
|
|||
`${ NAMESPACE }/${ endpoint }`,
|
||||
query
|
||||
);
|
||||
|
||||
yield setItemsTotalCount( itemType, query, totalCount );
|
||||
yield setItems( itemType, query, items );
|
||||
} catch ( error ) {
|
||||
|
@ -20,11 +22,7 @@ export function* getItems( itemType, query ) {
|
|||
}
|
||||
}
|
||||
|
||||
export function* getReviewsTotalCount( itemType, query ) {
|
||||
yield getItemsTotalCount( itemType, query );
|
||||
}
|
||||
|
||||
export function* getItemsTotalCount( itemType, query ) {
|
||||
export function* getItemsTotalCount( itemType: ItemType, query: Query ) {
|
||||
try {
|
||||
const totalsQuery = {
|
||||
...query,
|
||||
|
@ -42,3 +40,7 @@ export function* getItemsTotalCount( itemType, query ) {
|
|||
yield setError( itemType, query, error );
|
||||
}
|
||||
}
|
||||
|
||||
export function* getReviewsTotalCount( itemType: ItemType, query: Query ) {
|
||||
yield getItemsTotalCount( itemType, query );
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import createSelector from 'rememo';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { getResourceName } from '../utils';
|
||||
import { getTotalCountResourceName } from './utils';
|
||||
|
||||
export const getItems = createSelector(
|
||||
( state, itemType, query, defaultValue = new Map() ) => {
|
||||
const resourceName = getResourceName( itemType, query );
|
||||
const ids =
|
||||
state.items[ resourceName ] && state.items[ resourceName ].data;
|
||||
if ( ! ids ) {
|
||||
return defaultValue;
|
||||
}
|
||||
return ids.reduce( ( map, id ) => {
|
||||
map.set( id, state.data[ itemType ][ id ] );
|
||||
return map;
|
||||
}, new Map() );
|
||||
},
|
||||
( state, itemType, query ) => {
|
||||
const resourceName = getResourceName( itemType, query );
|
||||
return [ state.items[ resourceName ] ];
|
||||
}
|
||||
);
|
||||
|
||||
export const getItemsTotalCount = (
|
||||
state,
|
||||
itemType,
|
||||
query,
|
||||
defaultValue = 0
|
||||
) => {
|
||||
const resourceName = getTotalCountResourceName( itemType, query );
|
||||
const totalCount = state.items.hasOwnProperty( resourceName )
|
||||
? state.items[ resourceName ]
|
||||
: defaultValue;
|
||||
return totalCount;
|
||||
};
|
||||
|
||||
export const getItemsError = ( state, itemType, query ) => {
|
||||
const resourceName = getResourceName( itemType, query );
|
||||
return state.errors[ resourceName ];
|
||||
};
|
|
@ -0,0 +1,74 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import createSelector from 'rememo';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { getResourceName } from '../utils';
|
||||
import { getTotalCountResourceName } from './utils';
|
||||
|
||||
import { ItemType, ItemsState, Query, ItemInfer } from './types';
|
||||
|
||||
export type getItemsType = < T extends ItemType >(
|
||||
itemType: T,
|
||||
query: Query,
|
||||
defaultValue?: Map< number, ItemInfer< T > | undefined >
|
||||
) => Map< number, ItemInfer< T > | undefined >;
|
||||
|
||||
type getItemsSelectorType = < T extends ItemType >(
|
||||
state: ItemsState,
|
||||
itemType: T,
|
||||
query: Query,
|
||||
defaultValue?: Map< number, ItemInfer< T > | undefined >
|
||||
) => Map< number, Map< number, ItemInfer< T > | undefined > >;
|
||||
|
||||
export const getItems = createSelector< getItemsSelectorType >(
|
||||
( state, itemType, query, defaultValue = new Map() ) => {
|
||||
const resourceName = getResourceName( itemType, query );
|
||||
|
||||
let ids;
|
||||
if (
|
||||
state.items[ resourceName ] &&
|
||||
typeof state.items[ resourceName ] === 'object'
|
||||
) {
|
||||
ids = ( state.items[ resourceName ] as Record< string, number[] > )
|
||||
.data;
|
||||
}
|
||||
|
||||
if ( ! ids ) {
|
||||
return defaultValue;
|
||||
}
|
||||
return ids.reduce( ( map, id: number ) => {
|
||||
map.set( id, state.data[ itemType ]?.[ id ] );
|
||||
return map;
|
||||
}, new Map() );
|
||||
},
|
||||
( state, itemType, query ) => {
|
||||
const resourceName = getResourceName( itemType, query );
|
||||
return [ state.items[ resourceName ] ];
|
||||
}
|
||||
);
|
||||
|
||||
export const getItemsTotalCount = (
|
||||
state: ItemsState,
|
||||
itemType: ItemType,
|
||||
query: Query,
|
||||
defaultValue = 0
|
||||
) => {
|
||||
const resourceName = getTotalCountResourceName( itemType, query );
|
||||
const totalCount = state.items.hasOwnProperty( resourceName )
|
||||
? state.items[ resourceName ]
|
||||
: defaultValue;
|
||||
return totalCount;
|
||||
};
|
||||
|
||||
export const getItemsError = (
|
||||
state: ItemsState,
|
||||
itemType: ItemType,
|
||||
query: Query
|
||||
) => {
|
||||
const resourceName = getResourceName( itemType, query );
|
||||
return state.errors[ resourceName ];
|
||||
};
|
|
@ -5,6 +5,7 @@ import reducer from '../reducer';
|
|||
import TYPES from '../action-types';
|
||||
import { getResourceName } from '../../utils';
|
||||
import { getTotalCountResourceName } from '../utils';
|
||||
import { ProductItem } from '../types';
|
||||
|
||||
const defaultState = {
|
||||
items: {},
|
||||
|
@ -14,13 +15,14 @@ const defaultState = {
|
|||
|
||||
describe( 'items reducer', () => {
|
||||
it( 'should return a default state', () => {
|
||||
// @ts-expect-error - we're testing the default state
|
||||
const state = reducer( undefined, {} );
|
||||
expect( state ).toEqual( defaultState );
|
||||
expect( state ).not.toBe( defaultState );
|
||||
} );
|
||||
|
||||
it( 'should handle SET_ITEM', () => {
|
||||
const itemType = 'guyisms';
|
||||
const itemType = 'products';
|
||||
const initialState = {
|
||||
items: {
|
||||
[ itemType ]: {
|
||||
|
@ -31,14 +33,14 @@ describe( 'items reducer', () => {
|
|||
errors: {},
|
||||
data: {
|
||||
[ itemType ]: {
|
||||
1: { id: 1, title: 'Donkey', status: 'flavortown' },
|
||||
2: { id: 2, title: 'Sauce', status: 'flavortown' },
|
||||
1: { id: 1, name: 'Donkey', tax_status: 'flavortown' },
|
||||
2: { id: 2, name: 'Sauce', tax_status: 'flavortown' },
|
||||
},
|
||||
},
|
||||
};
|
||||
const update = {
|
||||
id: 2,
|
||||
status: 'bomb dot com',
|
||||
tax_status: 'bomb dot com',
|
||||
};
|
||||
|
||||
const state = reducer( initialState, {
|
||||
|
@ -51,26 +53,28 @@ describe( 'items reducer', () => {
|
|||
expect( state.items ).toEqual( initialState.items );
|
||||
expect( state.errors ).toEqual( initialState.errors );
|
||||
|
||||
expect( state.data[ itemType ][ '1' ] ).toEqual(
|
||||
initialState.data[ itemType ][ '1' ]
|
||||
);
|
||||
expect( state.data[ itemType ][ '2' ].id ).toEqual(
|
||||
const item = state.data[ itemType ] || {};
|
||||
|
||||
expect( item[ '1' ] ).toEqual( initialState.data[ itemType ][ '1' ] );
|
||||
expect( item[ '2' ].id ).toEqual(
|
||||
initialState.data[ itemType ][ '2' ].id
|
||||
);
|
||||
expect( state.data[ itemType ][ '2' ].title ).toEqual(
|
||||
initialState.data[ itemType ][ '2' ].title
|
||||
expect( ( item[ '2' ] as ProductItem ).name ).toEqual(
|
||||
initialState.data[ itemType ][ '2' ].name
|
||||
);
|
||||
expect( ( item[ '2' ] as Partial< ProductItem > ).tax_status ).toEqual(
|
||||
update.tax_status
|
||||
);
|
||||
expect( state.data[ itemType ][ '2' ].status ).toEqual( update.status );
|
||||
} );
|
||||
|
||||
it( 'should handle SET_ITEMS', () => {
|
||||
const items = [
|
||||
{ id: 1, title: 'Yum!' },
|
||||
{ id: 2, title: 'Dynamite!' },
|
||||
{ id: 1, name: 'Yum!' },
|
||||
{ id: 2, name: 'Dynamite!' },
|
||||
];
|
||||
const totalCount = 45;
|
||||
const query = { status: 'flavortown' };
|
||||
const itemType = 'BBQ';
|
||||
const query = { page: 1 };
|
||||
const itemType = 'products';
|
||||
const state = reducer( defaultState, {
|
||||
type: TYPES.SET_ITEMS,
|
||||
items,
|
||||
|
@ -81,16 +85,31 @@ describe( 'items reducer', () => {
|
|||
|
||||
const resourceName = getResourceName( itemType, query );
|
||||
|
||||
expect( state.items[ resourceName ].data ).toHaveLength( 2 );
|
||||
expect( state.items[ resourceName ].data.includes( 1 ) ).toBeTruthy();
|
||||
expect( state.items[ resourceName ].data.includes( 2 ) ).toBeTruthy();
|
||||
expect(
|
||||
( state.items[ resourceName ] as { [ key: string ]: number[] } )
|
||||
.data
|
||||
).toHaveLength( 2 );
|
||||
expect(
|
||||
(
|
||||
state.items[ resourceName ] as {
|
||||
[ key: string ]: number[];
|
||||
}
|
||||
).data.includes( 1 )
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
(
|
||||
state.items[ resourceName ] as {
|
||||
[ key: string ]: number[];
|
||||
}
|
||||
).data.includes( 2 )
|
||||
).toBeTruthy();
|
||||
|
||||
expect( state.data[ itemType ][ '1' ] ).toBe( items[ 0 ] );
|
||||
expect( state.data[ itemType ][ '2' ] ).toBe( items[ 1 ] );
|
||||
expect( ( state.data[ itemType ] || {} )[ '1' ] ).toBe( items[ 0 ] );
|
||||
expect( ( state.data[ itemType ] || {} )[ '2' ] ).toBe( items[ 1 ] );
|
||||
} );
|
||||
|
||||
it( 'should handle SET_ITEMS_TOTAL_COUNT', () => {
|
||||
const itemType = 'BBQ';
|
||||
const itemType = 'products';
|
||||
const initialQuery = {
|
||||
status: 'flavortown',
|
||||
page: 1,
|
||||
|
@ -105,6 +124,8 @@ describe( 'items reducer', () => {
|
|||
items: {
|
||||
[ resourceName ]: 1,
|
||||
},
|
||||
data: {},
|
||||
errors: {},
|
||||
};
|
||||
|
||||
// Additional coverage for getTotalCountResourceName().
|
||||
|
@ -112,7 +133,7 @@ describe( 'items reducer', () => {
|
|||
status: 'flavortown',
|
||||
page: 2,
|
||||
per_page: 10,
|
||||
_fields: [ 'id', 'title', 'status' ],
|
||||
_fields: [ 'id', 'name', 'status' ],
|
||||
};
|
||||
|
||||
const state = reducer( initialState, {
|
||||
|
@ -123,6 +144,8 @@ describe( 'items reducer', () => {
|
|||
} );
|
||||
|
||||
expect( state ).toEqual( {
|
||||
data: {},
|
||||
errors: {},
|
||||
items: {
|
||||
[ resourceName ]: 2,
|
||||
},
|
||||
|
@ -131,7 +154,7 @@ describe( 'items reducer', () => {
|
|||
|
||||
it( 'should handle SET_ERROR', () => {
|
||||
const query = { status: 'flavortown' };
|
||||
const itemType = 'BBQ';
|
||||
const itemType = 'products';
|
||||
const resourceName = getResourceName( itemType, query );
|
||||
const error = 'Baaam!';
|
||||
const state = reducer( defaultState, {
|
|
@ -0,0 +1,229 @@
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { BaseQueryParams } from '../types/query-params';
|
||||
|
||||
type Link = {
|
||||
href: string;
|
||||
};
|
||||
|
||||
// Category, Product, Customer item id is a number, and leaderboards item is a string.
|
||||
export type ItemID = number | string;
|
||||
|
||||
export type ItemType = 'categories' | 'products' | 'customers' | 'leaderboards';
|
||||
|
||||
export type ItemImage = {
|
||||
id: number;
|
||||
date_created: string;
|
||||
date_created_gmt: string;
|
||||
date_modified: string;
|
||||
date_modified_gmt: string;
|
||||
src: string;
|
||||
name: string;
|
||||
alt: string;
|
||||
};
|
||||
|
||||
// https://github.com/woocommerce/woocommerce/blob/trunk/plugins/woocommerce/includes/rest-api/Controllers/Version3/class-wc-rest-product-categories-controller.php#L97-L208
|
||||
export type CategoryItem = {
|
||||
id: number;
|
||||
name: string;
|
||||
slug: string;
|
||||
parent: number;
|
||||
description: string;
|
||||
display: string;
|
||||
image: null | ItemImage;
|
||||
menu_order: number;
|
||||
count: number;
|
||||
_links: {
|
||||
collection: Array< Link >;
|
||||
self: Array< Link >;
|
||||
};
|
||||
};
|
||||
|
||||
// https://github.com/woocommerce/woocommerce/blob/trunk/plugins/woocommerce/src/Admin/API/Products.php#L72-L83
|
||||
// https://github.com/woocommerce/woocommerce/blob/trunk/plugins/woocommerce/includes/rest-api/Controllers/Version3/class-wc-rest-products-controller.php#L809-L1423
|
||||
export type ProductItem = {
|
||||
id: number;
|
||||
name: string;
|
||||
slug: string;
|
||||
permalink: string;
|
||||
attributes: Array< {
|
||||
id: number;
|
||||
name: string;
|
||||
position: number;
|
||||
visible: boolean;
|
||||
variation: boolean;
|
||||
options: string[];
|
||||
} >;
|
||||
average_rating: string;
|
||||
backordered: boolean;
|
||||
backorders: string;
|
||||
backorders_allowed: boolean;
|
||||
button_text: string;
|
||||
catalog_visibility: string;
|
||||
categories: Array< {
|
||||
id: number;
|
||||
name: string;
|
||||
slug: string;
|
||||
} >;
|
||||
cross_sell_ids: number[];
|
||||
date_created: string;
|
||||
date_created_gmt: string;
|
||||
date_modified: string;
|
||||
date_modified_gmt: string;
|
||||
date_on_sale_from: null | string;
|
||||
date_on_sale_from_gmt: null | string;
|
||||
date_on_sale_to: null | string;
|
||||
date_on_sale_to_gmt: null | string;
|
||||
default_attributes: Array< {
|
||||
id: number;
|
||||
name: string;
|
||||
option: string;
|
||||
} >;
|
||||
description: string;
|
||||
dimensions: { length: string; width: string; height: string };
|
||||
download_expiry: number;
|
||||
download_limit: number;
|
||||
downloadable: boolean;
|
||||
downloads: Array< {
|
||||
id: number;
|
||||
name: string;
|
||||
file: string;
|
||||
} >;
|
||||
external_url: string;
|
||||
featured: boolean;
|
||||
grouped_products: Array< number >;
|
||||
has_options: boolean;
|
||||
images: Array< ItemImage >;
|
||||
low_stock_amount: null | number;
|
||||
manage_stock: boolean;
|
||||
menu_order: number;
|
||||
meta_data: Array< {
|
||||
id: number;
|
||||
key: string;
|
||||
value: string;
|
||||
} >;
|
||||
on_sale: boolean;
|
||||
parent_id: number;
|
||||
price: string;
|
||||
price_html: string;
|
||||
purchasable: boolean;
|
||||
purchase_note: string;
|
||||
rating_count: number;
|
||||
regular_price: string;
|
||||
related_ids: number[];
|
||||
reviews_allowed: boolean;
|
||||
sale_price: string;
|
||||
shipping_class: string;
|
||||
shipping_class_id: number;
|
||||
shipping_required: boolean;
|
||||
shipping_taxable: boolean;
|
||||
short_description: string;
|
||||
sku: string;
|
||||
sold_individually: boolean;
|
||||
status: string;
|
||||
stock_quantity: number;
|
||||
stock_status: string;
|
||||
tags: Array< {
|
||||
id: number;
|
||||
name: string;
|
||||
slug: string;
|
||||
} >;
|
||||
tax_class: string;
|
||||
tax_status: string;
|
||||
total_sales: number;
|
||||
type: string;
|
||||
upsell_ids: number[];
|
||||
variations: Array< {
|
||||
id: number;
|
||||
date_created: string;
|
||||
date_created_gmt: string;
|
||||
date_modified: string;
|
||||
date_modified_gmt: string;
|
||||
attributes: Array< {
|
||||
id: number;
|
||||
name: string;
|
||||
option: string;
|
||||
} >;
|
||||
image: string;
|
||||
price: string;
|
||||
regular_price: string;
|
||||
sale_price: string;
|
||||
sku: string;
|
||||
stock_quantity: number;
|
||||
tax_class: string;
|
||||
tax_status: string;
|
||||
total_sales: number;
|
||||
weight: string;
|
||||
} >;
|
||||
virtual: boolean;
|
||||
weight: string;
|
||||
last_order_date: string;
|
||||
};
|
||||
|
||||
// https://github.com/woocommerce/woocommerce/blob/trunk/plugins/woocommerce/src/Admin/API/Reports/Customers/Controller.php#L221-L318
|
||||
export type CustomerItem = {
|
||||
id: number;
|
||||
user_id: number;
|
||||
name: string;
|
||||
username: string;
|
||||
country: string;
|
||||
city: string;
|
||||
state: string;
|
||||
postcode: string;
|
||||
date_registered: string;
|
||||
date_registered_gmt: string;
|
||||
date_last_active: string;
|
||||
date_last_active_gmt: string;
|
||||
orders_count: number;
|
||||
total_spent: number;
|
||||
avg_order_value: number;
|
||||
_links: {
|
||||
self: Array< Link >;
|
||||
};
|
||||
};
|
||||
|
||||
// https://github.com/woocommerce/woocommerce/blob/trunk/plugins/woocommerce/src/Admin/API/Leaderboards.php#L527-L585
|
||||
export type LeaderboardItem = {
|
||||
id: string;
|
||||
label: string;
|
||||
headers: {
|
||||
label: string;
|
||||
};
|
||||
rows: {
|
||||
display: string;
|
||||
value: string;
|
||||
};
|
||||
};
|
||||
|
||||
export type Item = Partial<
|
||||
CategoryItem | ProductItem | CustomerItem | LeaderboardItem
|
||||
> & {
|
||||
id: ItemID;
|
||||
};
|
||||
|
||||
export type ItemInfer< T > = Partial<
|
||||
T extends 'categories'
|
||||
? CategoryItem
|
||||
: T extends 'products'
|
||||
? ProductItem
|
||||
: T extends 'customers'
|
||||
? CustomerItem
|
||||
: T extends 'leaderboards'
|
||||
? LeaderboardItem
|
||||
: never
|
||||
> & {
|
||||
id: ItemID;
|
||||
};
|
||||
|
||||
export type ItemsState = {
|
||||
items:
|
||||
| Record< string, { data: ItemID[] } | number >
|
||||
| Record< string, never >;
|
||||
data: Partial< Record< ItemType, Record< ItemID, Item > > >;
|
||||
errors: {
|
||||
[ key: string ]: unknown;
|
||||
};
|
||||
};
|
||||
|
||||
export type Query = Partial< BaseQueryParams >;
|
|
@ -2,12 +2,24 @@
|
|||
* External dependencies
|
||||
*/
|
||||
import { appendTimestamp, getCurrentDates } from '@woocommerce/date';
|
||||
|
||||
import { select as wpSelect } from '@wordpress/data';
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { STORE_NAME } from './constants';
|
||||
import { getResourceName } from '../utils';
|
||||
import { ItemInfer, ItemType, Query } from './types';
|
||||
import { ItemsSelector } from './';
|
||||
|
||||
type Options = {
|
||||
id: number;
|
||||
per_page: number;
|
||||
persisted_query: Query;
|
||||
filterQuery: Query;
|
||||
query: { [ key: string ]: string | undefined };
|
||||
select: typeof wpSelect;
|
||||
defaultDateRange: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns leaderboard data to render a leaderboard table.
|
||||
|
@ -17,11 +29,12 @@ import { getResourceName } from '../utils';
|
|||
* @param {number} options.per_page Per page limit
|
||||
* @param {Object} options.persisted_query Persisted query passed to endpoint
|
||||
* @param {Object} options.query Query parameters in the url
|
||||
* @param {Object} options.filterQuery Query parameters to filter the leaderboard
|
||||
* @param {Object} options.select Instance of @wordpress/select
|
||||
* @param {string} options.defaultDateRange User specified default date range.
|
||||
* @return {Object} Object containing leaderboard responses.
|
||||
*/
|
||||
export function getLeaderboard( options ) {
|
||||
export function getLeaderboard( options: Options ) {
|
||||
const endpoint = 'leaderboards';
|
||||
const {
|
||||
per_page: perPage,
|
||||
|
@ -30,6 +43,7 @@ export function getLeaderboard( options ) {
|
|||
select,
|
||||
filterQuery,
|
||||
} = options;
|
||||
|
||||
const { getItems, getItemsError, isResolving } = select( STORE_NAME );
|
||||
const response = {
|
||||
isRequesting: false,
|
||||
|
@ -49,7 +63,10 @@ export function getLeaderboard( options ) {
|
|||
// Disable eslint rule requiring `getItems` to be defined below because the next two statements
|
||||
// depend on `getItems` to have been called.
|
||||
// eslint-disable-next-line @wordpress/no-unused-vars-before-return
|
||||
const leaderboards = getItems( endpoint, leaderboardQuery );
|
||||
const leaderboards = getItems< 'leaderboards' >(
|
||||
endpoint,
|
||||
leaderboardQuery
|
||||
);
|
||||
|
||||
if ( isResolving( 'getItems', [ endpoint, leaderboardQuery ] ) ) {
|
||||
return { ...response, isRequesting: true };
|
||||
|
@ -69,15 +86,15 @@ export function getLeaderboard( options ) {
|
|||
* @param {Object} options Query options.
|
||||
* @return {Object} Object containing API request information and the matching items.
|
||||
*/
|
||||
export function searchItemsByString(
|
||||
selector,
|
||||
endpoint,
|
||||
search,
|
||||
options = {}
|
||||
export function searchItemsByString< T extends ItemType >(
|
||||
selector: ItemsSelector,
|
||||
endpoint: T,
|
||||
search: string[],
|
||||
options: Query = {}
|
||||
) {
|
||||
const { getItems, getItemsError, isResolving } = selector;
|
||||
|
||||
const items = {};
|
||||
const items: Record< number, ItemInfer< T > | undefined > = {};
|
||||
let isRequesting = false;
|
||||
let isError = false;
|
||||
search.forEach( ( searchWord ) => {
|
||||
|
@ -86,7 +103,7 @@ export function searchItemsByString(
|
|||
per_page: 10,
|
||||
...options,
|
||||
};
|
||||
const newItems = getItems( endpoint, query );
|
||||
const newItems = getItems< T >( endpoint, query );
|
||||
newItems.forEach( ( item, id ) => {
|
||||
items[ id ] = item;
|
||||
} );
|
||||
|
@ -111,11 +128,12 @@ export function searchItemsByString(
|
|||
* @param {Object} query Query for item totals count.
|
||||
* @return {string} Resource name for item totals.
|
||||
*/
|
||||
export function getTotalCountResourceName( itemType, query ) {
|
||||
export function getTotalCountResourceName( itemType: string, query: Query ) {
|
||||
// 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
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { _fields, page, per_page, ...totalsQuery } = query;
|
||||
|
||||
return getResourceName( 'total-' + itemType, totalsQuery );
|
||||
return getResourceName( 'total-' + itemType, { ...totalsQuery } );
|
||||
}
|
|
@ -4,18 +4,18 @@
|
|||
// [wp.data.getSelectors](https://github.com/WordPress/gutenberg/blob/319deee5f4d4838d6bc280e9e2be89c7f43f2509/packages/data/src/store/index.js#L16-L20)
|
||||
// [selector.js](https://github.com/WordPress/gutenberg/blob/trunk/packages/data/src/redux-store/metadata/selectors.js#L48-L52)
|
||||
export type WPDataSelectors = {
|
||||
getIsResolving: ( selector: string, args?: string[] ) => boolean;
|
||||
hasStartedResolution: ( selector: string, args?: string[] ) => boolean;
|
||||
hasFinishedResolution: ( selector: string, args?: string[] ) => boolean;
|
||||
isResolving: ( selector: string, args?: string[] ) => boolean;
|
||||
getIsResolving: ( selector: string, args?: unknown[] ) => boolean;
|
||||
hasStartedResolution: ( selector: string, args?: unknown[] ) => boolean;
|
||||
hasFinishedResolution: ( selector: string, args?: unknown[] ) => boolean;
|
||||
isResolving: ( selector: string, args?: unknown[] ) => boolean;
|
||||
getCachedResolvers: () => unknown;
|
||||
};
|
||||
|
||||
// [wp.data.getActions](https://github.com/WordPress/gutenberg/blob/319deee5f4d4838d6bc280e9e2be89c7f43f2509/packages/data/src/store/index.js#L31-L35)
|
||||
// [actions.js](https://github.com/WordPress/gutenberg/blob/aa2bed9010aa50467cb43063e370b70a91591e9b/packages/data/src/redux-store/metadata/actions.js)
|
||||
export type WPDataActions = {
|
||||
startResolution: ( selector: string, args?: string[] ) => void;
|
||||
finishResolution: ( selector: string, args?: string[] ) => void;
|
||||
startResolution: ( selector: string, args?: unknown[] ) => void;
|
||||
finishResolution: ( selector: string, args?: unknown[] ) => void;
|
||||
invalidateResolution: ( selector: string ) => void;
|
||||
invalidateResolutionForStore: ( selector: string ) => void;
|
||||
invalidateResolutionForStoreSelector: ( selector: string ) => void;
|
||||
|
|
|
@ -27,7 +27,7 @@ export const useCreateProductByType = () => {
|
|||
setIsRequesting( true );
|
||||
try {
|
||||
const data: {
|
||||
id?: string;
|
||||
id?: number;
|
||||
} = await createProductFromTemplate(
|
||||
{
|
||||
template_name: type,
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
Significance: patch
|
||||
Type: dev
|
||||
Comment: No logic or UI changes.
|
||||
|
||||
|
Loading…
Reference in New Issue