Skip to content

Commit

Permalink
refactor: use the ICM channel preference to configure the advancedVar…
Browse files Browse the repository at this point in the history
…iationHandling instead of the feature toggle of the same name
  • Loading branch information
SGrueber committed Jan 25, 2022
1 parent 9946ca2 commit 0500882
Show file tree
Hide file tree
Showing 22 changed files with 303 additions and 190 deletions.
1 change: 0 additions & 1 deletion docs/concepts/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,6 @@ Of course, the ICM server must supply appropriate REST resources to leverage fun
| rating | display product ratings |
| recently | display recently viewed products (additional configuration via `dataRetention` configuration options) |
| **B2B Features** | |
| advancedVariationHandling | handle product variations as individual products in listings and product detail pages |
| businessCustomerRegistration | create business customers on registration |
| costCenters | cost center feature |
| orderTemplates | order template feature |
Expand Down
4 changes: 4 additions & 0 deletions docs/guides/migrations.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ Now we removed the obsolete form components.
If you want to use the obsolete form components in your project nevertheless, skip the commit `remove obsolete form components`.
For more information concerning Formly please refer to our [Formly - Guide](./formly.md)).

The feature toggle 'advancedVariationHandling' has been removed.
Instead the ICM channel preference 'AdvancedVariationHandling' is used to configure it.
You will find this preference as 'List View' in the ICM backoffice under Channel Preferences -> Product Variations.

## 1.1 to 1.2

The `dist` folder now only contains results of the build process (except for `healthcheck.js`).
Expand Down
4 changes: 2 additions & 2 deletions docs/guides/multi-site-configurations.md
Original file line number Diff line number Diff line change
Expand Up @@ -184,14 +184,14 @@ To see what is possible through multi-site handling, have a look at this extende
- baseHref: /us
lang: en_US
channel: inSPIRED-inTRONICS_Business-Site
features: quoting,businessCustomerRegistration,advancedVariationHandling
features: quoting,businessCustomerRegistration
.+\.de:
channel: inSPIRED-inTRONICS-Site
lang: de_DE
theme: b2c
.+\.com:
channel: inSPIRED-inTRONICS_Business-Site
features: quoting,businessCustomerRegistration,advancedVariationHandling
features: quoting,businessCustomerRegistration
.+\.fr:
channel: inSPIRED-inTRONICS_Business-Site
lang: fr_FR
Expand Down
2 changes: 1 addition & 1 deletion nginx/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Please refer to the [documentation](../docs/guides/nginx-startup.md) for configu
theme: 'b2c'
.+\.com:
channel: inSPIRED-inTRONICS_Business-Site
features: quoting,businessCustomerRegistration,advancedVariationHandling
features: quoting,businessCustomerRegistration
.+\.fr:
channel: inSPIRED-inTRONICS-Site
lang: fr_FR
Expand Down
12 changes: 7 additions & 5 deletions src/app/core/facades/selected-product-context.facade.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { Injectable, Injector } from '@angular/core';
import { Router } from '@angular/router';
import { TranslateService } from '@ngx-translate/core';
import { filter, map, skip, withLatestFrom } from 'rxjs/operators';
import { concatMap, filter, map, skip, withLatestFrom } from 'rxjs/operators';

import { ProductVariationHelper } from 'ish-core/models/product-variation/product-variation.helper';
import { FeatureToggleService } from 'ish-core/utils/feature-toggle/feature-toggle.service';

import { AppFacade } from './app.facade';
import { ProductContextFacade } from './product-context.facade';
Expand All @@ -16,7 +15,6 @@ export class SelectedProductContextFacade extends ProductContextFacade {
shoppingFacade: ShoppingFacade,
translate: TranslateService,
injector: Injector,
private featureToggleService: FeatureToggleService,
private router: Router,
private appFacade: AppFacade
) {
Expand All @@ -28,9 +26,13 @@ export class SelectedProductContextFacade extends ProductContextFacade {
this.connect(
'sku',
this.select('product').pipe(
filter(() => !this.featureToggleService.enabled('advancedVariationHandling')),
filter(ProductVariationHelper.hasDefaultVariation),
map(p => p.defaultVariationSKU)
concatMap(p =>
this.appFacade.serverSetting$<boolean>('preferences.ChannelPreferences.EnableAdvancedVariationHandling').pipe(
filter(advancedVariationHandling => advancedVariationHandling !== undefined && !advancedVariationHandling),
map(() => p.defaultVariationSKU)
)
)
)
);

Expand Down
13 changes: 11 additions & 2 deletions src/app/core/services/filter/filter.service.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { TestBed } from '@angular/core/testing';
import { of } from 'rxjs';
import { anything, capture, instance, mock, verify, when } from 'ts-mockito';
import { anyString, anything, capture, instance, mock, verify, when } from 'ts-mockito';

import { AppFacade } from 'ish-core/facades/app.facade';
import { FilterNavigationData } from 'ish-core/models/filter-navigation/filter-navigation.interface';
import { ApiService, AvailableOptions } from 'ish-core/services/api/api.service';
import { CoreStoreModule } from 'ish-core/store/core/core-store.module';
Expand All @@ -12,6 +13,8 @@ import { FilterService } from './filter.service';
describe('Filter Service', () => {
let apiService: ApiService;
let filterService: FilterService;
let appFacadeMock: AppFacade;

const productsMock = {
elements: [
{ uri: 'products/123', attributes: [{ name: 'sku', value: '123' }] },
Expand All @@ -36,12 +39,18 @@ describe('Filter Service', () => {

beforeEach(() => {
apiService = mock(ApiService);
appFacadeMock = mock(AppFacade);

TestBed.configureTestingModule({
imports: [CoreStoreModule.forTesting(['configuration'])],
providers: [{ provide: ApiService, useFactory: () => instance(apiService) }],
providers: [
{ provide: ApiService, useFactory: () => instance(apiService) },
{ provide: AppFacade, useFactory: () => instance(appFacadeMock) },
],
});
filterService = TestBed.inject(FilterService);

when(appFacadeMock.serverSetting$(anyString())).thenReturn(of(false));
});

it('should be created', () => {
Expand Down
27 changes: 14 additions & 13 deletions src/app/core/services/filter/filter.service.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { HttpParams } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable, identity } from 'rxjs';
import { map } from 'rxjs/operators';
import { Observable } from 'rxjs';
import { map, withLatestFrom } from 'rxjs/operators';

import { AppFacade } from 'ish-core/facades/app.facade';
import { AttributeGroupTypes } from 'ish-core/models/attribute-group/attribute-group.types';
import { CategoryHelper } from 'ish-core/models/category/category.model';
import { FilterNavigationData } from 'ish-core/models/filter-navigation/filter-navigation.interface';
Expand All @@ -14,7 +15,6 @@ import { ProductMapper } from 'ish-core/models/product/product.mapper';
import { Product, ProductHelper } from 'ish-core/models/product/product.model';
import { ApiService } from 'ish-core/services/api/api.service';
import { ProductsService } from 'ish-core/services/products/products.service';
import { FeatureToggleService } from 'ish-core/utils/feature-toggle/feature-toggle.service';
import { omit } from 'ish-core/utils/functions';
import { URLFormParams, appendFormParamsToHttpParams } from 'ish-core/utils/url-form-params';

Expand All @@ -24,7 +24,7 @@ export class FilterService {
private apiService: ApiService,
private filterNavigationMapper: FilterNavigationMapper,
private productMapper: ProductMapper,
private featureToggleService: FeatureToggleService
private appFacade: AppFacade
) {}

getFilterForCategory(categoryUniqueId: string): Observable<FilterNavigation> {
Expand Down Expand Up @@ -91,22 +91,23 @@ export class FilterService {
total: x.total,
sortableAttributes: Object.values(x.sortableAttributes || {}),
})),
params.has('MasterSKU')
? identity
: map(({ products, sortableAttributes, total }) => ({
products: this.postProcessMasters(products),
sortableAttributes,
total,
}))
withLatestFrom(
this.appFacade.serverSetting$<boolean>('preferences.ChannelPreferences.EnableAdvancedVariationHandling')
),
map(([{ products, sortableAttributes, total }, advancedVariationHandling]) => ({
products: params.has('MasterSKU') ? products : this.postProcessMasters(products, advancedVariationHandling),
sortableAttributes,
total,
}))
);
}

/**
* exchange single-return variation products to master products for B2B
* TODO: this is a work-around
*/
private postProcessMasters(products: Partial<Product>[]): Product[] {
if (this.featureToggleService.enabled('advancedVariationHandling')) {
private postProcessMasters(products: Partial<Product>[], advancedVariationHandling: boolean): Product[] {
if (advancedVariationHandling) {
return products.map(p =>
ProductHelper.isVariationProduct(p) ? { sku: p.productMasterSKU, completenessLevel: 0 } : p
) as Product[];
Expand Down
13 changes: 11 additions & 2 deletions src/app/core/services/products/products.service.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { TestBed } from '@angular/core/testing';
import { of } from 'rxjs';
import { anything, capture, instance, mock, verify, when } from 'ts-mockito';
import { anyString, anything, capture, instance, mock, verify, when } from 'ts-mockito';

import { AppFacade } from 'ish-core/facades/app.facade';
import { Product } from 'ish-core/models/product/product.model';
import { ApiService, AvailableOptions } from 'ish-core/services/api/api.service';
import { CoreStoreModule } from 'ish-core/store/core/core-store.module';
Expand All @@ -13,6 +14,7 @@ import { ProductsService } from './products.service';
describe('Products Service', () => {
let productsService: ProductsService;
let apiServiceMock: ApiService;
let appFacadeMock: AppFacade;

const productSku = 'SKU';
const categoryId = 'CategoryID';
Expand Down Expand Up @@ -61,14 +63,21 @@ describe('Products Service', () => {

beforeEach(() => {
apiServiceMock = mock(ApiService);
appFacadeMock = mock(AppFacade);

TestBed.configureTestingModule({
imports: [
CoreStoreModule.forTesting(['configuration'], [ProductListingEffects]),
ShoppingStoreModule.forTesting('productListing'),
],
providers: [{ provide: ApiService, useFactory: () => instance(apiServiceMock) }],
providers: [
{ provide: ApiService, useFactory: () => instance(apiServiceMock) },
{ provide: AppFacade, useFactory: () => instance(appFacadeMock) },
],
});
productsService = TestBed.inject(ProductsService);

when(appFacadeMock.serverSetting$(anyString())).thenReturn(of(false));
});

it("should get Product data when 'getProduct' is called", done => {
Expand Down
28 changes: 15 additions & 13 deletions src/app/core/services/products/products.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ import { HttpParams } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { flatten, range } from 'lodash-es';
import { Observable, from, identity, of, throwError } from 'rxjs';
import { defaultIfEmpty, map, mergeMap, switchMap, toArray } from 'rxjs/operators';
import { defaultIfEmpty, map, mergeMap, switchMap, toArray, withLatestFrom } from 'rxjs/operators';

import { AppFacade } from 'ish-core/facades/app.facade';
import { AttributeGroupTypes } from 'ish-core/models/attribute-group/attribute-group.types';
import { CategoryHelper } from 'ish-core/models/category/category.model';
import { Link } from 'ish-core/models/link/link.model';
Expand All @@ -19,7 +20,6 @@ import {
VariationProductMaster,
} from 'ish-core/models/product/product.model';
import { ApiService, unpackEnvelope } from 'ish-core/services/api/api.service';
import { FeatureToggleService } from 'ish-core/utils/feature-toggle/feature-toggle.service';
import { mapToProperty } from 'ish-core/utils/operators';

/**
Expand All @@ -30,11 +30,7 @@ export class ProductsService {
static STUB_ATTRS =
'sku,salePrice,listPrice,availability,manufacturer,image,minOrderQuantity,maxOrderQuantity,stepOrderQuantity,inStock,promotions,packingUnit,mastered,productMaster,productMasterSKU,roundedAverageRating,retailSet';

constructor(
private apiService: ApiService,
private productMapper: ProductMapper,
private featureToggleService: FeatureToggleService
) {}
constructor(private apiService: ApiService, private productMapper: ProductMapper, private appFacade: AppFacade) {}

/**
* Get the full Product data for the given Product SKU.
Expand Down Expand Up @@ -96,8 +92,11 @@ export class ProductsService {
sortableAttributes: Object.values(response.sortableAttributes || {}),
total: response.total ? response.total : response.elements.length,
})),
map(({ products, sortableAttributes, total }) => ({
products: this.postProcessMasters(products),
withLatestFrom(
this.appFacade.serverSetting$<boolean>('preferences.ChannelPreferences.EnableAdvancedVariationHandling')
),
map(([{ products, sortableAttributes, total }, advancedVariationHandling]) => ({
products: this.postProcessMasters(products, advancedVariationHandling),
sortableAttributes,
total,
}))
Expand Down Expand Up @@ -146,8 +145,11 @@ export class ProductsService {
sortableAttributes: Object.values(response.sortableAttributes || {}),
total: response.total ? response.total : response.elements.length,
})),
map(({ products, sortableAttributes, total }) => ({
products: this.postProcessMasters(products),
withLatestFrom(
this.appFacade.serverSetting$<boolean>('preferences.ChannelPreferences.EnableAdvancedVariationHandling')
),
map(([{ products, sortableAttributes, total }, advancedVariationHandling]) => ({
products: this.postProcessMasters(products, advancedVariationHandling),
sortableAttributes,
total,
}))
Expand Down Expand Up @@ -194,8 +196,8 @@ export class ProductsService {
* exchange single-return variation products to master products for B2B
* TODO: this is a work-around
*/
private postProcessMasters(products: Partial<Product>[]): Product[] {
if (this.featureToggleService.enabled('advancedVariationHandling')) {
private postProcessMasters(products: Partial<Product>[], advancedVariationHandling: boolean): Product[] {
if (advancedVariationHandling) {
return products.map(p =>
ProductHelper.isVariationProduct(p) ? { sku: p.productMasterSKU, completenessLevel: 0 } : p
) as Product[];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { getCoreState } from 'ish-core/store/core/core-store';

const getServerConfigState = createSelector(getCoreState, state => state.serverConfig);

const getServerConfig = createSelector(getServerConfigState, state => state._config);
export const getServerConfig = createSelector(getServerConfigState, state => state._config);

export const isServerConfigurationLoaded = createSelector(getServerConfig, serverConfig => !!serverConfig);

Expand Down
6 changes: 6 additions & 0 deletions src/app/core/store/shopping/recently/recently.effects.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { cold } from 'jest-marbles';

import { FeatureToggleModule } from 'ish-core/feature-toggle.module';
import { ProductView } from 'ish-core/models/product-view/product-view.model';
import { getServerConfig } from 'ish-core/store/core/server-config';
import { getSelectedProduct } from 'ish-core/store/shopping/products';

import { addToRecently } from './recently.actions';
Expand All @@ -21,6 +22,11 @@ describe('Recently Effects', () => {

effects = TestBed.inject(RecentlyEffects);
store$ = TestBed.inject(MockStore);

// workaround: overrideSelector is not working for selectors with parameters https://github.com/ngrx/platform/issues/2717
store$.overrideSelector(getServerConfig, {
_config: { preferences: { ChannelPreferences: { EnableAdvancedVariationHandling: true } } },
});
});

describe('viewedProduct$', () => {
Expand Down
16 changes: 10 additions & 6 deletions src/app/core/store/shopping/recently/recently.effects.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,34 @@
import { Injectable } from '@angular/core';
import { createEffect } from '@ngrx/effects';
import { Store, select } from '@ngrx/store';
import { distinctUntilKeyChanged, filter, map } from 'rxjs/operators';
import { distinctUntilKeyChanged, filter, map, withLatestFrom } from 'rxjs/operators';

import { ProductHelper } from 'ish-core/models/product/product.model';
import { getServerConfigParameter } from 'ish-core/store/core/server-config';
import { getSelectedProduct } from 'ish-core/store/shopping/products/products.selectors';
import { FeatureToggleService } from 'ish-core/utils/feature-toggle/feature-toggle.service';
import { whenTruthy } from 'ish-core/utils/operators';

import { addToRecently } from './recently.actions';

@Injectable()
export class RecentlyEffects {
constructor(private store: Store, private featureToggleService: FeatureToggleService) {}
constructor(private store: Store) {}

viewedProduct$ = createEffect(() =>
this.store.pipe(
select(getSelectedProduct),
whenTruthy(),
filter(p => !ProductHelper.isFailedLoading(p)),
distinctUntilKeyChanged('sku'),
withLatestFrom(
this.store.pipe(
select(getServerConfigParameter<boolean>('preferences.ChannelPreferences.EnableAdvancedVariationHandling'))
)
),
filter(
product =>
this.featureToggleService.enabled('advancedVariationHandling') || !ProductHelper.isMasterProduct(product)
([product, advancedVariationHandling]) => advancedVariationHandling || !ProductHelper.isMasterProduct(product)
),
map(product => ({
map(([product]) => ({
sku: product.sku,
group: (ProductHelper.isVariationProduct(product) && product.productMasterSKU) || undefined,
})),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ describe('Recently Selectors', () => {
TestBed.configureTestingModule({
declarations: [DummyComponent],
imports: [
CoreStoreModule.forTesting(['router', 'configuration'], [RecentlyEffects]),
CoreStoreModule.forTesting(['router', 'configuration', 'serverConfig'], [RecentlyEffects]),
RouterTestingModule.withRoutes([{ path: 'product/:sku', component: DummyComponent }]),
ShoppingStoreModule.forTesting('_recently', 'categories', 'products'),
],
Expand Down
4 changes: 2 additions & 2 deletions src/app/core/store/shopping/shopping-store.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ describe('Shopping Store', () => {
});

const configurationServiceMock = mock(ConfigurationService);
when(configurationServiceMock.getServerConfiguration()).thenReturn(of({}));
when(configurationServiceMock.getServerConfiguration()).thenReturn(EMPTY);

const countryServiceMock = mock(CountryService);
when(countryServiceMock.getCountries()).thenReturn(EMPTY);
Expand Down Expand Up @@ -143,7 +143,7 @@ describe('Shopping Store', () => {
TestBed.configureTestingModule({
declarations: [DummyComponent],
imports: [
CoreStoreModule.forTesting(['router', 'configuration'], true),
CoreStoreModule.forTesting(['router', 'configuration', 'serverConfig'], true),
RouterTestingModule.withRoutes([
{
path: 'home',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<div *ngIf="visible$ | async" class="product-variation-container">
<ng-container *ishFeature="'advancedVariationHandling'; else variationSelect">
<ng-container
*ngIf="'preferences.ChannelPreferences.EnableAdvancedVariationHandling' | ishServerSetting; else variationSelect"
>
<ish-product-variation-display></ish-product-variation-display>
<ish-product-master-link></ish-product-master-link>
</ng-container>
Expand Down
Loading

1 comment on commit 0500882

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Azure Demo Servers are available:

Please sign in to comment.