-
Notifications
You must be signed in to change notification settings - Fork 86
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add Address Doctor integration (#1337)
* introduce notifier service to notify address doctor for new address check * use feature event service to notify extensions for further actions from the outside * use formly for address doctor form * mapper to map address doctor data to Intershop compatible Address interface * implement caching functionality to avoid unnecessary REST requests * add additional documentation for address doctor * introduce helper to check addresses for equality * don't open modal when no suggestions are available * update existing address form with selected address before submitting Co-authored-by: Marcel Eisentraut <meisentraut@intershop.de> Co-authored-by: Stefan Hauke <s.hauke@intershop.de>
- Loading branch information
1 parent
c5973ac
commit 28eb2ce
Showing
40 changed files
with
1,614 additions
and
223 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
<!-- | ||
kb_guide | ||
kb_pwa | ||
kb_everyone | ||
kb_sync_latest_only | ||
--> | ||
|
||
# Address Check with Address Doctor | ||
|
||
We integrated [Address Doctor](https://www.informatica.com/de/products/data-quality/data-as-a-service/address-verification.html) to verify address data for correctness. | ||
|
||
## Setup | ||
|
||
First, activate the feature toggle `addressDoctor`. | ||
You will also have to provide the endpoint and additional verification data. | ||
This can be done by defining it in [Angular CLI environment](../concepts/configuration.md#angular-cli-environments) files: | ||
|
||
```typescript | ||
export const environment: Environment = { | ||
...ENVIRONMENT_DEFAULTS, | ||
|
||
addressDoctor: { | ||
url: '<addressDoctor-url>', | ||
login: '<addressDoctor-login>', | ||
password: '<addressDoctor-password>', | ||
maxResultCount: 5, | ||
}, | ||
``` | ||
This configuration can also be supplied via environment variable `ADDRESS_DOCTOR` as stringified JSON: | ||
```text | ||
ADDRESS_DOCTOR='{ "addressDoctor": { "url": "<addressDoctor-url>", "login": "<addressDoctor-login>", "password": "<addressDoctor-password>", "maxResultCount": "5" } }'; | ||
``` | ||
## Workflow | ||
To check an address with the address doctor the PWA needs to render the `<ish-lazy-address-doctor>` component. | ||
When the user submits the address data, the PWA needs to send a [feature notification event](../../src/app/core/utils/feature-event/feature-event.service.ts) with the request to check the data. | ||
|
||
```typescript | ||
const id = this.featureEventService.sendNotification('addressDoctor', 'check-address', { | ||
address, | ||
}); | ||
``` | ||
This method will submit the address data to the Address Doctor REST API and open a modal with all suggestions. | ||
The user needs to decide and confirm the address, which is the correct one. | ||
With the subscribe on the [eventResultListener$](../../src/app/core/utils/feature-event/feature-event.service.ts) observable, the initial component can react on the confirmation data. | ||
```typescript | ||
this.featureEventService | ||
.eventResultListener$('addressDoctor', 'check-address', id) | ||
.pipe(whenTruthy(), take(1), takeUntil(this.destroy$)) | ||
.subscribe(({ address }) => { | ||
if (address) { | ||
this.accountFacade.updateCustomerAddress(address); | ||
} | ||
}); | ||
``` | ||
## Further References | ||
- [Concept - Configuration](../concepts/configuration.md) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
22 changes: 22 additions & 0 deletions
22
src/app/extensions/address-doctor/address-doctor.module.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import { NgModule } from '@angular/core'; | ||
|
||
import { FEATURE_EVENT_RESULT_LISTENER } from 'ish-core/utils/feature-event/feature-event.service'; | ||
import { SharedModule } from 'ish-shared/shared.module'; | ||
|
||
import { AddressDoctorEventsService } from './services/address-doctor-events/address-doctor-events.service'; | ||
import { AddressDoctorModalComponent } from './shared/address-doctor-modal/address-doctor-modal.component'; | ||
import { AddressDoctorComponent } from './shared/address-doctor/address-doctor.component'; | ||
|
||
@NgModule({ | ||
imports: [SharedModule], | ||
declarations: [AddressDoctorComponent, AddressDoctorModalComponent], | ||
exports: [SharedModule], | ||
providers: [ | ||
{ | ||
provide: FEATURE_EVENT_RESULT_LISTENER, | ||
useFactory: AddressDoctorEventsService.checkAddressResultListenerFactory, | ||
multi: true, | ||
}, | ||
], | ||
}) | ||
export class AddressDoctorModule {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
**/lazy* |
12 changes: 12 additions & 0 deletions
12
src/app/extensions/address-doctor/exports/address-doctor-exports.module.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import { CommonModule } from '@angular/common'; | ||
import { NgModule } from '@angular/core'; | ||
import { TranslateModule } from '@ngx-translate/core'; | ||
|
||
import { LazyAddressDoctorComponent } from './lazy-address-doctor/lazy-address-doctor.component'; | ||
|
||
@NgModule({ | ||
imports: [CommonModule, TranslateModule], | ||
declarations: [LazyAddressDoctorComponent], | ||
exports: [LazyAddressDoctorComponent], | ||
}) | ||
export class AddressDoctorExportsModule {} |
32 changes: 32 additions & 0 deletions
32
src/app/extensions/address-doctor/facades/address-doctor.facade.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import { Injectable, inject } from '@angular/core'; | ||
import { isEqual } from 'lodash-es'; | ||
import { Observable, map, of, race, tap, timer } from 'rxjs'; | ||
|
||
import { Address } from 'ish-core/models/address/address.model'; | ||
|
||
import { AddressDoctorService } from '../services/address-doctor/address-doctor.service'; | ||
|
||
@Injectable({ providedIn: 'root' }) | ||
export class AddressDoctorFacade { | ||
private addressDoctorService = inject(AddressDoctorService); | ||
|
||
private lastAddressCheck: Address; | ||
private lastAddressCheckResult: Address[] = []; | ||
|
||
checkAddress(address: Address): Observable<Address[]> { | ||
if (isEqual(address, this.lastAddressCheck)) { | ||
return of(this.lastAddressCheckResult); | ||
} | ||
|
||
this.lastAddressCheck = address; | ||
return race( | ||
this.addressDoctorService.postAddress(address).pipe( | ||
tap(result => { | ||
this.lastAddressCheckResult = result; | ||
}) | ||
), | ||
// if the address check takes longer than 5 seconds return with no suggestions | ||
timer(5000).pipe(map(() => [])) | ||
); | ||
} | ||
} |
6 changes: 6 additions & 0 deletions
6
src/app/extensions/address-doctor/models/address-doctor/address-doctor-config.model.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
export interface AddressDoctorConfig { | ||
url: string; | ||
login: string; | ||
password: string; | ||
maxResultCount: number; | ||
} |
5 changes: 5 additions & 0 deletions
5
src/app/extensions/address-doctor/models/address-doctor/address-doctor-event.model.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
export enum AddressDoctorEvents { | ||
CheckAddress = 'check-address', | ||
CheckAddressSuccess = 'check-address-successful', | ||
CheckAddressCancelled = 'check-address-cancellation', | ||
} |
16 changes: 16 additions & 0 deletions
16
src/app/extensions/address-doctor/models/address-doctor/address-doctor.helper.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import { isEqual, pick } from 'lodash-es'; | ||
|
||
import { Address } from 'ish-core/models/address/address.model'; | ||
|
||
import { AddressDoctorMapper } from './address-doctor.mapper'; | ||
|
||
export class AddressDoctorHelper { | ||
static equalityCheck(address1: Address, address2: Address): boolean { | ||
if (!address1 || !address2) { | ||
return false; | ||
} | ||
|
||
const attributes = AddressDoctorMapper.attributes; | ||
return isEqual(pick(address1, ...attributes), pick(address2, ...attributes)); | ||
} | ||
} |
68 changes: 68 additions & 0 deletions
68
src/app/extensions/address-doctor/models/address-doctor/address-doctor.interface.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
export interface AddressDoctorVariants { | ||
Variants: AddressDoctorVariant[]; | ||
} | ||
|
||
export interface AddressDoctorVariant { | ||
StatusValues: StatusValues; | ||
AddressElements: AddressElements; | ||
PreformattedData: PreformattedData; | ||
} | ||
|
||
interface StatusValues { | ||
AddressType: string; | ||
ResultGroup: string; | ||
LanguageISO3: string; | ||
UsedVerificationLevel: string; | ||
MatchPercentage: string; | ||
Script: string; | ||
AddressCount: string; | ||
ResultQuality: number; | ||
} | ||
|
||
interface AddressElements { | ||
Street: AddressElement[]; | ||
HouseNumber: AddressElement[]; | ||
Locality: AddressElement[]; | ||
PostalCode: AddressElement[]; | ||
AdministrativeDivision: AdministrativeDivision[]; | ||
Country: Country[]; | ||
} | ||
|
||
interface AddressElement { | ||
Value: string; | ||
SubItems: SubItems; | ||
} | ||
|
||
interface SubItems { | ||
Name?: string; | ||
// eslint-disable-next-line id-blacklist | ||
Number?: string; | ||
Base?: string; | ||
} | ||
|
||
interface AdministrativeDivision { | ||
Value: string; | ||
Variants: Variants; | ||
} | ||
|
||
interface Variants { | ||
Extended: string; | ||
ISO: string; | ||
Abbreviation: string; | ||
} | ||
|
||
interface Country { | ||
Code: string; | ||
Name: string; | ||
} | ||
|
||
interface PreformattedData { | ||
SingleAddressLine: PreformattedDataValue; | ||
PostalDeliveryAddressLines: PreformattedDataValue[]; | ||
PostalFormattedAddressLines: PreformattedDataValue[]; | ||
PostalLocalityLine: PreformattedDataValue; | ||
} | ||
|
||
interface PreformattedDataValue { | ||
Value: string; | ||
} |
23 changes: 23 additions & 0 deletions
23
src/app/extensions/address-doctor/models/address-doctor/address-doctor.mapper.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import { Address } from 'ish-core/models/address/address.model'; | ||
|
||
import { AddressDoctorVariant } from './address-doctor.interface'; | ||
|
||
/** | ||
* Map incoming data from Address Doctor REST API to ICM compliant addresses. | ||
* The mapper is implemented and tested against German and British addresses. | ||
* The implementation with attributes needs to be adapted for other foreign addresses, when the overwrite does not match. | ||
* | ||
*/ | ||
export class AddressDoctorMapper { | ||
static attributes = ['addressLine1', 'postalCode', 'city']; | ||
|
||
static fromData(variant: AddressDoctorVariant): Partial<Address> { | ||
return { | ||
addressLine1: `${variant.AddressElements.Street ? variant.AddressElements.Street[0].Value : ''} ${ | ||
variant.AddressElements.HouseNumber ? variant.AddressElements.HouseNumber[0].Value : '' | ||
}`.trim(), | ||
postalCode: (variant.AddressElements.PostalCode ? variant.AddressElements.PostalCode[0].Value : '').trim(), | ||
city: variant.AddressElements.Locality.map(loc => loc.Value).join(' '), | ||
}; | ||
} | ||
} |
38 changes: 38 additions & 0 deletions
38
...extensions/address-doctor/services/address-doctor-events/address-doctor-events.service.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import { inject } from '@angular/core'; | ||
import { filter, take, takeUntil } from 'rxjs/operators'; | ||
|
||
import { FeatureEventResultListener, FeatureEventService } from 'ish-core/utils/feature-event/feature-event.service'; | ||
import { whenTruthy } from 'ish-core/utils/operators'; | ||
|
||
import { AddressDoctorEvents } from '../../models/address-doctor/address-doctor-event.model'; | ||
|
||
export class AddressDoctorEventsService { | ||
static checkAddressResultListenerFactory(): FeatureEventResultListener { | ||
const featureEventService = inject(FeatureEventService); | ||
return { | ||
feature: 'addressDoctor', | ||
event: AddressDoctorEvents.CheckAddress, | ||
resultListener$: (id: string) => { | ||
if (!id) { | ||
return; | ||
} | ||
|
||
return featureEventService.eventResults$.pipe( | ||
whenTruthy(), | ||
// respond only when CheckAddressSuccess event is emitted for specific notification id | ||
filter( | ||
result => result.id === id && result.event === AddressDoctorEvents.CheckAddressSuccess && result.successful | ||
), | ||
take(1), | ||
takeUntil( | ||
featureEventService.eventResults$.pipe( | ||
whenTruthy(), | ||
// close event stream when CheckAddressCancelled event is emitted for specific notification id | ||
filter(result => result.id === id && result.event === AddressDoctorEvents.CheckAddressCancelled) | ||
) | ||
) | ||
); | ||
}, | ||
}; | ||
} | ||
} |
Oops, something went wrong.