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_ERROR = 'GET_PRODUCTS_ERROR',
GET_PRODUCTS_TOTAL_COUNT_SUCCESS = 'GET_PRODUCTS_TOTAL_COUNT_SUCCESS', GET_PRODUCTS_TOTAL_COUNT_SUCCESS = 'GET_PRODUCTS_TOTAL_COUNT_SUCCESS',
GET_PRODUCTS_TOTAL_COUNT_ERROR = 'GET_PRODUCTS_TOTAL_COUNT_ERROR', 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; export default TYPES;

View File

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

View File

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

View File

@ -85,6 +85,15 @@ export const getCreateProductError = (
return state.errors[ resourceName ]; 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 = { export type ProductsSelectors = {
getCreateProductError: WPDataSelector< typeof getCreateProductError >; getCreateProductError: WPDataSelector< typeof getCreateProductError >;
getProducts: WPDataSelector< typeof getProducts >; getProducts: WPDataSelector< typeof getProducts >;

View File

@ -210,4 +210,54 @@ describe( 'products reducer', () => {
expect( state.errors[ resourceName ] ).toBe( error ); 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; sale_price: string;
}; };
export type MutableProperties = export type ReadOnlyProperties =
| 'name' | 'id'
| 'slug' | 'permalink'
| 'type' | 'date_created'
| 'status' | 'date_created_gmt'
| 'featured' | 'date_modified'
| 'description' | 'date_modified_gmt'
| 'short_description' | 'price'
| 'sku' | 'price_html'
| 'regular_price' | 'on_sale'
| 'sale_price'; | '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' >; export type PartialProduct = Partial< Product > & Pick< Product, 'id' >;