Skip to content

Commit

Permalink
refactor: extend the ExternalDisplayPropertiesProvider to get not onl…
Browse files Browse the repository at this point in the history
…y the product from the product context but the prices too (#1657)

BREAKING CHANGES: The `ExternalDisplayPropertiesProvider` `setup` method was changed to no longer working with product data only but a combination of product and prices (see [Migrations / From 5.1 to 5.2](https://github.com/intershop/intershop-pwa/blob/develop/docs/guides/migrations.md#from-51-to-52) for more details).
  • Loading branch information
dhhyi authored and shauke committed May 16, 2024
1 parent 42864a7 commit 0e4f96e
Show file tree
Hide file tree
Showing 6 changed files with 56 additions and 25 deletions.
3 changes: 3 additions & 0 deletions docs/guides/migrations.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ Each integrated view context triggers a REST call that will potentially decrease
For that reason the examples were commented out in the source code and have to be activated in the project source code if needed.
See [CMS Integration - View Contexts](../concepts/cms-integration.md#view-contexts) for more information.

The `ExternalDisplayPropertiesProvider` `setup` notation was changed from providing only the `product` context to providing a combined `{ product, prices }` context object.
For that reason any custom `ContextDisplayPropertiesService` that implements the `ExternalDisplayPropertiesProvider` needs to be adapted accordingly (see the changes of #1657).

## From 5.0 to 5.1

The OrderListComponent is strictly presentational, components using it have to supply the data.
Expand Down
29 changes: 21 additions & 8 deletions src/app/core/facades/product-context.facade.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@ import { AttributeGroup } from 'ish-core/models/attribute-group/attribute-group.
import { AttributeGroupTypes } from 'ish-core/models/attribute-group/attribute-group.types';
import { CategoryView } from 'ish-core/models/category-view/category-view.model';
import { Category } from 'ish-core/models/category/category.model';
import { PriceHelper } from 'ish-core/models/price/price.helper';
import { ProductView } from 'ish-core/models/product-view/product-view.model';
import { ProductCompletenessLevel } from 'ish-core/models/product/product.model';

import { AppFacade } from './app.facade';
import {
EXTERNAL_DISPLAY_PROPERTY_PROVIDER,
ExternalDisplayPropertiesProvider,
ProductContext,
ProductContextDisplayProperties,
ProductContextFacade,
} from './product-context.facade';
Expand Down Expand Up @@ -325,6 +327,7 @@ describe('Product Context Facade', () => {

describe('display properties', () => {
it('should set correct display properties for product', () => {
context.set('prices', () => ({ salePrice: PriceHelper.empty() }));
expect(context.get('displayProperties')).toMatchInlineSnapshot(`
{
"addToBasket": true,
Expand All @@ -350,6 +353,7 @@ describe('Product Context Facade', () => {
});

it('should include external displayProperty overrides when calculating', () => {
context.set('prices', () => ({ salePrice: PriceHelper.empty() }));
context.config = {
readOnly: true,
name: false,
Expand Down Expand Up @@ -575,6 +579,7 @@ describe('Product Context Facade', () => {
{ sku: 'p2', quantity: 2 },
])
);
context.set('prices', () => ({ salePrice: PriceHelper.empty() }));
context.set('sku', () => '123');
});

Expand Down Expand Up @@ -638,6 +643,7 @@ describe('Product Context Facade', () => {
} as ProductView;
when(shoppingFacade.product$(anything(), anything())).thenReturn(of(product));

context.set('prices', () => ({ salePrice: PriceHelper.empty() }));
context.set('sku', () => '123');
});

Expand Down Expand Up @@ -720,10 +726,12 @@ describe('Product Context Facade', () => {
let someOther$: Subject<boolean>;

class ProviderA implements ExternalDisplayPropertiesProvider {
setup(product$: Observable<ProductView>): Observable<Partial<ProductContextDisplayProperties<false>>> {
return product$.pipe(
map(p =>
p?.sku === '456'
setup(
context$: Observable<Pick<ProductContext, 'product' | 'prices'>>
): Observable<Partial<ProductContextDisplayProperties<false>>> {
return context$.pipe(
map(({ product }) =>
product?.sku === '456'
? {
addToBasket: false,
addToCompare: false,
Expand All @@ -738,8 +746,10 @@ describe('Product Context Facade', () => {
}

class ProviderB implements ExternalDisplayPropertiesProvider {
setup(product$: Observable<ProductView>): Observable<Partial<ProductContextDisplayProperties<false>>> {
return product$.pipe(
setup(
context$: Observable<Pick<ProductContext, 'product' | 'prices'>>
): Observable<Partial<ProductContextDisplayProperties<false>>> {
return context$.pipe(
map(() => ({
shipment: false,
promotions: false,
Expand All @@ -749,8 +759,10 @@ describe('Product Context Facade', () => {
}

class ProviderC implements ExternalDisplayPropertiesProvider {
setup(product$: Observable<ProductView>): Observable<Partial<ProductContextDisplayProperties<false>>> {
return product$.pipe(
setup(
context$: Observable<Pick<ProductContext, 'product' | 'prices'>>
): Observable<Partial<ProductContextDisplayProperties<false>>> {
return context$.pipe(
switchMap(() => someOther$),
map(prop => (prop ? { price: false } : {}))
);
Expand Down Expand Up @@ -795,6 +807,7 @@ describe('Product Context Facade', () => {
});

it('should set correct display properties respecting overrides from providers for product 123', () => {
context.set('prices', () => ({ salePrice: PriceHelper.empty() }));
context.set('sku', () => '123');

expect(context.get('displayProperties')).toMatchInlineSnapshot(`
Expand Down
15 changes: 12 additions & 3 deletions src/app/core/facades/product-context.facade.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Injectable, InjectionToken, Injector, OnDestroy } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { RxState } from '@rx-angular/state';
import { isEqual } from 'lodash-es';
import { isEqual, pick } from 'lodash-es';
import { BehaviorSubject, Observable, combineLatest, race } from 'rxjs';
import {
debounceTime,
Expand Down Expand Up @@ -77,7 +77,9 @@ const defaultDisplayProperties: ProductContextDisplayProperties<true | undefined
};

export interface ExternalDisplayPropertiesProvider {
setup(product$: Observable<ProductView>): Observable<Partial<ProductContextDisplayProperties<false>>>;
setup(
context$: Observable<Pick<ProductContext, 'product' | 'prices'>>
): Observable<Partial<ProductContextDisplayProperties<false>>>;
}

export const EXTERNAL_DISPLAY_PROPERTY_PROVIDER = new InjectionToken<ExternalDisplayPropertiesProvider>(
Expand Down Expand Up @@ -348,7 +350,14 @@ export class ProductContextFacade extends RxState<ProductContext> implements OnD
const externalDisplayPropertyProviders = [
injector.get(ProductContextDisplayPropertiesService),
...injector.get<ExternalDisplayPropertiesProvider[]>(EXTERNAL_DISPLAY_PROPERTY_PROVIDER, []),
].map(edp => edp.setup(this.select('product')));
].map(edp =>
edp.setup(
this.select().pipe(
map(context => pick(context, 'product', 'prices')),
distinctUntilChanged(isEqual)
)
)
);

const internalDisplayProperty$ = combineLatest([this.select('product'), this.privateConfig$]).pipe(
map(([product, privateConfig]) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,19 @@ import { map } from 'rxjs/operators';

import {
ExternalDisplayPropertiesProvider,
ProductContext,
ProductContextDisplayProperties,
} from 'ish-core/facades/product-context.facade';
import { ProductView } from 'ish-core/models/product-view/product-view.model';
import { ProductHelper } from 'ish-core/models/product/product.helper';

@Injectable({ providedIn: 'root' })
export class ProductContextDisplayPropertiesService implements ExternalDisplayPropertiesProvider {
setup(product$: Observable<ProductView>): Observable<Partial<ProductContextDisplayProperties<false>>> {
return product$.pipe(
map(product => {
const canBeOrdered = !ProductHelper.isMasterProduct(product) && product.available;
setup(
context$: Observable<Pick<ProductContext, 'product' | 'prices'>>
): Observable<Partial<ProductContextDisplayProperties<false>>> {
return context$.pipe(
map(({ product }) => {
const canBeOrdered = !ProductHelper.isMasterProduct(product) && product?.available;

const canBeOrderedNotRetail = canBeOrdered && !ProductHelper.isRetailSet(product);

Expand All @@ -26,8 +28,8 @@ export class ProductContextDisplayPropertiesService implements ExternalDisplayPr
retailSetParts: ProductHelper.isRetailSet(product),
shipment:
canBeOrderedNotRetail &&
Number.isInteger(product.readyForShipmentMin) &&
Number.isInteger(product.readyForShipmentMax),
Number.isInteger(product?.readyForShipmentMin) &&
Number.isInteger(product?.readyForShipmentMax),
addToBasket: canBeOrdered,
addToWishlist: !ProductHelper.isMasterProduct(product),
addToOrderTemplate: canBeOrdered,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,19 @@ import { map, switchMap } from 'rxjs/operators';

import {
ExternalDisplayPropertiesProvider,
ProductContext,
ProductContextDisplayProperties,
} from 'ish-core/facades/product-context.facade';
import { ProductView } from 'ish-core/models/product-view/product-view.model';
import { RoleToggleService } from 'ish-core/role-toggle.module';

@Injectable({ providedIn: 'root' })
export class PunchoutProductContextDisplayPropertiesService implements ExternalDisplayPropertiesProvider {
constructor(private roleToggleService: RoleToggleService) {}

setup(product$: Observable<ProductView>): Observable<Partial<ProductContextDisplayProperties<false>>> {
return product$.pipe(
setup(
context$: Observable<Pick<ProductContext, 'product' | 'prices'>>
): Observable<Partial<ProductContextDisplayProperties<false>>> {
return context$.pipe(
switchMap(() => this.roleToggleService.hasRole(['APP_B2B_CXML_USER', 'APP_B2B_OCI_USER'])),
map(isPunchoutUser =>
isPunchoutUser
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,17 @@ import { map } from 'rxjs/operators';

import {
ExternalDisplayPropertiesProvider,
ProductContext,
ProductContextDisplayProperties,
} from 'ish-core/facades/product-context.facade';
import { ProductView } from 'ish-core/models/product-view/product-view.model';

@Injectable()
export class TactonProductContextDisplayPropertiesService implements ExternalDisplayPropertiesProvider {
setup(product$: Observable<ProductView>): Observable<Partial<ProductContextDisplayProperties<false>>> {
return product$.pipe(
map(product =>
setup(
context$: Observable<Pick<ProductContext, 'product' | 'prices'>>
): Observable<Partial<ProductContextDisplayProperties<false>>> {
return context$.pipe(
map(({ product }) =>
product?.type === 'TactonProduct'
? {
addToBasket: false,
Expand Down

0 comments on commit 0e4f96e

Please sign in to comment.