Skip to content

Commit

Permalink
feat: introduce price update strategies (#1089)
Browse files Browse the repository at this point in the history
  • Loading branch information
dhhyi authored May 3, 2022
1 parent 78ef2e1 commit c980cbe
Show file tree
Hide file tree
Showing 8 changed files with 50 additions and 77 deletions.
8 changes: 8 additions & 0 deletions src/app/core/configurations/injection-keys.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { InjectionToken } from '@angular/core';

import { CookieConsentOptions } from 'ish-core/models/cookies/cookies.model';
import { PriceUpdateType } from 'ish-core/models/price/price.model';
import { ViewType } from 'ish-core/models/viewtype/viewtype.types';
import { DataRetentionPolicy } from 'ish-core/utils/meta-reducers';

Expand Down Expand Up @@ -58,6 +59,13 @@ export const DATA_RETENTION_POLICY = new InjectionToken<DataRetentionPolicy>('da
factory: () => environment.dataRetention,
});

/**
* the configured price update policy for the application
*/
export const PRICE_UPDATE = new InjectionToken<PriceUpdateType>('priceUpdate', {
factory: () => environment.priceUpdate,
});

/**
* the configured theme color
*/
Expand Down
15 changes: 13 additions & 2 deletions src/app/core/facades/product-context.facade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -402,9 +402,20 @@ export class ProductContextFacade extends RxState<ProductContext> implements OnD
case 'prices':
wrap(
'prices',
combineLatest([this.select('displayProperties', 'price'), this.select('product', 'sku')]).pipe(
combineLatest([
this.select('displayProperties', 'price'),
this.select('product').pipe(
filter(p => !!p && !p.failed),
mapToProperty('sku'),
distinctUntilChanged()
),
this.select('requiredCompletenessLevel').pipe(
map(completeness => completeness === true),
distinctUntilChanged()
),
]).pipe(
filter(([visible]) => !!visible),
switchMap(([, ids]) => this.shoppingFacade.productPrices$(ids))
switchMap(([, sku, fresh]) => this.shoppingFacade.productPrices$(sku, fresh))
)
);
break;
Expand Down
24 changes: 19 additions & 5 deletions src/app/core/facades/shopping.facade.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { Injectable } from '@angular/core';
import { Inject, Injectable } from '@angular/core';
import { Store, select } from '@ngrx/store';
import { Observable, combineLatest } from 'rxjs';
import { Observable, combineLatest, identity } from 'rxjs';
import { debounce, filter, map, pairwise, startWith, switchMap, tap } from 'rxjs/operators';

import { PRICE_UPDATE } from 'ish-core/configurations/injection-keys';
import { PriceItemHelper } from 'ish-core/models/price-item/price-item.helper';
import { PriceUpdateType } from 'ish-core/models/price/price.model';
import { ProductListingID } from 'ish-core/models/product-listing/product-listing.model';
import { ProductCompletenessLevel, ProductHelper } from 'ish-core/models/product/product.model';
import { selectRouteParam } from 'ish-core/store/core/router';
Expand All @@ -24,6 +26,7 @@ import {
getProductListingViewType,
loadMoreProducts,
} from 'ish-core/store/shopping/product-listing';
import { loadProductPrices } from 'ish-core/store/shopping/product-prices';
import { getProductPrice } from 'ish-core/store/shopping/product-prices/product-prices.selectors';
import {
getProduct,
Expand All @@ -43,7 +46,7 @@ import { whenFalsy, whenTruthy } from 'ish-core/utils/operators';
/* eslint-disable @typescript-eslint/member-ordering */
@Injectable({ providedIn: 'root' })
export class ShoppingFacade {
constructor(private store: Store) {}
constructor(private store: Store, @Inject(PRICE_UPDATE) private priceUpdate: PriceUpdateType) {}

// CATEGORY

Expand Down Expand Up @@ -105,11 +108,22 @@ export class ShoppingFacade {
);
}

productPrices$(sku: string | Observable<string>) {
productPrices$(sku: string | Observable<string>, fresh = false) {
return toObservable(sku).pipe(
whenTruthy(),
switchMap(plainSKU =>
combineLatest([
this.store.pipe(select(getProductPrice(plainSKU))),
this.store.pipe(
select(getProductPrice(plainSKU)),
// reset state when updates are forced
this.priceUpdate === 'always' || fresh ? startWith(undefined) : identity,
tap(prices => {
if (!prices) {
this.store.dispatch(loadProductPrices({ skus: [plainSKU] }));
}
}),
whenTruthy()
),
this.store.pipe(select(getPriceDisplayType)),
]).pipe(map(args => PriceItemHelper.selectPricing(...args)))
)
Expand Down
2 changes: 2 additions & 0 deletions src/app/core/models/price/price.model.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export type PriceUpdateType = 'stable' | 'always';

export interface Price {
type: 'Money';
value: number;
Expand Down
13 changes: 0 additions & 13 deletions src/app/core/store/shopping/products/products.effects.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import { loadCategory } from 'ish-core/store/shopping/categories';
import { loadProductsForFilter } from 'ish-core/store/shopping/filter';
import { setProductListingPageSize } from 'ish-core/store/shopping/product-listing';
import { loadProductPricesSuccess } from 'ish-core/store/shopping/product-prices';
import { loadProductPrices } from 'ish-core/store/shopping/product-prices/product-prices.actions';
import { ShoppingStoreModule } from 'ish-core/store/shopping/shopping-store.module';
import { makeHttpError } from 'ish-core/utils/dev/api-service-utils';
import { HttpStatusCodeService } from 'ish-core/utils/http-status-code/http-status-code.service';
Expand Down Expand Up @@ -199,18 +198,6 @@ describe('Products Effects', () => {
}));
});

describe('loadProductPricesAfterProductSuccess$', () => {
it('should trigger action to load product prices after successful load product action', () => {
const sku = 'sku123';
const action = loadProductSuccess({ product: { sku } as Product });
const completion = loadProductPrices({ skus: [sku] });
actions$ = hot('-a-a-a', { a: action });
const expected$ = cold('-c-c-c', { c: completion });

expect(effects.loadProductPricesAfterProductSuccess$).toBeObservable(expected$);
});
});

describe('loadProductsForCategory$', () => {
it('should call service for SKU list', done => {
actions$ = of(loadProductsForCategory({ categoryId: '123', sorting: 'name-asc' }));
Expand Down
11 changes: 0 additions & 11 deletions src/app/core/store/shopping/products/products.effects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ import { personalizationStatusDetermined } from 'ish-core/store/customer/user';
import { loadCategory } from 'ish-core/store/shopping/categories';
import { loadProductsForFilter } from 'ish-core/store/shopping/filter';
import { getProductListingItemsPerPage, setProductListingPages } from 'ish-core/store/shopping/product-listing';
import { loadProductPrices } from 'ish-core/store/shopping/product-prices';
import { HttpStatusCodeService } from 'ish-core/utils/http-status-code/http-status-code.service';
import {
delayUntil,
Expand Down Expand Up @@ -110,16 +109,6 @@ export class ProductsEffects {
)
);

loadProductPricesAfterProductSuccess$ = createEffect(() =>
this.actions$.pipe(
ofType(loadProductSuccess),
mapToPayloadProperty('product'),
mapToProperty('sku'),
whenTruthy(),
map(sku => loadProductPrices({ skus: [sku] }))
)
);

/**
* retrieve products for category incremental respecting paging
*/
Expand Down
46 changes: 0 additions & 46 deletions src/app/core/store/shopping/shopping-store.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -316,10 +316,6 @@ describe('Shopping Store', () => {
sortableAttributes: []
[Filter API] Load Filter Success:
filterNavigation: {}
[Product Price Internal] Load Product Prices:
skus: ["P2"]
[Products API] Load Product Prices Success:
prices: []
`);
}));

Expand All @@ -338,11 +334,7 @@ describe('Shopping Store', () => {
sku: "P2"
[Products API] Load Product Success:
product: {"sku":"P2","name":"nP2"}
[Product Price Internal] Load Product Prices:
skus: ["P2"]
@ngrx/router-store/navigated: /product/P2
[Products API] Load Product Prices Success:
prices: []
`);
}));
});
Expand Down Expand Up @@ -467,12 +459,6 @@ describe('Shopping Store', () => {
sortableAttributes: []
[Filter API] Load Filter Success:
filterNavigation: {}
[Product Price Internal] Load Product Prices:
skus: ["P1"]
[Product Price Internal] Load Product Prices:
skus: ["P2"]
[Products API] Load Product Prices Success:
prices: []
`);
}));

Expand All @@ -491,11 +477,7 @@ describe('Shopping Store', () => {
sku: "P1"
[Products API] Load Product Success:
product: {"sku":"P1","name":"nP1"}
[Product Price Internal] Load Product Prices:
skus: ["P1"]
@ngrx/router-store/navigated: /category/A.123.456/product/P1
[Products API] Load Product Prices Success:
prices: []
`);
}));

Expand Down Expand Up @@ -562,10 +544,6 @@ describe('Shopping Store', () => {
sortableAttributes: []
[Filter API] Load Filter Success:
filterNavigation: {}
[Product Price Internal] Load Product Prices:
skus: ["P2"]
[Products API] Load Product Prices Success:
prices: []
`);
}));

Expand Down Expand Up @@ -606,17 +584,11 @@ describe('Shopping Store', () => {
sortableAttributes: []
[Filter API] Load Filter Success:
filterNavigation: {}
[Product Price Internal] Load Product Prices:
skus: ["P1"]
[Product Price Internal] Load Product Prices:
skus: ["P2"]
@ngrx/router-store/navigated: /category/A.123.456
[Product Listing] Load More Products:
id: {"type":"category","value":"A.123.456"}
[Viewconf Internal] Set Breadcrumb Data:
breadcrumbData: [{"text":"nA","link":"/nA-catA"},{"text":"nA123","link":"/nA...
[Products API] Load Product Prices Success:
prices: []
`);
}));
});
Expand Down Expand Up @@ -685,11 +657,7 @@ describe('Shopping Store', () => {
sku: "P1"
[Products API] Load Product Success:
product: {"sku":"P1","name":"nP1"}
[Product Price Internal] Load Product Prices:
skus: ["P1"]
@ngrx/router-store/navigated: /category/A.123.456/product/P1
[Products API] Load Product Prices Success:
prices: []
`);
}));

Expand Down Expand Up @@ -741,17 +709,11 @@ describe('Shopping Store', () => {
sortableAttributes: []
[Filter API] Load Filter Success:
filterNavigation: {}
[Product Price Internal] Load Product Prices:
skus: ["P1"]
[Product Price Internal] Load Product Prices:
skus: ["P2"]
@ngrx/router-store/navigated: /category/A.123.456
[Product Listing] Load More Products:
id: {"type":"category","value":"A.123.456"}
[Viewconf Internal] Set Breadcrumb Data:
breadcrumbData: [{"text":"nA","link":"/nA-catA"},{"text":"nA123","link":"/nA...
[Products API] Load Product Prices Success:
prices: []
`);
}));
});
Expand Down Expand Up @@ -809,11 +771,7 @@ describe('Shopping Store', () => {
sku: "P1"
[Products API] Load Product Success:
product: {"sku":"P1","name":"nP1"}
[Product Price Internal] Load Product Prices:
skus: ["P1"]
@ngrx/router-store/navigated: /product/P1
[Products API] Load Product Prices Success:
prices: []
`);
}));

Expand Down Expand Up @@ -961,10 +919,6 @@ describe('Shopping Store', () => {
sortableAttributes: []
[Filter API] Load Filter Success:
filterNavigation: {}
[Product Price Internal] Load Product Prices:
skus: ["P2"]
[Products API] Load Product Prices Success:
prices: []
`);
}));
});
Expand Down
8 changes: 8 additions & 0 deletions src/environments/environment.model.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Auth0Config } from 'ish-core/identity-provider/auth0.identity-provider';
import { CookieConsentOptions } from 'ish-core/models/cookies/cookies.model';
import { PriceUpdateType } from 'ish-core/models/price/price.model';
import { DeviceType, ViewType } from 'ish-core/models/viewtype/viewtype.types';
import { DataRetentionPolicy } from 'ish-core/utils/meta-reducers';
import { MultiSiteLocaleMap } from 'ish-core/utils/multi-site/multi-site.service';
Expand Down Expand Up @@ -113,6 +114,12 @@ export interface Environment {

// enable and configure data persistence for specific stores (compare, recently, tacton)
dataRetention: DataRetentionPolicy;

/** Price update mechanism:
* - 'always': fetch fresh price information all the time
* - 'stable': only fetch prices once per application lifetime
*/
priceUpdate: PriceUpdateType;
}

export const ENVIRONMENT_DEFAULTS: Omit<Environment, 'icmChannel'> = {
Expand Down Expand Up @@ -171,4 +178,5 @@ export const ENVIRONMENT_DEFAULTS: Omit<Environment, 'icmChannel'> = {
recently: 60 * 24 * 7, // 1 week
tacton: 'forever',
},
priceUpdate: 'always',
};

0 comments on commit c980cbe

Please sign in to comment.