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
Show file tree
Hide file tree
Changes from all 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
Expand Up @@ -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

Expand Down
4 changes: 2 additions & 2 deletions e2e/cypress/integration/pages/meta-data.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export class MetaDataModule {
'og:image': /.*og-image-default.*/,
'og:type': 'website',
'og:locale': 'en_US',
'og:locale:alternate': ['de_DE', 'fr_FR'],
'og:locale:alternate': ['fr_FR', 'de_DE'],
};

meta(key: string) {
Expand All @@ -29,7 +29,7 @@ export class MetaDataModule {
}

private checkStrategy(val: string | RegExp | string[]) {
return typeof val === 'string' ? 'equal' : Array.isArray(val) ? 'deep.equal' : 'match';
return typeof val === 'string' ? 'equal' : Array.isArray(val) ? 'include.members' : 'match';
}

check(expect: { title?: string; url?: RegExp; description?: string; [key: string]: string | RegExp }) {
Expand Down
2 changes: 1 addition & 1 deletion src/app/core/facades/app.facade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion src/app/core/facades/checkout.facade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down
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
Expand Up @@ -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,
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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$),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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] },
]),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -31,6 +32,7 @@ const initialState: ConfigurationState = {
application: undefined,
features: undefined,
theme: undefined,
defaultLocale: environment.defaultLocale,
locales: environment.locales,
lang: undefined,
_deviceType: environment.defaultDeviceType,
Expand Down
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';
Expand All @@ -21,7 +23,7 @@ describe('Configuration Selectors', () => {

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

Expand Down Expand Up @@ -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
Expand Up @@ -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';

Expand Down Expand Up @@ -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);
Expand Down
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
Expand Up @@ -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';

Expand All @@ -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,
Expand Down
2 changes: 2 additions & 0 deletions src/app/core/store/core/core-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Up @@ -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';
Expand All @@ -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) },
Expand Down
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';
Expand All @@ -12,7 +11,7 @@ describe('Server Config Selectors', () => {

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

Expand Down
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);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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 }] },
Expand Down
Loading