Add update product actions to product data store (#33282)

* Add update product actions to product data store

* Add changelog entry

* Use MutableProperties for update data

* Omit read only properties instead of picking mutable properties
This commit is contained in:
Joshua T Flowers 2022-06-10 12:24:27 -04:00 committed by GitHub
parent 935f915cb5
commit 9d98f1b1da
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 136 additions and 14 deletions

View File

@ -0,0 +1,4 @@
Significance: minor
Type: add
Add update product actions to product data store #33282

View File

@ -7,6 +7,8 @@ export enum TYPES {
GET_PRODUCTS_ERROR = 'GET_PRODUCTS_ERROR',
GET_PRODUCTS_TOTAL_COUNT_SUCCESS = 'GET_PRODUCTS_TOTAL_COUNT_SUCCESS',
GET_PRODUCTS_TOTAL_COUNT_ERROR = 'GET_PRODUCTS_TOTAL_COUNT_ERROR',
UPDATE_PRODUCT_ERROR = 'UPDATE_PRODUCT_ERROR',
UPDATE_PRODUCT_SUCCESS = 'UPDATE_PRODUCT_SUCCESS',
}
export default TYPES;

View File

@ -9,7 +9,7 @@ import { DispatchFromMap } from '@automattic/data-stores';
*/
import TYPES from './action-types';
import {
MutableProperties,
ReadOnlyProperties,
PartialProduct,
Product,
ProductQuery,
@ -35,7 +35,7 @@ export function getProductError(
};
}
function createProductSuccess( id: number, product: PartialProduct ) {
function createProductSuccess( id: number, product: Partial< Product > ) {
return {
type: TYPES.CREATE_PRODUCT_SUCCESS as const,
id,
@ -54,6 +54,22 @@ export function createProductError(
};
}
function updateProductSuccess( id: number, product: Partial< Product > ) {
return {
type: TYPES.UPDATE_PRODUCT_SUCCESS as const,
id,
product,
};
}
export function updateProductError( id: number, error: unknown ) {
return {
type: TYPES.UPDATE_PRODUCT_ERROR as const,
id,
error,
};
}
export function getProductsSuccess(
query: Partial< ProductQuery >,
products: PartialProduct[],
@ -100,7 +116,7 @@ export function getProductsTotalCountError(
};
}
export function* createProduct( data: Pick< Product, MutableProperties > ) {
export function* createProduct( data: Omit< Product, ReadOnlyProperties > ) {
try {
const product: Product = yield apiFetch( {
path: WC_PRODUCT_NAMESPACE,
@ -116,6 +132,25 @@ export function* createProduct( data: Pick< Product, MutableProperties > ) {
}
}
export function* updateProduct(
id: number,
data: Omit< Product, ReadOnlyProperties >
) {
try {
const product: Product = yield apiFetch( {
path: `${ WC_PRODUCT_NAMESPACE }/${ id }`,
method: 'PUT',
data,
} );
yield updateProductSuccess( product.id, product );
return product;
} catch ( error ) {
yield updateProductError( id, error );
throw error;
}
}
export type Actions = ReturnType<
| typeof createProductError
| typeof createProductSuccess
@ -125,8 +160,11 @@ export type Actions = ReturnType<
| typeof getProductsError
| typeof getProductsTotalCountSuccess
| typeof getProductsTotalCountError
| typeof updateProductError
| typeof updateProductSuccess
>;
export type ActionDispatchers = DispatchFromMap< {
createProduct: typeof createProduct;
updateProduct: typeof updateProduct;
} >;

View File

@ -39,6 +39,7 @@ const reducer: Reducer< ProductState, Actions > = (
switch ( payload.type ) {
case TYPES.CREATE_PRODUCT_SUCCESS:
case TYPES.GET_PRODUCT_SUCCESS:
case TYPES.UPDATE_PRODUCT_SUCCESS:
const productData = state.data || {};
return {
...state,
@ -99,6 +100,14 @@ const reducer: Reducer< ProductState, Actions > = (
) ]: payload.error,
},
};
case TYPES.UPDATE_PRODUCT_ERROR:
return {
...state,
errors: {
...state.errors,
[ `update/${ payload.id }` ]: payload.error,
},
};
default:
return state;
}

View File

@ -85,6 +85,15 @@ export const getCreateProductError = (
return state.errors[ resourceName ];
};
export const getUpdateProductError = (
state: ProductState,
id: number,
query: ProductQuery
) => {
const resourceName = getProductResourceName( query );
return state.errors[ `update/${ id }/${ resourceName }` ];
};
export type ProductsSelectors = {
getCreateProductError: WPDataSelector< typeof getCreateProductError >;
getProducts: WPDataSelector< typeof getProducts >;

View File

@ -210,4 +210,54 @@ describe( 'products reducer', () => {
expect( state.errors[ resourceName ] ).toBe( error );
} );
it( 'should handle UPDATE_PRODUCT_SUCCESS', () => {
const itemType = 'guyisms';
const initialState: ProductState = {
products: {
[ itemType ]: {
data: [ 1, 2 ],
},
},
productsCount: {
'total-guyisms:{}': 2,
},
errors: {},
data: {
1: { id: 1, name: 'Donkey', status: 'draft' },
2: { id: 2, name: 'Sauce', status: 'publish' },
},
};
const product: PartialProduct = {
id: 2,
name: 'Holy smokes!',
status: 'draft',
};
const state = reducer( initialState, {
type: TYPES.UPDATE_PRODUCT_SUCCESS,
id: product.id,
product,
} );
expect( state.products ).toEqual( initialState.products );
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 ].name ).toEqual( product.name );
} );
it( 'should handle UPDATE_PRODUCT_ERROR', () => {
const id = 1;
const error = 'Baaam!';
const state = reducer( defaultState, {
type: TYPES.UPDATE_PRODUCT_ERROR,
id,
error,
} );
expect( state.errors[ `update/${ id }` ] ).toBe( error );
} );
} );

View File

@ -40,17 +40,27 @@ export type Product<
sale_price: string;
};
export type MutableProperties =
| 'name'
| 'slug'
| 'type'
| 'status'
| 'featured'
| 'description'
| 'short_description'
| 'sku'
| 'regular_price'
| 'sale_price';
export type ReadOnlyProperties =
| 'id'
| 'permalink'
| 'date_created'
| 'date_created_gmt'
| 'date_modified'
| 'date_modified_gmt'
| 'price'
| 'price_html'
| 'on_sale'
| 'purchasable'
| 'total_sales'
| 'backorders_allowed'
| 'backordered'
| 'shipping_required'
| 'shipping_taxable'
| 'shipping_class_id'
| 'average_rating'
| 'rating_count'
| 'related_ids'
| 'variations';
export type PartialProduct = Partial< Product > & Pick< Product, 'id' >;