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:
parent
935f915cb5
commit
9d98f1b1da
|
@ -0,0 +1,4 @@
|
||||||
|
Significance: minor
|
||||||
|
Type: add
|
||||||
|
|
||||||
|
Add update product actions to product data store #33282
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
} >;
|
} >;
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 >;
|
||||||
|
|
|
@ -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 );
|
||||||
|
} );
|
||||||
} );
|
} );
|
||||||
|
|
|
@ -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' >;
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue