From 9d98f1b1dac56cee53106dd3f6749fe960f99f47 Mon Sep 17 00:00:00 2001 From: Joshua T Flowers Date: Fri, 10 Jun 2022 12:24:27 -0400 Subject: [PATCH] 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 --- packages/js/data/changelog/add-33076-2 | 4 ++ packages/js/data/src/products/action-types.ts | 2 + packages/js/data/src/products/actions.ts | 44 ++++++++++++++-- packages/js/data/src/products/reducer.ts | 9 ++++ packages/js/data/src/products/selectors.ts | 9 ++++ packages/js/data/src/products/test/reducer.ts | 50 +++++++++++++++++++ packages/js/data/src/products/types.ts | 32 ++++++++---- 7 files changed, 136 insertions(+), 14 deletions(-) create mode 100644 packages/js/data/changelog/add-33076-2 diff --git a/packages/js/data/changelog/add-33076-2 b/packages/js/data/changelog/add-33076-2 new file mode 100644 index 00000000000..78b87da7bae --- /dev/null +++ b/packages/js/data/changelog/add-33076-2 @@ -0,0 +1,4 @@ +Significance: minor +Type: add + +Add update product actions to product data store #33282 diff --git a/packages/js/data/src/products/action-types.ts b/packages/js/data/src/products/action-types.ts index b738cd22c07..6009ca95e47 100644 --- a/packages/js/data/src/products/action-types.ts +++ b/packages/js/data/src/products/action-types.ts @@ -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; diff --git a/packages/js/data/src/products/actions.ts b/packages/js/data/src/products/actions.ts index 0ce593a933e..14f30af3312 100644 --- a/packages/js/data/src/products/actions.ts +++ b/packages/js/data/src/products/actions.ts @@ -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; } >; diff --git a/packages/js/data/src/products/reducer.ts b/packages/js/data/src/products/reducer.ts index 867124d4e17..905d4b34fb8 100644 --- a/packages/js/data/src/products/reducer.ts +++ b/packages/js/data/src/products/reducer.ts @@ -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; } diff --git a/packages/js/data/src/products/selectors.ts b/packages/js/data/src/products/selectors.ts index 57bd22cb8c1..4be8a1e2374 100644 --- a/packages/js/data/src/products/selectors.ts +++ b/packages/js/data/src/products/selectors.ts @@ -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 >; diff --git a/packages/js/data/src/products/test/reducer.ts b/packages/js/data/src/products/test/reducer.ts index c34b2f61a68..926094a1191 100644 --- a/packages/js/data/src/products/test/reducer.ts +++ b/packages/js/data/src/products/test/reducer.ts @@ -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 ); + } ); } ); diff --git a/packages/js/data/src/products/types.ts b/packages/js/data/src/products/types.ts index 24c05e55c76..125bc130623 100644 --- a/packages/js/data/src/products/types.ts +++ b/packages/js/data/src/products/types.ts @@ -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' >;