Migrate `@woocommerce/data` item store to TS (#33482)

Migrate item store to TS
This commit is contained in:
Chi-Hsuan Huang 2022-06-21 16:46:30 +08:00 committed by GitHub
parent 7068c78101
commit e412d5912b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 583 additions and 209 deletions

View File

@ -0,0 +1,4 @@
Significance: patch
Type: dev
Migrate item store to TS

View File

@ -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;

View File

@ -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
>;

View File

@ -1 +1 @@
export const STORE_NAME = 'wc/admin/items';
export const STORE_NAME = 'wc/admin/items' as const;

View File

@ -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;

View File

@ -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;
}

View File

@ -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;

View File

@ -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;

View File

@ -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 );
}

View File

@ -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 ];
};

View File

@ -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 ];
};

View File

@ -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, {

View File

@ -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 >;

View File

@ -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 } );
}

View File

@ -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;

View File

@ -27,7 +27,7 @@ export const useCreateProductByType = () => {
setIsRequesting( true );
try {
const data: {
id?: string;
id?: number;
} = await createProductFromTemplate(
{
template_name: type,

View File

@ -0,0 +1,5 @@
Significance: patch
Type: dev
Comment: No logic or UI changes.