Skip to content

Commit

Permalink
feat: product notifications (price, in stock) (#1327)
Browse files Browse the repository at this point in the history
* the product notification functionality for in stock and price notifications in the My Account Area (listing/editing/deleting) and on PDP (creating/editing/deleting) implemented as an extension for the PWA
* requires ICM version 7.10.40.1 for adapted email templates, REST functionality is already available in earlier versions

Closes: #693

Co-authored-by: Andreas Steinmann <asteinmann@intershop.com>
Co-authored-by: Silke <s.grueber@intershop.de>
  • Loading branch information
3 people authored Apr 2, 2023
1 parent 6888947 commit c07edca
Show file tree
Hide file tree
Showing 63 changed files with 3,092 additions and 1 deletion.
3 changes: 3 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@
# from src/app/extensions/order-templates/exports/.gitignore
/src/app/extensions/order-templates/exports/**/lazy*

# from src/app/extensions/product-notifications/exports/.gitignore
/src/app/extensions/product-notifications/exports/**/lazy*

# from src/app/extensions/punchout/exports/.gitignore
/src/app/extensions/punchout/exports/**/lazy*

Expand Down
1 change: 1 addition & 0 deletions docs/concepts/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ Of course, the ICM server must supply appropriate REST resources to leverage fun
| ---------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| compare | product compare feature (additional configuration via `dataRetention` configuration options) |
| contactUs | allows the user to contact the website provider via a contact web form |
| productNotifications | product notifications feature for price and in stock notifications |
| rating | display product ratings |
| recently | display recently viewed products (additional configuration via `dataRetention` configuration options) |
| storeLocator | display physical stores and their addresses |
Expand Down
2 changes: 2 additions & 0 deletions docs/guides/migrations.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,8 @@ If you want to inject a token use the methods `injectSingle` and `injectMultiple
There is a new linting rule `useTypeSafeInjectionTokenRule` that enforces the usage of these methods.
Find more information in the [Configuration Concept](../concepts/configuration.md#angular-cli-environments)

We introduced the product notifications feature as a new extension which is toggled with the feature toggle 'productNotifications' in the `environment.model.ts`.

## 3.2 to 3.3

To improve the accessibility of the PWA in regards to more elements being tab focusable a lot of `[routerLink]="[]"` where added to links that previously did not have a link reference.
Expand Down
13 changes: 13 additions & 0 deletions e2e/cypress/e2e/pages/account/edit-product-notification.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export class EditProductNotificationModule {
private priceInput = () => cy.get('ish-fieldset-field').find('[data-testing-id="priceValue"]');

private emailInput = () => cy.get('ish-fieldset-field').find('[data-testing-id="email"]');

editPriceNotification(price: number, email: string) {
this.priceInput().clear();
this.priceInput().type(price.toString());
this.emailInput().clear();
this.emailInput().type(email);
cy.get('.modal-footer button.btn-primary').click();
}
}
4 changes: 4 additions & 0 deletions e2e/cypress/e2e/pages/account/my-account.page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ export class MyAccountPage {
cy.get('a[data-testing-id="wishlists-nav-link"]').click();
}

navigateToProductNotifications() {
cy.get('a[data-testing-id="notifications-nav-link"]').click();
}

navigateToOrderTemplates() {
cy.get('a[data-testing-id="order-templates-nav-link"]').click();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { BreadcrumbModule } from '../breadcrumb.module';
import { HeaderModule } from '../header.module';

export class ProductNotificationsOverviewPage {
readonly tag = 'ish-account-product-notifications-page ';

readonly header = new HeaderModule();
readonly breadcrumb = new BreadcrumbModule();

get productNotificationsArray() {
return cy.get('[data-testing-id="product-notification-list-item"]');
}

get productNotificationNameArray() {
return this.productNotificationsArray.find('a[data-testing-id="product-name-link"]').invoke('text');
}

get productNotificationMessage() {
return this.productNotificationsArray.find('div[data-testing-id="product-notification-message"]').invoke('text');
}

get productNotificationListItems() {
return cy.get('[data-testing-id = "product-notification-list-item"]');
}

get productNotificationListItemLinks() {
return this.productNotificationListItems.find('a[data-testing-id="product-name-link"]');
}

updateProductNotificationByProductName(productName: string, price: number, email: string) {
this.productNotificationsArray
.find('a')
.contains(productName)
.closest('[data-testing-id="product-notification-list-item"]')
.find('[data-testing-id="product-notification-edit"]')
.click();
cy.get('[data-testing-id="priceValue"]').clear().type(price.toString());
cy.get('[data-testing-id="email"]').clear().type(email);
cy.get('[data-testing-id="product-notification-edit-dialog-edit"]').click();
}

deleteProductNotificationByProductName(productName: string) {
this.productNotificationsArray
.find('a')
.contains(productName)
.closest('[data-testing-id="product-notification-list-item"]')
.find('[data-testing-id="product-notification-delete"]')
.click();
cy.get('[data-testing-id="confirm"]').click();
}
}
10 changes: 10 additions & 0 deletions e2e/cypress/e2e/pages/shopping/product-detail.page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Interception } from 'cypress/types/net-stubbing';
import { performAddToCart, waitLoadingEnd } from '../../framework';
import { AddToOrderTemplateModule } from '../account/add-to-order-template.module';
import { AddToWishlistModule } from '../account/add-to-wishlist.module';
import { EditProductNotificationModule } from '../account/edit-product-notification.module';
import { BreadcrumbModule } from '../breadcrumb.module';
import { HeaderModule } from '../header.module';
import { MetaDataModule } from '../meta-data.module';
Expand All @@ -23,6 +24,7 @@ export class ProductDetailPage {

readonly addToWishlist = new AddToWishlistModule();
readonly addToOrderTemplate = new AddToOrderTemplateModule();
readonly editProductNotificationModule = new EditProductNotificationModule();

reviewTab = new ProductReviewModule();

Expand All @@ -48,6 +50,10 @@ export class ProductDetailPage {

private addToQuoteRequestButton = () => cy.get('ish-product-detail').find('[data-testing-id="addToQuoteButton"]');

private editProductNotificationButton() {
return cy.get('ish-product-detail').find('[data-testing-id="product-notification-edit"]');
}

private quantityInput = () => cy.get('ish-product-detail').find('[data-testing-id="quantity"]');

isComplete() {
Expand Down Expand Up @@ -84,6 +90,10 @@ export class ProductDetailPage {
this.addToWishlistButton().click();
}

editProductNotification() {
this.editProductNotificationButton().click();
}

setQuantity(quantity: number) {
this.quantityInput().clear();
this.quantityInput().type(quantity.toString());
Expand Down
109 changes: 109 additions & 0 deletions e2e/cypress/e2e/specs/extras/price-notifications.b2c.e2e-spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import { at, waitLoadingEnd } from '../../framework';
import { createUserViaREST } from '../../framework/users';
import { LoginPage } from '../../pages/account/login.page';
import { MyAccountPage } from '../../pages/account/my-account.page';
import { ProductNotificationsOverviewPage } from '../../pages/account/product-notifications-overview.page';
import { sensibleDefaults } from '../../pages/account/registration.page';
import { CategoryPage } from '../../pages/shopping/category.page';
import { FamilyPage } from '../../pages/shopping/family.page';
import { ProductDetailPage } from '../../pages/shopping/product-detail.page';

describe('Product Notification MyAccount Functionality', () => {
const _ = {
user: {
login: `test${new Date().getTime()}@testcity.de`,
...sensibleDefaults,
},
category: 'Home-Entertainment',
subcategory: 'Home-Entertainment.SmartHome',
product1: {
sku: '201807171',
name: 'Google Home',
},
product2: {
sku: '201807191',
name: 'Philips Hue bridge',
},
email1: 'patricia@test.intershop.de',
email2: 'test@test.intershop.de',
};

before(() => {
createUserViaREST(_.user);
LoginPage.navigateTo('/account/notifications');
at(LoginPage, page => {
page.fillForm(_.user.login, _.user.password);
page.submit().its('response.statusCode').should('equal', 200);
waitLoadingEnd();
});
at(ProductNotificationsOverviewPage);
});

it('user creates two product price notifications', () => {
at(ProductNotificationsOverviewPage, page => {
page.header.gotoCategoryPage(_.category);
});

at(CategoryPage, page => page.gotoSubCategory(_.subcategory));
at(FamilyPage, page => page.productList.gotoProductDetailPageBySku(_.product1.sku));
at(ProductDetailPage, page => {
page.editProductNotification();
page.editProductNotificationModule.editPriceNotification(150, _.email1);
page.header.goToMyAccount();
});

at(MyAccountPage, page => {
page.navigateToProductNotifications();
});

at(ProductNotificationsOverviewPage, page => {
page.breadcrumb.items.should('have.length', 3);
page.productNotificationNameArray.should('contain', _.product1.name);
page.productNotificationMessage.should('contain', '150');
page.productNotificationMessage.should('contain', _.email1);
});

at(ProductNotificationsOverviewPage, page => {
page.header.gotoCategoryPage(_.category);
});

at(CategoryPage, page => page.gotoSubCategory(_.subcategory));
at(FamilyPage, page => page.productList.gotoProductDetailPageBySku(_.product2.sku));
at(ProductDetailPage, page => {
page.editProductNotification();
page.editProductNotificationModule.editPriceNotification(50, _.email1);
page.header.goToMyAccount();
});

at(MyAccountPage, page => {
page.navigateToProductNotifications();
});

at(ProductNotificationsOverviewPage, page => {
page.productNotificationMessage.should('contain', '50');
page.productNotificationListItemLinks.should('have.length', 2);
});
});

it('user updates a product notification', () => {
at(ProductNotificationsOverviewPage, page => {
page.updateProductNotificationByProductName(_.product1.name, 130, _.email2);

page.productNotificationMessage.should('contain', '130');
page.productNotificationMessage.should('contain', _.email2);
});
});

it('user deletes one notification', () => {
at(ProductNotificationsOverviewPage, page => {
page.productNotificationsArray.then($listItems => {
const initLen = $listItems.length;

page.deleteProductNotificationByProductName(_.product1.name);

page.productNotificationsArray.should('have.length', initLen - 1);
page.productNotificationNameArray.should('not.contain', _.product1.sku);
});
});
});
});
10 changes: 10 additions & 0 deletions src/app/core/facades/product-context.facade.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ describe('Product Context Facade', () => {
{
"addToBasket": false,
"addToCompare": true,
"addToNotification": true,
"addToOrderTemplate": false,
"addToQuote": false,
"addToWishlist": true,
Expand Down Expand Up @@ -328,6 +329,7 @@ describe('Product Context Facade', () => {
{
"addToBasket": true,
"addToCompare": true,
"addToNotification": true,
"addToOrderTemplate": true,
"addToQuote": true,
"addToWishlist": true,
Expand Down Expand Up @@ -357,6 +359,7 @@ describe('Product Context Facade', () => {
{
"addToBasket": true,
"addToCompare": true,
"addToNotification": true,
"addToOrderTemplate": true,
"addToQuote": true,
"addToWishlist": true,
Expand Down Expand Up @@ -527,6 +530,7 @@ describe('Product Context Facade', () => {
{
"addToBasket": true,
"addToCompare": true,
"addToNotification": false,
"addToOrderTemplate": true,
"addToQuote": true,
"addToWishlist": true,
Expand Down Expand Up @@ -597,6 +601,7 @@ describe('Product Context Facade', () => {
{
"addToBasket": true,
"addToCompare": true,
"addToNotification": true,
"addToOrderTemplate": true,
"addToQuote": true,
"addToWishlist": true,
Expand Down Expand Up @@ -641,6 +646,7 @@ describe('Product Context Facade', () => {
{
"addToBasket": true,
"addToCompare": true,
"addToNotification": true,
"addToOrderTemplate": true,
"addToQuote": true,
"addToWishlist": true,
Expand Down Expand Up @@ -683,6 +689,7 @@ describe('Product Context Facade', () => {
{
"addToBasket": false,
"addToCompare": false,
"addToNotification": false,
"addToOrderTemplate": false,
"addToQuote": false,
"addToWishlist": false,
Expand Down Expand Up @@ -794,6 +801,7 @@ describe('Product Context Facade', () => {
{
"addToBasket": true,
"addToCompare": true,
"addToNotification": true,
"addToOrderTemplate": true,
"addToQuote": true,
"addToWishlist": true,
Expand Down Expand Up @@ -824,6 +832,7 @@ describe('Product Context Facade', () => {
{
"addToBasket": false,
"addToCompare": false,
"addToNotification": true,
"addToOrderTemplate": false,
"addToQuote": false,
"addToWishlist": false,
Expand Down Expand Up @@ -859,6 +868,7 @@ describe('Product Context Facade', () => {
{
"addToBasket": false,
"addToCompare": false,
"addToNotification": true,
"addToOrderTemplate": false,
"addToQuote": false,
"addToWishlist": false,
Expand Down
2 changes: 2 additions & 0 deletions src/app/core/facades/product-context.facade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export interface ProductContextDisplayProperties<T = boolean> {
addToOrderTemplate: T;
addToCompare: T;
addToQuote: T;
addToNotification: T;
}

const defaultDisplayProperties: ProductContextDisplayProperties<true | undefined> = {
Expand All @@ -72,6 +73,7 @@ const defaultDisplayProperties: ProductContextDisplayProperties<true | undefined
addToOrderTemplate: true,
addToCompare: true,
addToQuote: true,
addToNotification: true,
};

export interface ExternalDisplayPropertiesProvider {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export class ProductContextDisplayPropertiesService implements ExternalDisplayPr
addToOrderTemplate: canBeOrdered,
addToCompare: !ProductHelper.isMasterProduct(product),
addToQuote: canBeOrdered,
addToNotification: !ProductHelper.isRetailSet(product) && !ProductHelper.isMasterProduct(product),
};

return (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
**/lazy*
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { NgModule } from '@angular/core';

import { FeatureToggleModule } from 'ish-core/feature-toggle.module';
import { LAZY_FEATURE_MODULE } from 'ish-core/utils/module-loader/module-loader.service';

import { LazyProductNotificationEditComponent } from './lazy-product-notification-edit/lazy-product-notification-edit.component';

@NgModule({
imports: [FeatureToggleModule],
providers: [
{
provide: LAZY_FEATURE_MODULE,
useValue: {
feature: 'productNotifications',
location: () =>
import('../store/product-notifications-store.module').then(m => m.ProductNotificationsStoreModule),
},
multi: true,
},
],
declarations: [LazyProductNotificationEditComponent],
exports: [LazyProductNotificationEditComponent],
})
export class ProductNotificationsExportsModule {}
Loading

0 comments on commit c07edca

Please sign in to comment.