Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: use locales from ICM configuration #685

Merged
merged 6 commits into from
May 20, 2021
Merged
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/concepts/configuration.md
Original file line number Diff line number Diff line change
@@ -128,7 +128,7 @@ To access these properties we provide the [`StatePropertiesService`](../../src/a
ICM provides a Configurations REST resource - `/configurations` - that is supposed to provide all relevant runtime configurations that can be defined in the ICM back office and are required to configure a REST client as well.
This includes service configurations, locales, basket preferences, etc.

The ICM configurations information can be accessed through the [`getServerConfigParameter`](../../src/app/core/store/general/server-config/server-config.selectors.ts) selector.
The ICM configurations information can be accessed through the [`getServerConfigParameter`](../../src/app/core/store/core/server-config/server-config.selectors.ts) selector.

## ICM Endpoint Configuration

2 changes: 1 addition & 1 deletion src/app/core/facades/app.facade.ts
Original file line number Diff line number Diff line change
@@ -13,11 +13,11 @@ import {
} from 'ish-core/store/core/configuration';
import { businessError, getGeneralError, getGeneralErrorType } from 'ish-core/store/core/error';
import { selectPath } from 'ish-core/store/core/router';
import { getServerConfigParameter } from 'ish-core/store/core/server-config';
import { getBreadcrumbData, getHeaderType, getWrapperClass, isStickyHeader } from 'ish-core/store/core/viewconf';
import { getLoggedInCustomer } from 'ish-core/store/customer/user';
import { getAllCountries, loadCountries } from 'ish-core/store/general/countries';
import { getRegionsByCountryCode, loadRegions } from 'ish-core/store/general/regions';
import { getServerConfigParameter } from 'ish-core/store/general/server-config';

@Injectable({ providedIn: 'root' })
export class AppFacade {
2 changes: 1 addition & 1 deletion src/app/core/facades/checkout.facade.ts
Original file line number Diff line number Diff line change
@@ -8,6 +8,7 @@ import { Attribute } from 'ish-core/models/attribute/attribute.model';
import { LineItemUpdate } from 'ish-core/models/line-item-update/line-item-update.model';
import { PaymentInstrument } from 'ish-core/models/payment-instrument/payment-instrument.model';
import { selectRouteData } from 'ish-core/store/core/router';
import { getServerConfigParameter } from 'ish-core/store/core/server-config';
import { getAllAddresses } from 'ish-core/store/customer/addresses';
import {
addPromotionCodeToBasket,
@@ -47,7 +48,6 @@ import {
} from 'ish-core/store/customer/basket';
import { getOrdersError, getSelectedOrder } from 'ish-core/store/customer/orders';
import { getLoggedInUser } from 'ish-core/store/customer/user';
import { getServerConfigParameter } from 'ish-core/store/general/server-config';
import { whenFalsy, whenTruthy } from 'ish-core/utils/operators';

// tslint:disable:member-ordering
2 changes: 1 addition & 1 deletion src/app/core/services/api/api.service.spec.ts
Original file line number Diff line number Diff line change
@@ -525,7 +525,7 @@ describe('Api Service', () => {
TestBed.configureTestingModule({
// https://angular.io/guide/http#testing-http-requests
imports: [
CoreStoreModule.forTesting(['configuration']),
CoreStoreModule.forTesting(['configuration', 'serverConfig']),
CustomerStoreModule.forTesting('user'),
HttpClientTestingModule,
],
Original file line number Diff line number Diff line change
@@ -22,7 +22,10 @@ describe('Configuration Effects', () => {
translateServiceMock = mock(TranslateService);

TestBed.configureTestingModule({
imports: [BrowserTransferStateModule, CoreStoreModule.forTesting(['configuration'], [ConfigurationEffects])],
imports: [
BrowserTransferStateModule,
CoreStoreModule.forTesting(['configuration', 'serverConfig'], [ConfigurationEffects]),
],
providers: [
{ provide: TranslateService, useFactory: () => instance(translateServiceMock) },
provideMockActions(() => actions$),
Original file line number Diff line number Diff line change
@@ -35,7 +35,11 @@ describe('Configuration Integration', () => {
declarations: [DummyComponent],
imports: [
BrowserTransferStateModule,
CoreStoreModule.forTesting(['router', 'configuration'], [ConfigurationEffects], [configurationMeta]),
CoreStoreModule.forTesting(
['router', 'configuration', 'serverConfig'],
[ConfigurationEffects],
[configurationMeta]
),
RouterTestingModule.withRoutes([
{ path: 'home', component: DummyComponent, canActivate: [ConfigurationGuard] },
]),
Original file line number Diff line number Diff line change
@@ -17,6 +17,7 @@ export interface ConfigurationState {
identityProviders?: { [id: string]: { type?: string; [key: string]: unknown } };
features?: string[];
theme?: string;
defaultLocale?: string;
locales?: Locale[];
lang?: string;
// not synced via state transfer
@@ -31,6 +32,7 @@ const initialState: ConfigurationState = {
application: undefined,
features: undefined,
theme: undefined,
defaultLocale: environment.defaultLocale,
locales: environment.locales,
lang: undefined,
_deviceType: environment.defaultDeviceType,
109 changes: 108 additions & 1 deletion src/app/core/store/core/configuration/configuration.selectors.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { TestBed } from '@angular/core/testing';

import { Locale } from 'ish-core/models/locale/locale.model';
import { CoreStoreModule } from 'ish-core/store/core/core-store.module';
import { loadServerConfigSuccess } from 'ish-core/store/core/server-config';
import { StoreWithSnapshots, provideStoreSnapshots } from 'ish-core/utils/dev/ngrx-testing';

import { applyConfiguration } from './configuration.actions';
@@ -21,7 +23,7 @@ describe('Configuration Selectors', () => {

beforeEach(() => {
TestBed.configureTestingModule({
imports: [CoreStoreModule.forTesting(['configuration'])],
imports: [CoreStoreModule.forTesting(['configuration', 'serverConfig'])],
providers: [provideStoreSnapshots()],
});

@@ -106,4 +108,109 @@ describe('Configuration Selectors', () => {
`);
});
});

describe('after setting default locales', () => {
beforeEach(() => {
store$.dispatch(
applyConfiguration({
locales: [
{ lang: 'de_DE' },
{ lang: 'en_US' },
{ lang: 'fr_BE' },
{ lang: 'nl_BE' },
{ lang: 'no_NO' },
{ lang: 'zh_CN' },
] as Locale[],
})
);
});

describe('without ICM server configuration', () => {
it('should choose the internal default Locale when no ICM is available', () => {
store$.dispatch(
applyConfiguration({
...store$.state.configuration,
defaultLocale: 'en_US',
})
);
expect(getCurrentLocale(store$.state)).toMatchInlineSnapshot(`
Object {
"lang": "en_US",
}
`);
});

it('should choose the first locale when no ICM or internal configuration is available', () => {
store$.dispatch(
applyConfiguration({
...store$.state.configuration,
defaultLocale: undefined,
})
);
expect(getCurrentLocale(store$.state)).toMatchInlineSnapshot(`
Object {
"lang": "de_DE",
}
`);
});

it.each`
requested | chosen
${'de_DE'} | ${'de_DE'}
${'no_NO'} | ${'no_NO'}
${'nl_BE'} | ${'nl_BE'}
${'zh_CN'} | ${'zh_CN'}
${'nl_NL'} | ${'de_DE'}
`('should choose $chosen when $requested is requested', ({ requested, chosen }) => {
store$.dispatch(applyConfiguration({ lang: requested, defaultLocale: undefined }));
expect(getCurrentLocale(store$.state)?.lang).toEqual(chosen);
});
});

describe('with ICM server configuration', () => {
beforeEach(() => {
store$.dispatch(
loadServerConfigSuccess({
config: {
general: {
defaultLocale: 'en_US',
locales: ['en_US', 'de_DE', 'fr_BE', 'nl_BE', 'fr_FR'],
},
},
})
);
});

it('should filter available locales for matching ICM server locales', () => {
expect(getAvailableLocales(store$.state)?.map(l => l.lang)).toMatchInlineSnapshot(`
Array [
"en_US",
"de_DE",
"fr_BE",
"nl_BE",
]
`);
});

it('should choose the ICM configured default locale when ICM configuration is available', () => {
expect(getCurrentLocale(store$.state)).toMatchInlineSnapshot(`
Object {
"lang": "en_US",
}
`);
});

it.each`
requested | chosen
${'de_DE'} | ${'de_DE'}
${'no_NO'} | ${'en_US'}
${'nl_BE'} | ${'nl_BE'}
${'zh_CN'} | ${'en_US'}
${'nl_NL'} | ${'en_US'}
`('should choose $chosen when $requested is requested', ({ requested, chosen }) => {
store$.dispatch(applyConfiguration({ lang: requested }));
expect(getCurrentLocale(store$.state)?.lang).toEqual(chosen);
});
});
});
});
37 changes: 34 additions & 3 deletions src/app/core/store/core/configuration/configuration.selectors.ts
Original file line number Diff line number Diff line change
@@ -2,6 +2,7 @@ import { createSelector, createSelectorFactory, resultMemoize } from '@ngrx/stor
import { isEqual } from 'lodash-es';

import { getCoreState } from 'ish-core/store/core/core-store';
import { getServerConfigParameter } from 'ish-core/store/core/server-config';

import { ConfigurationState } from './configuration.reducer';

@@ -33,14 +34,44 @@ export const getFeatures = createSelector(getConfigurationState, state => state.

export const getTheme = createSelector(getConfigurationState, state => state.theme);

export const getAvailableLocales = createSelector(getConfigurationState, state => state.locales);
const defaultLocale = createSelector(getConfigurationState, state => state.defaultLocale);

/**
* locales configured in environment.ts
*/
const internalLocales = createSelector(getConfigurationState, state => state.locales);

/**
* environment.ts locales filtered by locales configured in ICM
*
* TODO: available locales should not be filtered by local environment,
* if no locale is available, then a default configured locale from environment.ts should be loaded as fallback
*/
export const getAvailableLocales = createSelector(
internalLocales,
getServerConfigParameter<string[]>('general.locales'),
(configured, activated) =>
activated?.length ? activated.map(lang => configured?.find(l => l.lang === lang)).filter(x => !!x) : configured
);

const internalRequestedLocale = createSelector(getConfigurationState, state => state.lang);

/**
* selects the current locale if set. If not returns the first available locale
* tries to find requested locale,
* falls back to ICM configured default locale if no match is found,
* and finally falls back to first available locale if none is configured
*/
export const getCurrentLocale = createSelector(
getConfigurationState,
state => state.locales.find(l => l.lang === state.lang) || state.locales[0]
getAvailableLocales,
internalRequestedLocale,
defaultLocale,
getServerConfigParameter<string>('general.defaultLocale'),
(available, requested, internalDefaultLocale, configuredDefault) =>
available?.find(l => l.lang === requested) ??
available?.find(l => l.lang === configuredDefault) ??
available?.find(l => l.lang === internalDefaultLocale) ??
available?.[0]
);

export const getDeviceType = createSelector(getConfigurationState, state => state._deviceType);
5 changes: 4 additions & 1 deletion src/app/core/store/core/core-store.module.ts
Original file line number Diff line number Diff line change
@@ -17,6 +17,8 @@ import { ErrorEffects } from './error/error.effects';
import { errorReducer } from './error/error.reducer';
import { MessagesEffects } from './messages/messages.effects';
import { CustomRouterSerializer } from './router/router.serializer';
import { ServerConfigEffects } from './server-config/server-config.effects';
import { serverConfigReducer } from './server-config/server-config.reducer';
import { ViewconfEffects } from './viewconf/viewconf.effects';
import { viewconfReducer } from './viewconf/viewconf.reducer';

@@ -25,9 +27,10 @@ const coreReducers: ActionReducerMap<CoreState> = {
error: errorReducer,
viewconf: viewconfReducer,
configuration: configurationReducer,
serverConfig: serverConfigReducer,
};

const coreEffects = [ErrorEffects, ViewconfEffects, ConfigurationEffects, MessagesEffects];
const coreEffects = [ErrorEffects, ViewconfEffects, ConfigurationEffects, MessagesEffects, ServerConfigEffects];

const coreMetaReducers: MetaReducer<CoreState>[] = [
ngrxStateTransferMeta,
2 changes: 2 additions & 0 deletions src/app/core/store/core/core-store.ts
Original file line number Diff line number Diff line change
@@ -6,12 +6,14 @@ import { ErrorState } from 'ish-core/store/core/error/error.reducer';
import { ViewconfState } from 'ish-core/store/core/viewconf/viewconf.reducer';

import { RouterState } from './router/router.reducer';
import { ServerConfigState } from './server-config/server-config.reducer';

export interface CoreState {
router: RouterReducerState<RouterState>;
error: ErrorState;
viewconf: ViewconfState;
configuration: ConfigurationState;
serverConfig: ServerConfigState;
}

export const getCoreState: Selector<CoreState, CoreState> = state => state;
Original file line number Diff line number Diff line change
@@ -8,7 +8,6 @@ import { instance, mock, when } from 'ts-mockito';

import { ConfigurationService } from 'ish-core/services/configuration/configuration.service';
import { CoreStoreModule } from 'ish-core/store/core/core-store.module';
import { GeneralStoreModule } from 'ish-core/store/general/general-store.module';
import { makeHttpError } from 'ish-core/utils/dev/api-service-utils';
import { StoreWithSnapshots, provideStoreSnapshots } from 'ish-core/utils/dev/ngrx-testing';
import { routerTestNavigationAction } from 'ish-core/utils/dev/routing';
@@ -25,7 +24,7 @@ describe('Server Config Effects', () => {
when(configurationServiceMock.getServerConfiguration()).thenReturn(of({}));

TestBed.configureTestingModule({
imports: [CoreStoreModule.forTesting([], [ServerConfigEffects]), GeneralStoreModule.forTesting('serverConfig')],
imports: [CoreStoreModule.forTesting(['serverConfig'], [ServerConfigEffects])],
providers: [
provideStoreSnapshots(),
{ provide: ConfigurationService, useFactory: () => instance(configurationServiceMock) },
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { TestBed } from '@angular/core/testing';

import { CoreStoreModule } from 'ish-core/store/core/core-store.module';
import { GeneralStoreModule } from 'ish-core/store/general/general-store.module';
import { StoreWithSnapshots, provideStoreSnapshots } from 'ish-core/utils/dev/ngrx-testing';

import { loadServerConfigSuccess } from './server-config.actions';
@@ -12,7 +11,7 @@ describe('Server Config Selectors', () => {

beforeEach(() => {
TestBed.configureTestingModule({
imports: [CoreStoreModule.forTesting(), GeneralStoreModule.forTesting('serverConfig')],
imports: [CoreStoreModule.forTesting(['serverConfig'])],
providers: [provideStoreSnapshots()],
});

Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { createSelector } from '@ngrx/store';

import { getGeneralState } from 'ish-core/store/general/general-store';
import { getCoreState } from 'ish-core/store/core/core-store';

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

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

Original file line number Diff line number Diff line change
@@ -12,10 +12,9 @@ import { BasketValidation } from 'ish-core/models/basket-validation/basket-valid
import { Product } from 'ish-core/models/product/product.model';
import { BasketService } from 'ish-core/services/basket/basket.service';
import { CoreStoreModule } from 'ish-core/store/core/core-store.module';
import { loadServerConfigSuccess } from 'ish-core/store/core/server-config';
import { CustomerStoreModule } from 'ish-core/store/customer/customer-store.module';
import { createOrder } from 'ish-core/store/customer/orders';
import { GeneralStoreModule } from 'ish-core/store/general/general-store.module';
import { loadServerConfigSuccess } from 'ish-core/store/general/server-config';
import { loadProductSuccess } from 'ish-core/store/shopping/products';
import { makeHttpError } from 'ish-core/utils/dev/api-service-utils';
import { BasketMockData } from 'ish-core/utils/dev/basket-mock-data';
@@ -50,9 +49,8 @@ describe('Basket Validation Effects', () => {
TestBed.configureTestingModule({
declarations: [DummyComponent],
imports: [
CoreStoreModule.forTesting(),
CoreStoreModule.forTesting(['serverConfig']),
CustomerStoreModule.forTesting('user', 'basket'),
GeneralStoreModule.forTesting('serverConfig'),
RouterTestingModule.withRoutes([
{ path: 'checkout', children: [{ path: 'address', component: DummyComponent }] },
{ path: 'checkout', children: [{ path: 'review', component: DummyComponent }] },
Original file line number Diff line number Diff line change
@@ -12,8 +12,8 @@ import {
BasketValidationScopeType,
} from 'ish-core/models/basket-validation/basket-validation.model';
import { BasketService } from 'ish-core/services/basket/basket.service';
import { getServerConfigParameter } from 'ish-core/store/core/server-config';
import { createOrder } from 'ish-core/store/customer/orders';
import { getServerConfigParameter } from 'ish-core/store/general/server-config';
import { loadProduct } from 'ish-core/store/shopping/products';
import { mapErrorToAction, mapToPayload, mapToPayloadProperty, whenTruthy } from 'ish-core/utils/operators';

Loading