From 9b715e304a344f63a5acc12cfb2f0fae639e6cd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Frederik=20Gie=C3=9Fke?= Date: Thu, 25 Jul 2024 09:46:05 +0200 Subject: [PATCH 1/2] feat: add icmServerWeb/ICM_SERVER_WEB configuration option for ICM Pipeline URL references * provide configuration option similar to `icmServer` or `icmServerStatic`` --- src/app/core/facades/app.facade.ts | 2 ++ .../configuration/configuration.effects.ts | 3 +++ .../configuration/configuration.reducer.ts | 2 ++ .../configuration.selectors.spec.ts | 23 +++++++++++++--- .../configuration/configuration.selectors.ts | 26 ++++++++++++++----- src/environments/environment.model.ts | 2 ++ 6 files changed, 49 insertions(+), 9 deletions(-) diff --git a/src/app/core/facades/app.facade.ts b/src/app/core/facades/app.facade.ts index 0c353cbc36..be1fb244f1 100644 --- a/src/app/core/facades/app.facade.ts +++ b/src/app/core/facades/app.facade.ts @@ -11,6 +11,7 @@ import { getCurrentLocale, getDeviceType, getICMBaseURL, + getPipelineEndpoint, getRestEndpoint, } from 'ish-core/store/core/configuration'; import { businessError, getGeneralError, getGeneralErrorType } from 'ish-core/store/core/error'; @@ -44,6 +45,7 @@ export class AppFacade { breadcrumbData$ = this.store.pipe(select(getBreadcrumbData)); getRestEndpoint$ = this.store.pipe(select(getRestEndpoint)); + getPipelineEndpoint$ = this.store.pipe(select(getPipelineEndpoint)); appWrapperClasses$ = combineLatest([ this.store.pipe( diff --git a/src/app/core/store/core/configuration/configuration.effects.ts b/src/app/core/store/core/configuration/configuration.effects.ts index 2f4b00b76d..3da39ea0ec 100644 --- a/src/app/core/store/core/configuration/configuration.effects.ts +++ b/src/app/core/store/core/configuration/configuration.effects.ts @@ -68,6 +68,7 @@ export class ConfigurationEffects { this.stateProperties.getStateOrEnvOrDefault('ICM_BASE_URL', 'icmBaseURL'), this.stateProperties.getStateOrEnvOrDefault('ICM_SERVER', 'icmServer'), this.stateProperties.getStateOrEnvOrDefault('ICM_SERVER_STATIC', 'icmServerStatic'), + this.stateProperties.getStateOrEnvOrDefault('ICM_SERVER_WEB', 'icmServerWeb'), this.stateProperties.getStateOrEnvOrDefault('ICM_CHANNEL', 'icmChannel'), this.stateProperties.getStateOrEnvOrDefault('ICM_APPLICATION', 'icmApplication'), this.stateProperties @@ -91,6 +92,7 @@ export class ConfigurationEffects { baseURL, server, serverStatic, + serverWeb, channel, application, features, @@ -102,6 +104,7 @@ export class ConfigurationEffects { baseURL, server, serverStatic, + serverWeb, channel, application, features, diff --git a/src/app/core/store/core/configuration/configuration.reducer.ts b/src/app/core/store/core/configuration/configuration.reducer.ts index 2429cb5d97..0fa2d65885 100644 --- a/src/app/core/store/core/configuration/configuration.reducer.ts +++ b/src/app/core/store/core/configuration/configuration.reducer.ts @@ -12,6 +12,7 @@ export interface ConfigurationState { baseURL?: string; server?: string; serverStatic?: string; + serverWeb?: string; channel?: string; application?: string; hybridApplication?: string; @@ -34,6 +35,7 @@ const initialState: ConfigurationState = { baseURL: undefined, server: undefined, serverStatic: undefined, + serverWeb: undefined, channel: undefined, application: undefined, hybridApplication: undefined, diff --git a/src/app/core/store/core/configuration/configuration.selectors.spec.ts b/src/app/core/store/core/configuration/configuration.selectors.spec.ts index e4c21c26b3..1a22d2ddc2 100644 --- a/src/app/core/store/core/configuration/configuration.selectors.spec.ts +++ b/src/app/core/store/core/configuration/configuration.selectors.spec.ts @@ -16,6 +16,7 @@ import { getICMServerURL, getICMStaticURL, getIdentityProvider, + getPipelineEndpoint, getRestEndpoint, } from './configuration.selectors'; @@ -42,13 +43,14 @@ describe('Configuration Selectors', () => { describe('initial state', () => { it('should be undefined or empty values for most selectors', () => { - expect(getRestEndpoint(store$.state)).toBeUndefined(); expect(getICMBaseURL(store$.state)).toBeUndefined(); expect(getICMServerURL(store$.state)).toBeUndefined(); expect(getICMStaticURL(store$.state)).toBeUndefined(); expect(getFeatures(store$.state)).toBeUndefined(); expect(getDeviceType(store$.state)).toBeUndefined(); expect(getIdentityProvider(store$.state)).toBeUndefined(); + expect(getRestEndpoint(store$.state)).toBeUndefined(); + expect(getPipelineEndpoint(store$.state)).toBeUndefined(); }); }); @@ -59,18 +61,32 @@ describe('Configuration Selectors', () => { baseURL: 'http://example.org', server: 'api', serverStatic: 'static', + serverWeb: 'web', channel: 'site', features: ['compare', 'recently'], }) ); + store$.dispatch( + loadServerConfigSuccess({ + config: { + general: { + defaultLocale: 'en_US', + defaultCurrency: 'USD', + locales: ['en_US', 'fr_BE', 'de_DE'], + currencies: ['USD', 'EUR'], + }, + }, + }) + ); }); it('should have defined values for all selectors', () => { - expect(getRestEndpoint(store$.state)).toEqual('http://example.org/api/site/-'); expect(getICMBaseURL(store$.state)).toEqual('http://example.org'); expect(getICMServerURL(store$.state)).toEqual('http://example.org/api'); expect(getICMStaticURL(store$.state)).toEqual('http://example.org/static/site/-'); expect(getFeatures(store$.state)).toIncludeAllMembers(['compare', 'recently']); + expect(getRestEndpoint(store$.state)).toEqual('http://example.org/api/site/-'); + expect(getPipelineEndpoint(store$.state)).toEqual('http://example.org/web/site/en_US/-/USD'); }); describe('after setting application', () => { @@ -83,11 +99,12 @@ describe('Configuration Selectors', () => { }); it('should have defined values for all selectors', () => { - expect(getRestEndpoint(store$.state)).toEqual('http://example.org/api/site/app'); expect(getICMBaseURL(store$.state)).toEqual('http://example.org'); expect(getICMServerURL(store$.state)).toEqual('http://example.org/api'); expect(getICMStaticURL(store$.state)).toEqual('http://example.org/static/site/app'); expect(getFeatures(store$.state)).toIncludeAllMembers(['compare', 'recently']); + expect(getRestEndpoint(store$.state)).toEqual('http://example.org/api/site/app'); + expect(getPipelineEndpoint(store$.state)).toEqual('http://example.org/web/site/en_US/app/USD'); }); }); diff --git a/src/app/core/store/core/configuration/configuration.selectors.ts b/src/app/core/store/core/configuration/configuration.selectors.ts index 142b5ee8f5..9bf67912a8 100644 --- a/src/app/core/store/core/configuration/configuration.selectors.ts +++ b/src/app/core/store/core/configuration/configuration.selectors.ts @@ -22,12 +22,8 @@ export const getICMServerURL = createSelector(getConfigurationState, state => state.baseURL && state.server ? `${ssrBaseUrl || state.baseURL}/${state.server}` : undefined ); -export const getRestEndpoint = createSelector( - getICMServerURL, - getConfigurationState, - getICMApplication, - (serverUrl, state, application) => - serverUrl && state.channel ? `${serverUrl}/${state.channel}/${application}` : undefined +export const getICMServerWebURL = createSelector(getConfigurationState, state => + state.baseURL && state.serverWeb ? `${ssrBaseUrl || state.baseURL}/${state.serverWeb}` : undefined ); export const getICMStaticURL = createSelector(getConfigurationState, getICMApplication, (state, application) => @@ -136,3 +132,21 @@ export const getMultiSiteLocaleMap = createSelector( getConfigurationState, (state: ConfigurationState) => state.multiSiteLocaleMap ); + +export const getRestEndpoint = createSelector( + getICMServerURL, + getConfigurationState, + getICMApplication, + (serverUrl, state, application) => + serverUrl && state.channel ? `${serverUrl}/${state.channel}/${application}` : undefined +); + +export const getPipelineEndpoint = createSelector( + getICMServerWebURL, + getConfigurationState, + getCurrentLocale, + getICMApplication, + getCurrentCurrency, + (serverUrl, state, locale, application, currency) => + serverUrl && state.channel ? `${serverUrl}/${state.channel}/${locale}/${application}/${currency}` : undefined +); diff --git a/src/environments/environment.model.ts b/src/environments/environment.model.ts index 9bf3f0a723..55aebdcbd5 100644 --- a/src/environments/environment.model.ts +++ b/src/environments/environment.model.ts @@ -12,6 +12,7 @@ export interface Environment { icmBaseURL: string; icmServer: string; icmServerStatic: string; + icmServerWeb: string; icmChannel: string; icmApplication?: string; hybridApplication?: string; @@ -148,6 +149,7 @@ export const ENVIRONMENT_DEFAULTS: Omit = { icmBaseURL: 'https://develop.icm.intershop.de', icmServer: 'INTERSHOP/rest/WFS', icmServerStatic: 'INTERSHOP/static/WFS', + icmServerWeb: 'INTERSHOP/web/WFS', icmApplication: '-', /* FEATURE TOGGLES */ From 6fa90dc735d4fdf01cfec7f4cb4ae812b73539be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Frederik=20Gie=C3=9Fke?= Date: Thu, 25 Jul 2024 09:48:48 +0200 Subject: [PATCH 2/2] feat: change OCI punchout URL to point to an ICM Pipeline (#1702) * ICM pipeline can handle OCI punchout functions `BACKGROUND_SEARCH` and `VALIDATE` better * ICM redirects to the PWA only for catalog browsing and the detail function + needs ICM 12.2.0, 11.11.1 or 7.10.41.3 * ICM pipeline `ViewOCICatalogPWA-Start` is used for the OCI punchout URL * added migration note and deprecation notes for similar PWA functionality --- docs/guides/migrations.md | 5 +++++ .../core/configuration/configuration.selectors.ts | 2 +- .../punchout-identity-provider.spec.ts | 1 + .../punchout-identity-provider.ts | 5 +++-- .../account-punchout-page.component.html | 2 +- .../account-punchout-page.component.spec.ts | 14 ++++++++++++++ .../account-punchout-page.component.ts | 5 ++++- .../punchout/services/punchout/punchout.service.ts | 4 ++++ 8 files changed, 33 insertions(+), 5 deletions(-) diff --git a/docs/guides/migrations.md b/docs/guides/migrations.md index bfa6450ff4..02f3697ade 100644 --- a/docs/guides/migrations.md +++ b/docs/guides/migrations.md @@ -7,6 +7,11 @@ kb_sync_latest_only # Migrations +## From 5.2 to 5.3 + +With ICM 12.2.0, 11.11.1 or 7.10.41.3 the ICM server itself provides an OCI punchout URL (Pipeline) that works better for the OCI punchout functions `BACKGROUND_SEARCH` and `VALIDATE` than the similar now deprecated functionality in the PWA. +For that reason the provided OCI Punchout URL is now pointing to the ICM pipeline `ViewOCICatalogPWA-Start` that handles the different functionalities and redirects to the PWA (configured as _External Base URL_ in ICM) only for catalog browsing and the detail function. + ## From 5.1 to 5.2 > [!NOTE] diff --git a/src/app/core/store/core/configuration/configuration.selectors.ts b/src/app/core/store/core/configuration/configuration.selectors.ts index 9bf67912a8..c9c31c9acc 100644 --- a/src/app/core/store/core/configuration/configuration.selectors.ts +++ b/src/app/core/store/core/configuration/configuration.selectors.ts @@ -22,7 +22,7 @@ export const getICMServerURL = createSelector(getConfigurationState, state => state.baseURL && state.server ? `${ssrBaseUrl || state.baseURL}/${state.server}` : undefined ); -export const getICMServerWebURL = createSelector(getConfigurationState, state => +const getICMServerWebURL = createSelector(getConfigurationState, state => state.baseURL && state.serverWeb ? `${ssrBaseUrl || state.baseURL}/${state.serverWeb}` : undefined ); diff --git a/src/app/extensions/punchout/identity-provider/punchout-identity-provider.spec.ts b/src/app/extensions/punchout/identity-provider/punchout-identity-provider.spec.ts index 116229d694..624e5f8aea 100644 --- a/src/app/extensions/punchout/identity-provider/punchout-identity-provider.spec.ts +++ b/src/app/extensions/punchout/identity-provider/punchout-identity-provider.spec.ts @@ -1,3 +1,4 @@ +/* eslint-disable etc/no-deprecated */ import { TestBed, fakeAsync, tick } from '@angular/core/testing'; import { ActivatedRouteSnapshot, Params, Router, UrlTree, convertToParamMap } from '@angular/router'; import { MockStore, provideMockStore } from '@ngrx/store/testing'; diff --git a/src/app/extensions/punchout/identity-provider/punchout-identity-provider.ts b/src/app/extensions/punchout/identity-provider/punchout-identity-provider.ts index 65d13c2244..ddfb4b181b 100644 --- a/src/app/extensions/punchout/identity-provider/punchout-identity-provider.ts +++ b/src/app/extensions/punchout/identity-provider/punchout-identity-provider.ts @@ -1,3 +1,4 @@ +/* eslint-disable etc/no-deprecated */ import { HttpEvent, HttpHandler, HttpRequest } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { ActivatedRouteSnapshot, Router, UrlTree } from '@angular/router'; @@ -178,7 +179,7 @@ export class PunchoutIdentityProvider implements IdentityProvider { if (route.queryParamMap.get('FUNCTION') === 'DETAIL' && route.queryParamMap.get('PRODUCTID')) { return of(this.router.parseUrl(`/product/${route.queryParamMap.get('PRODUCTID')}`)); - // Validation of Products + // Validation of Products - @deprecated This functionality should be handled by the ICM pipeline `ViewOCICatalogPWA-Start`. } else if (route.queryParamMap.get('FUNCTION') === 'VALIDATE' && route.queryParamMap.get('PRODUCTID')) { return this.punchoutService .getOciPunchoutProductData(route.queryParamMap.get('PRODUCTID'), route.queryParamMap.get('QUANTITY') || '1') @@ -187,7 +188,7 @@ export class PunchoutIdentityProvider implements IdentityProvider { map(() => false) ); - // Background Search + // Background Search - @deprecated: is now handled by the ICM ViewOCICatalogPWA-Start pipeline } else if (route.queryParamMap.get('FUNCTION') === 'BACKGROUND_SEARCH' && route.queryParamMap.get('SEARCHSTRING')) { return this.punchoutService.getOciPunchoutSearchData(route.queryParamMap.get('SEARCHSTRING')).pipe( tap(data => this.punchoutService.submitOciPunchoutData(data, false)), diff --git a/src/app/extensions/punchout/pages/account-punchout/account-punchout-page.component.html b/src/app/extensions/punchout/pages/account-punchout/account-punchout-page.component.html index be6702c23c..571a034e16 100644 --- a/src/app/extensions/punchout/pages/account-punchout/account-punchout-page.component.html +++ b/src/app/extensions/punchout/pages/account-punchout/account-punchout-page.component.html @@ -80,7 +80,7 @@

{{ 'account.punchout.oci.info.url.helptext' | translate }}

-

{{ ociPunchoutUrl }}

+

{{ ociPunchoutUrl$ | async }}

diff --git a/src/app/extensions/punchout/pages/account-punchout/account-punchout-page.component.spec.ts b/src/app/extensions/punchout/pages/account-punchout/account-punchout-page.component.spec.ts index 65c68dba83..daa8fd0e13 100644 --- a/src/app/extensions/punchout/pages/account-punchout/account-punchout-page.component.spec.ts +++ b/src/app/extensions/punchout/pages/account-punchout/account-punchout-page.component.spec.ts @@ -63,6 +63,9 @@ describe('Account Punchout Page Component', () => { when(punchoutFacade.punchoutUsersByRoute$()).thenReturn(of(users)); when(punchoutFacade.selectedPunchoutType$).thenReturn(of('oci')); when(appFacade.getRestEndpoint$).thenReturn(of('https://myBaseServerURL/INTERSHOP/rest/WFS/myChannel/rest')); + when(appFacade.getPipelineEndpoint$).thenReturn( + of('https://myBaseServerURL/INTERSHOP/web/WFS/myChannel/en_US/rest/USD') + ); when(punchoutFacade.punchoutLoading$).thenReturn(of(false)); }); @@ -114,4 +117,15 @@ describe('Account Punchout Page Component', () => { done(); }); }); + + it('should determine oci punchout url on init', done => { + fixture.detectChanges(); + + component.ociPunchoutUrl$.subscribe(url => { + expect(url).toMatchInlineSnapshot( + `"https://myBaseServerURL/INTERSHOP/web/WFS/myChannel/en_US/rest/USD/ViewOCICatalogPWA-Start?USERNAME=&PASSWORD=&HOOK_URL="` + ); + done(); + }); + }); }); diff --git a/src/app/extensions/punchout/pages/account-punchout/account-punchout-page.component.ts b/src/app/extensions/punchout/pages/account-punchout/account-punchout-page.component.ts index c59866a676..0799122bb9 100644 --- a/src/app/extensions/punchout/pages/account-punchout/account-punchout-page.component.ts +++ b/src/app/extensions/punchout/pages/account-punchout/account-punchout-page.component.ts @@ -21,8 +21,8 @@ export class AccountPunchoutPageComponent implements OnInit { loading$: Observable; error$: Observable; - ociPunchoutUrl = `${window.location.origin}/punchout?USERNAME=&PASSWORD=&HOOK_URL=`; cxmlPunchoutUrl$: Observable; + ociPunchoutUrl$: Observable; constructor( private accountFacade: AccountFacade, @@ -42,6 +42,9 @@ export class AccountPunchoutPageComponent implements OnInit { withLatestFrom(this.accountFacade.customer$), map(([url, customer]) => `${url}/customers/${customer?.customerNo}/punchouts/cxml1.2/setuprequest`) ); + this.ociPunchoutUrl$ = this.appFacade.getPipelineEndpoint$.pipe( + map(url => `${url}/ViewOCICatalogPWA-Start?USERNAME=&PASSWORD=&HOOK_URL=`) + ); } deleteUser(user: PunchoutUser) { diff --git a/src/app/extensions/punchout/services/punchout/punchout.service.ts b/src/app/extensions/punchout/services/punchout/punchout.service.ts index 13b8254e20..39e40be803 100644 --- a/src/app/extensions/punchout/services/punchout/punchout.service.ts +++ b/src/app/extensions/punchout/services/punchout/punchout.service.ts @@ -391,6 +391,8 @@ export class PunchoutService { * * @param productId The product id (SKU) of the product to validate. * @param quantity The quantity for the validation (default: '1'). + * + * @deprecated This functionality should be handled by the ICM pipeline `ViewOCICatalogPWA-Start`. */ getOciPunchoutProductData(productId: string, quantity = '1'): Observable[]> { if (!productId) { @@ -420,6 +422,8 @@ export class PunchoutService { * Gets a JSON object with the necessary OCI punchout data for the background search. * * @param searchString The search string to search punchout products. + * + * @deprecated This functionality should be handled by the ICM pipeline `ViewOCICatalogPWA-Start`. */ getOciPunchoutSearchData(searchString: string): Observable[]> { if (!searchString) {