diff --git a/.eslintrc.json b/.eslintrc.json index de13d0dadbf..308ab98249a 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -114,6 +114,12 @@ "@nx/workspace/use-provide-default-feature-toggles": "error", "@nx/workspace/use-provide-default-feature-toggles-factory": "error" } + }, + { + "files": ["*.action*.ts"], + "rules": { + "@nx/workspace/no-ngrx-fail-action-without-error-action-implementation": "error" + } } ] } diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4910fb35ca4..86e8d8b95cd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -216,8 +216,32 @@ jobs: BUILD_NUMBER: ci-build-number-${{ github.event.pull_request.head.sha || github.run_id }} run: | ci-scripts/e2e-cypress.sh -s b2b + ssr_tests: + needs: [no_retries, validate_e2e_execution] + name: SSR Tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Setup node + uses: actions/setup-node@v2 + with: + node-version: ${{ env.NODE_VERSION }} + - name: Cache node_modules + id: cache-node-modules + uses: actions/cache@v2 + with: + path: | + node_modules + key: nodemodules-${{ github.event.pull_request.base.sha }} + restore-keys: nodemodules-${{ github.event.pull_request.base.sha }} + - name: Package installation + run: npm ci + - name: Build SSR Server + run: npm run build:libs && npm run build && npm run build:ssr:local-http-backend + - name: Run SSR tests + run: npm run test:ssr:ci --verbose build_conclusion: - needs: [no_retries, unit_tests, linting, b2c_e2e_tests, b2c_ssr_e2e_tests, b2b_e2e_tests, sonarqube_scan] + needs: [no_retries, unit_tests, linting, b2c_e2e_tests, b2c_ssr_e2e_tests, b2b_e2e_tests, ssr_tests, sonarqube_scan] name: Build Conclusion runs-on: ubuntu-latest if: ${{ always() }} @@ -234,7 +258,8 @@ jobs: needs.linting.result == 'failure' || needs.linting.result == 'cancelled' || needs.b2c_e2e_tests.result == 'failure' || needs.b2c_e2e_tests.result == 'cancelled' || needs.b2c_ssr_e2e_tests.result == 'failure' || needs.b2c_ssr_e2e_tests.result == 'cancelled' || - needs.b2b_e2e_tests.result == 'failure' || needs.b2b_e2e_tests.result == 'cancelled' + needs.b2b_e2e_tests.result == 'failure' || needs.b2b_e2e_tests.result == 'cancelled' || + needs.ssr_tests.result == 'failure' || needs.ssr_tests.result == 'cancelled' send_slack_message: needs: build_conclusion name: Slack message for failed CI build in Spartacus diff --git a/core-libs/setup/ssr/engine/__snapshots__/cx-common-engine.spec.ts.snap b/core-libs/setup/ssr/engine/__snapshots__/cx-common-engine.spec.ts.snap new file mode 100644 index 00000000000..36a99e199f2 --- /dev/null +++ b/core-libs/setup/ssr/engine/__snapshots__/cx-common-engine.spec.ts.snap @@ -0,0 +1,12 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`CxCommonEngine should handle APP_INITIALIZER errors the standard Angular way and throw if any occurred 1`] = ` +"R3InjectorError(TokenServerModule)[InjectionToken SOME_TOKEN -> InjectionToken SOME_TOKEN]: + NullInjectorError: No provider for InjectionToken SOME_TOKEN!" +`; + +exports[`CxCommonEngine should handle errors propagated from SSR 1`] = `"test error"`; + +exports[`CxCommonEngine should not override providers passed to options 1`] = `"message:test"`; + +exports[`CxCommonEngine should return html if no errors 1`] = `"some template"`; diff --git a/core-libs/setup/ssr/engine/cx-common-engine.spec.ts b/core-libs/setup/ssr/engine/cx-common-engine.spec.ts new file mode 100644 index 00000000000..4a37596fe60 --- /dev/null +++ b/core-libs/setup/ssr/engine/cx-common-engine.spec.ts @@ -0,0 +1,130 @@ +// eslint-disable-next-line import/no-unassigned-import +import '@angular/compiler'; + +import { Component, InjectionToken, NgModule, inject } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { ServerModule } from '@angular/platform-server'; +import { PROPAGATE_ERROR_TO_SERVER } from '../error-handling/error-response/propagate-error-to-server'; +import { CxCommonEngine } from './cx-common-engine'; + +// Test how the CxCommonEngine handles successful server-side rendering +@Component({ selector: 'cx-mock', template: 'some template' }) +export class SuccessComponent {} + +@NgModule({ + imports: [BrowserModule, ServerModule], + declarations: [SuccessComponent], + bootstrap: [SuccessComponent], +}) +export class SuccessServerModule {} + +// Test how the CxCommonEngine handles propagated error +@Component({ + selector: 'cx-response', + template: ``, +}) +export class WithPropagatedErrorComponent { + constructor() { + inject(PROPAGATE_ERROR_TO_SERVER)(new Error('test error')); + } +} + +@NgModule({ + imports: [BrowserModule, ServerModule], + declarations: [WithPropagatedErrorComponent], + bootstrap: [WithPropagatedErrorComponent], +}) +export class WithPropagatedErrorServerModule {} + +// Test that the CxCommonEngine doesn't override providers +// If SOME_TOKEN not provided, test how the CxCommonEngine handles APP_INITIALIZER errors +export const SOME_TOKEN = new InjectionToken('SOME_TOKEN'); + +@Component({ + selector: 'cx-token', + template: `message:{{ someToken }}`, +}) +export class TokenComponent { + someToken = inject(SOME_TOKEN); +} + +@NgModule({ + imports: [BrowserModule, ServerModule], + declarations: [TokenComponent], + bootstrap: [TokenComponent], +}) +export class TokenServerModule {} + +describe('CxCommonEngine', () => { + let engine: CxCommonEngine; + + beforeAll(() => { + jest.spyOn(console, 'error').mockImplementation(); + jest.spyOn(console, 'log').mockImplementation(); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should return html if no errors', async () => { + engine = new CxCommonEngine({ + bootstrap: SuccessServerModule, + }); + + const html = await engine.render({ + url: 'http://localhost:4200', + document: '', + }); + + // Cannot use `.toMatchInlineSnapshot()` due to bug in jest: + // see: https://github.com/thymikee/jest-preset-angular/issues/1084 + expect(html).toMatchSnapshot(); + }); + + it('should not override providers passed to options', async () => { + engine = new CxCommonEngine({ + bootstrap: TokenServerModule, + }); + + const html = await engine.render({ + url: 'http://localhost:4200', + document: '', + providers: [{ provide: SOME_TOKEN, useValue: 'test' }], + }); + + // Cannot use `.toMatchInlineSnapshot()` due to bug in jest: + // see: https://github.com/thymikee/jest-preset-angular/issues/1084 + expect(html).toMatchSnapshot(); + }); + + it('should handle APP_INITIALIZER errors the standard Angular way and throw if any occurred', async () => { + engine = new CxCommonEngine({ + bootstrap: TokenServerModule, + }); + + // Cannot use `.toMatchInlineSnapshot()` due to bug in jest: + // see: https://github.com/thymikee/jest-preset-angular/issues/1084 + await expect( + engine.render({ + url: 'http://localhost:4200', + document: '', + }) + ).rejects.toThrowErrorMatchingSnapshot(); + }); + + it('should handle errors propagated from SSR', async () => { + engine = new CxCommonEngine({ + bootstrap: WithPropagatedErrorServerModule, + }); + + // Cannot use `.toMatchInlineSnapshot()` due to bug in jest: + // see: https://github.com/thymikee/jest-preset-angular/issues/1084 + await expect( + engine.render({ + url: 'http://localhost:4200', + document: '', + }) + ).rejects.toThrowErrorMatchingSnapshot(); + }); +}); diff --git a/core-libs/setup/ssr/engine/cx-common-engine.ts b/core-libs/setup/ssr/engine/cx-common-engine.ts new file mode 100644 index 00000000000..6abbcd08a41 --- /dev/null +++ b/core-libs/setup/ssr/engine/cx-common-engine.ts @@ -0,0 +1,67 @@ +/* + * SPDX-FileCopyrightText: 2024 SAP Spartacus team + * + * SPDX-License-Identifier: Apache-2.0 + */ + +import { + CommonEngine, + CommonEngineOptions, + CommonEngineRenderOptions, +} from '@angular/ssr'; +import { PROPAGATE_ERROR_TO_SERVER } from '../error-handling/error-response/propagate-error-to-server'; + +/** + * The Spartacus extension of the Angular's `CommonEngine`. It is able to handle the propagated server responses caught during server-side rendering of a Spartacus app. + * For reference, see Angular's source code: https://github.com/angular/angular-cli/blob/6cf866225ab09f8b4b3803c000b632bed8448ce4/packages/angular/ssr/src/common-engine.ts#L56 + * + * @extends {CommonEngine} + */ +export class CxCommonEngine extends CommonEngine { + constructor(options?: CommonEngineOptions) { + super(options); + } + + /** + * @override + * Renders for the given options. + * If an error is populated from the rendered applications + * (via `PROPAGATE_ERROR_TO_SERVER` callback), then such an error + * will be thrown and the result promise rejected - but only AFTER the rendering is complete. + * In other words, at first an error occurs and it's captured, then we wait until the rendering completes + * and ONLY then we reject the promise with the payload being the encountered error. + * + * Note: if more errors are captured during the rendering, only the first one will be used + * as the payload of the rejected promise, others won't. + * + * @param {CommonEngineRenderOptions} options - The options to render. + * @returns {Promise} Promise which resolves with the rendered HTML as a string + * OR rejects with the error, if any is propagated from the rendered app. + */ + override async render(options: CommonEngineRenderOptions): Promise { + let error: undefined | unknown; + + return super + .render({ + ...options, + providers: [ + { + provide: PROPAGATE_ERROR_TO_SERVER, + useFactory: () => { + return (propagatedError: unknown) => { + // We're interested only the first propagated error, so we use `??=` instead of `=`: + error ??= propagatedError; + }; + }, + }, + ...(options.providers ?? []), + ], + }) + .then((html: string) => { + if (error) { + throw error; + } + return html; + }); + } +} diff --git a/core-libs/setup/ssr/engine/ng-express-engine.spec.ts b/core-libs/setup/ssr/engine/ng-express-engine.spec.ts index 9f2cb0ef33c..6871dd25eea 100644 --- a/core-libs/setup/ssr/engine/ng-express-engine.spec.ts +++ b/core-libs/setup/ssr/engine/ng-express-engine.spec.ts @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2023 SAP Spartacus team + * SPDX-FileCopyrightText: 2024 SAP Spartacus team * * SPDX-License-Identifier: Apache-2.0 */ diff --git a/core-libs/setup/ssr/engine/ng-express-engine.ts b/core-libs/setup/ssr/engine/ng-express-engine.ts index 826ea5c45ed..45b6c669dfd 100644 --- a/core-libs/setup/ssr/engine/ng-express-engine.ts +++ b/core-libs/setup/ssr/engine/ng-express-engine.ts @@ -5,13 +5,10 @@ */ import { StaticProvider } from '@angular/core'; -import { - CommonEngine, - CommonEngineOptions, - CommonEngineRenderOptions, -} from '@angular/ssr'; +import { CommonEngineOptions, CommonEngineRenderOptions } from '@angular/ssr'; import { Request, Response } from 'express'; import { REQUEST, RESPONSE } from '../tokens/express.tokens'; +import { CxCommonEngine } from './cx-common-engine'; /** * @license @@ -80,7 +77,7 @@ export interface RenderOptions extends CommonEngineRenderOptions { * - https://github.com/angular/universal/blob/e798d256de5e4377b704e63d993dc56ea35df97d/modules/express-engine/src/main.ts */ export function ngExpressEngine(setupOptions: NgSetupOptions) { - const engine = new CommonEngine({ + const engine = new CxCommonEngine({ bootstrap: setupOptions.bootstrap, providers: setupOptions.providers, enablePerformanceProfiler: setupOptions.enablePerformanceProfiler, diff --git a/core-libs/setup/ssr/error-handling/error-response/index.ts b/core-libs/setup/ssr/error-handling/error-response/index.ts new file mode 100644 index 00000000000..734d871cfb3 --- /dev/null +++ b/core-libs/setup/ssr/error-handling/error-response/index.ts @@ -0,0 +1,7 @@ +/* + * SPDX-FileCopyrightText: 2024 SAP Spartacus team + * + * SPDX-License-Identifier: Apache-2.0 + */ + +export * from './propagate-error-to-server'; diff --git a/core-libs/setup/ssr/error-handling/error-response/propagate-error-to-server.ts b/core-libs/setup/ssr/error-handling/error-response/propagate-error-to-server.ts new file mode 100644 index 00000000000..391a651b4be --- /dev/null +++ b/core-libs/setup/ssr/error-handling/error-response/propagate-error-to-server.ts @@ -0,0 +1,22 @@ +/* + * SPDX-FileCopyrightText: 2024 SAP Spartacus team + * + * SPDX-License-Identifier: Apache-2.0 + */ + +import { InjectionToken } from '@angular/core'; + +/** + * Propagates the given error object to the higher layer in the server. + * + * It's meant to propagate errors for example to ExpressJS layer when using SSR + * or to a Prerendering Worker when using Server Prerendering. + * Currently, it's provided OOTB only in SSR (not prerendering), in the `CxCommonEngine` class. + * + * Note: We need it until Angular implements a proper propagation of async errors + * from an app to the the higher layer in the server. + * For more, see the Angular issue https://github.com/angular/angular/issues/33642 + */ +export const PROPAGATE_ERROR_TO_SERVER = new InjectionToken< + (error: unknown) => void +>('PROPAGATE_ERROR_RESPONSE'); diff --git a/core-libs/setup/ssr/error-handling/express-error-handlers/express-error-handlers.spec.ts b/core-libs/setup/ssr/error-handling/express-error-handlers/express-error-handlers.spec.ts new file mode 100644 index 00000000000..898dcc39fa9 --- /dev/null +++ b/core-libs/setup/ssr/error-handling/express-error-handlers/express-error-handlers.spec.ts @@ -0,0 +1,56 @@ +import { HttpErrorResponse } from '@angular/common/http'; +import { CmsPageNotFoundOutboundHttpError } from '@spartacus/core'; +import { defaultExpressErrorHandlers } from './express-error-handlers'; + +describe('expressErrorHandlers', () => { + let documentContent: string; + let req: any; + let res: any; + let next: any; + + beforeEach(() => { + documentContent = 'some document content'; + req = {}; + res = { + set: jest.fn(), + status: jest.fn().mockReturnThis(), + send: jest.fn(), + }; + }); + + it('should do nothing if headers are already sent', () => { + const err = new HttpErrorResponse({ + error: 'Page not found', + }); + const errorRequestHandler = defaultExpressErrorHandlers(documentContent); + res.headersSent = true; + + errorRequestHandler(err, req, res, next); + + expect(res.set).not.toHaveBeenCalled(); + expect(res.status).not.toHaveBeenCalled(); + expect(res.send).not.toHaveBeenCalled(); + }); + + it('should handle CmsPageNotFoundOutboundHttpError', () => { + const err = new CmsPageNotFoundOutboundHttpError('Page not found'); + const errorRequestHandler = defaultExpressErrorHandlers(documentContent); + + errorRequestHandler(err, req, res, next); + + expect(res.set).toHaveBeenCalledWith('Cache-Control', 'no-store'); + expect(res.status).toHaveBeenCalledWith(404); + expect(res.send).toHaveBeenCalledWith(documentContent); + }); + + it('should handle unknown error', () => { + const err = new Error('unknown error'); + const errorRequestHandler = defaultExpressErrorHandlers(documentContent); + + errorRequestHandler(err, req, res, next); + + expect(res.set).toHaveBeenCalledWith('Cache-Control', 'no-store'); + expect(res.status).toHaveBeenCalledWith(500); + expect(res.send).toHaveBeenCalledWith(documentContent); + }); +}); diff --git a/core-libs/setup/ssr/error-handling/express-error-handlers/express-error-handlers.ts b/core-libs/setup/ssr/error-handling/express-error-handlers/express-error-handlers.ts new file mode 100644 index 00000000000..42aba5c9d49 --- /dev/null +++ b/core-libs/setup/ssr/error-handling/express-error-handlers/express-error-handlers.ts @@ -0,0 +1,32 @@ +/* + * SPDX-FileCopyrightText: 2024 SAP Spartacus team + * + * SPDX-License-Identifier: Apache-2.0 + */ + +import { + CmsPageNotFoundOutboundHttpError, + HttpResponseStatus, +} from '@spartacus/core'; +import { ErrorRequestHandler } from 'express'; + +/** + * Returns default handlers which results in a fallback to client side rendering. + * - If cms page not found, the document content is sent to the client with the appropriate 404 status code. + * - For rest of errors, the document content is sent to the client with the appropriate status 500 code. + * + * @param documentContent The document content to be sent to the client. + * @returns The error request handler. + */ +export const defaultExpressErrorHandlers = + (documentContent: string): ErrorRequestHandler => + (err, _req, res, _next) => { + if (!res.headersSent) { + res.set('Cache-Control', 'no-store'); + const statusCode = + err instanceof CmsPageNotFoundOutboundHttpError + ? HttpResponseStatus.NOT_FOUND + : HttpResponseStatus.INTERNAL_SERVER_ERROR; + res.status(statusCode).send(documentContent); + } + }; diff --git a/core-libs/setup/ssr/error-handling/express-error-handlers/index.ts b/core-libs/setup/ssr/error-handling/express-error-handlers/index.ts new file mode 100644 index 00000000000..00fe9d6251f --- /dev/null +++ b/core-libs/setup/ssr/error-handling/express-error-handlers/index.ts @@ -0,0 +1,7 @@ +/* + * SPDX-FileCopyrightText: 2024 SAP Spartacus team + * + * SPDX-License-Identifier: Apache-2.0 + */ + +export * from './express-error-handlers'; diff --git a/core-libs/setup/ssr/error-handling/index.ts b/core-libs/setup/ssr/error-handling/index.ts new file mode 100644 index 00000000000..c96a4b4acde --- /dev/null +++ b/core-libs/setup/ssr/error-handling/index.ts @@ -0,0 +1,9 @@ +/* + * SPDX-FileCopyrightText: 2024 SAP Spartacus team + * + * SPDX-License-Identifier: Apache-2.0 + */ + +export * from './error-response/index'; +export * from './express-error-handlers/index'; +export * from './multi-error-handlers/index'; diff --git a/core-libs/setup/ssr/error-handling/multi-error-handlers/index.ts b/core-libs/setup/ssr/error-handling/multi-error-handlers/index.ts new file mode 100644 index 00000000000..d1c6353b9e5 --- /dev/null +++ b/core-libs/setup/ssr/error-handling/multi-error-handlers/index.ts @@ -0,0 +1,7 @@ +/* + * SPDX-FileCopyrightText: 2024 SAP Spartacus team + * + * SPDX-License-Identifier: Apache-2.0 + */ + +export * from './propagating-to-server-error-handler'; diff --git a/core-libs/setup/ssr/error-handling/multi-error-handlers/propagating-to-server-error-handler.spec.ts b/core-libs/setup/ssr/error-handling/multi-error-handlers/propagating-to-server-error-handler.spec.ts new file mode 100644 index 00000000000..fa7e7358767 --- /dev/null +++ b/core-libs/setup/ssr/error-handling/multi-error-handlers/propagating-to-server-error-handler.spec.ts @@ -0,0 +1,54 @@ +import { TestBed } from '@angular/core/testing'; +import { FeatureConfigService } from '@spartacus/core'; +import { PROPAGATE_ERROR_TO_SERVER } from '../error-response/propagate-error-to-server'; +import { PropagatingToServerErrorHandler } from './propagating-to-server-error-handler'; + +describe('PropagatingToServerErrorHandler', () => { + let propagatingToServerErrorHandler: PropagatingToServerErrorHandler; + let featureConfigService: FeatureConfigService; + let propagateErrorResponse: any; + + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [ + PropagatingToServerErrorHandler, + FeatureConfigService, + { + provide: PROPAGATE_ERROR_TO_SERVER, + useValue: jest.fn(), + }, + ], + }); + propagatingToServerErrorHandler = TestBed.inject( + PropagatingToServerErrorHandler + ); + propagateErrorResponse = TestBed.inject(PROPAGATE_ERROR_TO_SERVER); + featureConfigService = TestBed.inject(FeatureConfigService); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should propagate error when propagateErrorsToServer is enabled', () => { + jest + .spyOn(featureConfigService, 'isEnabled') + .mockImplementationOnce((val) => val === 'propagateErrorsToServer'); + const error = new Error('test error'); + + propagatingToServerErrorHandler.handleError(error); + + expect(propagateErrorResponse as jest.Mock).toHaveBeenCalledWith(error); + }); + + it('should not propagate error when propagateErrorsToServer is disabled', () => { + jest + .spyOn(featureConfigService, 'isEnabled') + .mockImplementationOnce((val) => !(val === 'propagateErrorsToServer')); + const error = new Error('test error'); + + propagatingToServerErrorHandler.handleError(error); + + expect(propagateErrorResponse as jest.Mock).not.toHaveBeenCalled(); + }); +}); diff --git a/core-libs/setup/ssr/error-handling/multi-error-handlers/propagating-to-server-error-handler.ts b/core-libs/setup/ssr/error-handling/multi-error-handlers/propagating-to-server-error-handler.ts new file mode 100644 index 00000000000..68a65aa0e96 --- /dev/null +++ b/core-libs/setup/ssr/error-handling/multi-error-handlers/propagating-to-server-error-handler.ts @@ -0,0 +1,40 @@ +/* + * SPDX-FileCopyrightText: 2024 SAP Spartacus team + * + * SPDX-License-Identifier: Apache-2.0 + */ + +import { Injectable, inject } from '@angular/core'; +import { FeatureConfigService, MultiErrorHandler } from '@spartacus/core'; +import { PROPAGATE_ERROR_TO_SERVER } from '../error-response/propagate-error-to-server'; + +/** + * Propagates the given error object to the higher layer in the server. + * + * It's meant to propagate errors for example to ExpressJS layer when using SSR + * or to a Prerendering Worker when using Server Prerendering. + * Currently, it's provided OOTB only in SSR (not prerendering), in the `CxCommonEngine` class. + * + * Note: We need it until Angular implements a proper propagation of async errors + * from an app to the the higher layer in the server. + * For more, see the Angular issue https://github.com/angular/angular/issues/33642 + * + * Intended to be used as part of a multi-error handler strategy. + * + * @see MultiErrorHandler + */ +@Injectable({ + providedIn: 'root', +}) +export class PropagatingToServerErrorHandler implements MultiErrorHandler { + protected propagateErrorToServer = inject(PROPAGATE_ERROR_TO_SERVER); + private featureConfigService: FeatureConfigService = + inject(FeatureConfigService); + + handleError(error: unknown): void { + if (!this.featureConfigService.isEnabled('propagateErrorsToServer')) { + return; + } + this.propagateErrorToServer(error); + } +} diff --git a/core-libs/setup/ssr/optimized-engine/index.ts b/core-libs/setup/ssr/optimized-engine/index.ts index 18afefbeb11..cfa41066a42 100644 --- a/core-libs/setup/ssr/optimized-engine/index.ts +++ b/core-libs/setup/ssr/optimized-engine/index.ts @@ -6,6 +6,7 @@ export * from './optimized-ssr-engine'; export * from './rendering-cache'; +export * from './rendering-cache.model'; export * from './rendering-strategy-resolver'; export * from './rendering-strategy-resolver-options'; export { RequestContext, getRequestContext } from './request-context'; diff --git a/core-libs/setup/ssr/optimized-engine/optimized-ssr-engine.spec.ts b/core-libs/setup/ssr/optimized-engine/optimized-ssr-engine.spec.ts index ce3b478b3e1..1ea94319538 100644 --- a/core-libs/setup/ssr/optimized-engine/optimized-ssr-engine.spec.ts +++ b/core-libs/setup/ssr/optimized-engine/optimized-ssr-engine.spec.ts @@ -20,6 +20,7 @@ jest.mock('fs', () => ({ readFileSync: () => '', })); const consoleLogSpy = jest.spyOn(console, 'log').mockImplementation(); +const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(); class MockExpressServerLogger implements Partial { log(message: string, context: ExpressServerLoggerContext): void { @@ -39,7 +40,7 @@ class MockExpressServerLogger implements Partial { */ class TestEngineRunner { /** Accumulates html output for engine runs */ - renders: string[] = []; + renders: (string | Error)[] = []; /** Accumulates response parameters for engine runs */ responseParams: object[] = []; @@ -48,7 +49,11 @@ class TestEngineRunner { optimizedSsrEngine: OptimizedSsrEngine; engineInstance: NgExpressEngineInstance; - constructor(options: SsrOptimizationOptions, renderTime?: number) { + constructor( + options: SsrOptimizationOptions, + renderTime?: number, + params?: { withError?: boolean } + ) { // mocked engine instance that will render test output in 100 milliseconds const engineInstanceMock = ( filePath: string, @@ -56,17 +61,31 @@ class TestEngineRunner { callback: SsrCallbackFn ) => { setTimeout(() => { - callback(undefined, `${filePath}-${this.renderCount++}`); + const result = `${filePath}-${this.renderCount++}`; + + if (params?.withError) { + const err = new Error(result); + callback(err, undefined); + } else { + callback(undefined, result); + } }, renderTime ?? defaultRenderTime); }; - this.optimizedSsrEngine = new OptimizedSsrEngine( - engineInstanceMock, - options - ); + this.optimizedSsrEngine = new OptimizedSsrEngine(engineInstanceMock, { + ...options, + }); this.engineInstance = this.optimizedSsrEngine.engineInstance; } + /** Create engine that results with error during render */ + static withError( + options: SsrOptimizationOptions, + renderTime = defaultRenderTime + ): TestEngineRunner { + return new TestEngineRunner(options, renderTime, { withError: true }); + } + /** Run request against the engine. The result will be stored in rendering property. */ request( url: string, @@ -102,8 +121,8 @@ class TestEngineRunner { }, }; - this.engineInstance(url, optionsMock, (_, html): void => { - this.renders.push(html ?? ''); + this.engineInstance(url, optionsMock, (error, html): void => { + this.renders.push(html ?? error ?? ''); this.responseParams.push(response); }); @@ -176,7 +195,11 @@ describe('OptimizedSsrEngine', () => { "maxRenderTime": 300000, "reuseCurrentRendering": true, "renderingStrategyResolver": "() => ssr_optimization_options_1.RenderingStrategy.ALWAYS_SSR", - "logger": "DefaultExpressServerLogger" + "logger": "DefaultExpressServerLogger", + "shouldCacheRenderingResult": "({ options, entry }) => !(options.ssrFeatureToggles?.avoidCachingErrors === true &&\\n Boolean(entry.err))", + "ssrFeatureToggles": { + "avoidCachingErrors": false + } } } }", @@ -185,6 +208,34 @@ describe('OptimizedSsrEngine', () => { }); }); + describe('rendering', () => { + it('should return rendered output if no errors', fakeAsync(() => { + const originalUrl = 'a'; + const engineRunner = new TestEngineRunner({}).request('a'); + + tick(200); + expect(engineRunner.renders).toEqual(['a-0']); + expect(consoleLogSpy).toHaveBeenCalledWith( + expect.stringContaining( + `Request is resolved with the SSR rendering result (${originalUrl})` + ) + ); + })); + + it('should return error if rendering fails', fakeAsync(() => { + const originalUrl = 'a'; + const engineRunner = TestEngineRunner.withError({}).request('a'); + + tick(200); + expect(engineRunner.renders).toEqual([new Error('a-0')]); + expect(consoleErrorSpy).toHaveBeenCalledWith( + expect.stringContaining( + `Request is resolved with the SSR rendering error (${originalUrl})` + ) + ); + })); + }); + describe('rendering cache', () => { it('should be initialized with default optimization options none of the custom options are provided', () => { const engineRunner = new TestEngineRunner({}); @@ -304,7 +355,6 @@ describe('OptimizedSsrEngine', () => { tick(200); expect(engineRunner.renders).toEqual(['a-0', 'a-1', 'a-2']); })); - it('should cache requests if enabled', fakeAsync(() => { const engineRunner = new TestEngineRunner({ cache: true, @@ -334,6 +384,148 @@ describe('OptimizedSsrEngine', () => { })); }); + describe('avoidCachingErrors option', () => { + describe('when using default shouldCacheRenderingResult', () => { + it('should not cache errors if `avoidCachingErrors` is set to true', fakeAsync(() => { + const engineRunner = TestEngineRunner.withError({ + cache: true, + ssrFeatureToggles: { + avoidCachingErrors: true, + }, + }).request('a'); + + tick(200); + engineRunner.request('a'); + tick(200); + engineRunner.request('a'); + tick(200); + expect(engineRunner.renders).toEqual([ + new Error('a-0'), + new Error('a-1'), + new Error('a-2'), + ]); + })); + + it('should cache errors if `avoidCachingErrors` is set to false', fakeAsync(() => { + const engineRunner = TestEngineRunner.withError({ + cache: true, + ssrFeatureToggles: { + avoidCachingErrors: false, + }, + }).request('a'); + + tick(200); + engineRunner.request('a'); + tick(200); + engineRunner.request('a'); + tick(200); + expect(engineRunner.renders).toEqual([ + new Error('a-0'), + new Error('a-0'), + new Error('a-0'), + ]); + })); + + it('should cache HTML if `avoidCachingErrors` is set to true', fakeAsync(() => { + const engineRunner = new TestEngineRunner({ + cache: true, + ssrFeatureToggles: { + avoidCachingErrors: true, + }, + }).request('a'); + + tick(200); + engineRunner.request('a'); + tick(200); + engineRunner.request('a'); + tick(200); + expect(engineRunner.renders).toEqual(['a-0', 'a-0', 'a-0']); + })); + + it('should cache HTML if `avoidCachingErrors` is set to false', fakeAsync(() => { + const engineRunner = new TestEngineRunner({ + cache: true, + ssrFeatureToggles: { + avoidCachingErrors: true, + }, + }).request('a'); + + tick(200); + engineRunner.request('a'); + tick(200); + engineRunner.request('a'); + tick(200); + expect(engineRunner.renders).toEqual(['a-0', 'a-0', 'a-0']); + })); + }); + }); + + describe('shouldCacheRenderingResult option', () => { + it('should not cache errors if `shouldCacheRenderingResult` returns false', fakeAsync(() => { + const engineRunner = TestEngineRunner.withError({ + cache: true, + shouldCacheRenderingResult: () => false, + }).request('a'); + + tick(200); + engineRunner.request('a'); + tick(200); + engineRunner.request('a'); + tick(200); + expect(engineRunner.renders).toEqual([ + new Error('a-0'), + new Error('a-1'), + new Error('a-2'), + ]); + })); + + it('should cache errors if `shouldCacheRenderingResult` returns true', fakeAsync(() => { + const engineRunner = TestEngineRunner.withError({ + cache: true, + shouldCacheRenderingResult: () => true, + }).request('a'); + + tick(200); + engineRunner.request('a'); + tick(200); + engineRunner.request('a'); + tick(200); + expect(engineRunner.renders).toEqual([ + new Error('a-0'), + new Error('a-0'), + new Error('a-0'), + ]); + })); + + it('should not cache HTML if `shouldCacheRenderingResult` returns false', fakeAsync(() => { + const engineRunner = new TestEngineRunner({ + cache: true, + shouldCacheRenderingResult: () => false, + }).request('a'); + + tick(200); + engineRunner.request('a'); + tick(200); + engineRunner.request('a'); + tick(200); + expect(engineRunner.renders).toEqual(['a-0', 'a-1', 'a-2']); + })); + + it('should cache HTML if `shouldCacheRenderingResult` returns true', fakeAsync(() => { + const engineRunner = new TestEngineRunner({ + cache: true, + shouldCacheRenderingResult: () => true, + }).request('a'); + + tick(200); + engineRunner.request('a'); + tick(200); + engineRunner.request('a'); + tick(200); + expect(engineRunner.renders).toEqual(['a-0', 'a-0', 'a-0']); + })); + }); + describe('concurrency option', () => { it('should limit concurrency and fallback to csr', fakeAsync(() => { const engineRunner = new TestEngineRunner({ @@ -1261,56 +1453,41 @@ describe('OptimizedSsrEngine', () => { it('should use the default server logger, if custom logger is not specified', () => { new TestEngineRunner({}); + expect(consoleLogSpy).toHaveBeenCalled(); + }); + it('should use the provided logger', () => { + new TestEngineRunner({ + logger: new MockExpressServerLogger() as ExpressServerLogger, + }); expect(consoleLogSpy.mock.lastCall).toMatchInlineSnapshot(` [ - "{ - "message": "[spartacus] SSR optimization engine initialized", - "context": { - "timestamp": "2023-01-01T00:00:00.000Z", + "[spartacus] SSR optimization engine initialized", + { "options": { "cacheSize": 3000, "concurrency": 10, - "timeout": 3000, "forcedSsrTimeout": 60000, + "logger": "MockExpressServerLogger", "maxRenderTime": 300000, - "reuseCurrentRendering": true, - "renderingStrategyResolver": "(request) => {\\n if (hasExcludedUrl(request, defaultAlwaysCsrOptions.excludedUrls)) {\\n return ssr_optimization_options_1.RenderingStrategy.ALWAYS_CSR;\\n }\\n return shouldFallbackToCsr(request, options)\\n ? ssr_optimization_options_1.RenderingStrategy.ALWAYS_CSR\\n : ssr_optimization_options_1.RenderingStrategy.DEFAULT;\\n}", - "logger": "DefaultExpressServerLogger" + "renderingStrategyResolver": "(request) => { + if (hasExcludedUrl(request, defaultAlwaysCsrOptions.excludedUrls)) { + return ssr_optimization_options_1.RenderingStrategy.ALWAYS_CSR; } - } + return shouldFallbackToCsr(request, options) + ? ssr_optimization_options_1.RenderingStrategy.ALWAYS_CSR + : ssr_optimization_options_1.RenderingStrategy.DEFAULT; }", + "reuseCurrentRendering": true, + "shouldCacheRenderingResult": "({ options, entry }) => !(options.ssrFeatureToggles?.avoidCachingErrors === true && + Boolean(entry.err))", + "ssrFeatureToggles": { + "avoidCachingErrors": false, + }, + "timeout": 3000, + }, + }, ] `); }); - - it('should use the provided logger', () => { - new TestEngineRunner({ - logger: new MockExpressServerLogger() as ExpressServerLogger, - }); - expect(consoleLogSpy.mock.lastCall).toMatchInlineSnapshot(` - [ - "[spartacus] SSR optimization engine initialized", - { - "options": { - "cacheSize": 3000, - "concurrency": 10, - "forcedSsrTimeout": 60000, - "logger": "MockExpressServerLogger", - "maxRenderTime": 300000, - "renderingStrategyResolver": "(request) => { - if (hasExcludedUrl(request, defaultAlwaysCsrOptions.excludedUrls)) { - return ssr_optimization_options_1.RenderingStrategy.ALWAYS_CSR; - } - return shouldFallbackToCsr(request, options) - ? ssr_optimization_options_1.RenderingStrategy.ALWAYS_CSR - : ssr_optimization_options_1.RenderingStrategy.DEFAULT; - }", - "reuseCurrentRendering": true, - "timeout": 3000, - }, - }, - ] - `); - }); }); }); diff --git a/core-libs/setup/ssr/optimized-engine/optimized-ssr-engine.ts b/core-libs/setup/ssr/optimized-engine/optimized-ssr-engine.ts index f5a61e7e08a..84894d223b2 100644 --- a/core-libs/setup/ssr/optimized-engine/optimized-ssr-engine.ts +++ b/core-libs/setup/ssr/optimized-engine/optimized-ssr-engine.ts @@ -74,6 +74,11 @@ export class OptimizedSsrEngine { ...defaultSsrOptimizationOptions, // overrides the default options ...ssrOptions, + // merge feature toggles + ssrFeatureToggles: { + ...defaultSsrOptimizationOptions.ssrFeatureToggles, + ...ssrOptions.ssrFeatureToggles, + }, } : undefined; @@ -292,11 +297,7 @@ export class OptimizedSsrEngine { clearTimeout(requestTimeout); callback(err, html); - this.log( - `Request is resolved with the SSR rendering result (${request?.originalUrl})`, - true, - { request } - ); + this.logForRenderResult(err, html, request); // store the render only if caching is enabled if (this.ssrOptions?.cache) { @@ -475,4 +476,27 @@ export class OptimizedSsrEngine { renderCallback(err, html); }); } + + /** + * Logs the result of the render. + */ + private logForRenderResult( + err: unknown | undefined, + html: string | undefined, + request: Request + ): void { + if (html) { + this.log( + `Request is resolved with the SSR rendering result (${request?.originalUrl})`, + true, + { request } + ); + } + if (err) { + this.logger.error( + `Request is resolved with the SSR rendering error (${request?.originalUrl})`, + { request, error: err } + ); + } + } } diff --git a/core-libs/setup/ssr/optimized-engine/rendering-cache.model.ts b/core-libs/setup/ssr/optimized-engine/rendering-cache.model.ts new file mode 100644 index 00000000000..d95a53c8f0b --- /dev/null +++ b/core-libs/setup/ssr/optimized-engine/rendering-cache.model.ts @@ -0,0 +1,15 @@ +/* + * SPDX-FileCopyrightText: 2024 SAP Spartacus team + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * Represents a rendering entry in the rendering cache. + */ +export interface RenderingEntry { + html?: any; + err?: any; + time?: number; + rendering?: boolean; +} diff --git a/core-libs/setup/ssr/optimized-engine/rendering-cache.spec.ts b/core-libs/setup/ssr/optimized-engine/rendering-cache.spec.ts index 5cc6e8794e0..81592e76823 100644 --- a/core-libs/setup/ssr/optimized-engine/rendering-cache.spec.ts +++ b/core-libs/setup/ssr/optimized-engine/rendering-cache.spec.ts @@ -1,12 +1,21 @@ /// import { RenderingCache } from './rendering-cache'; +import { + SsrOptimizationOptions, + defaultSsrOptimizationOptions, +} from './ssr-optimization-options'; + +const options: SsrOptimizationOptions = { + shouldCacheRenderingResult: + defaultSsrOptimizationOptions.shouldCacheRenderingResult, +}; describe('RenderingCache', () => { let renderingCache: RenderingCache; beforeEach(() => { - renderingCache = new RenderingCache({}); + renderingCache = new RenderingCache(options); }); it('should create rendering cache instance', () => { @@ -77,13 +86,13 @@ describe('RenderingCache with ttl', () => { let renderingCache: RenderingCache; beforeEach(() => { - renderingCache = new RenderingCache({ ttl: 100 }); + renderingCache = new RenderingCache({ ...options, ttl: 100 }); }); describe('get', () => { it('should return timestamp', () => { renderingCache.store('test', null, 'testHtml'); - expect(renderingCache.get('test').time).toBeTruthy(); + expect(renderingCache.get('test')?.time).toBeTruthy(); }); }); @@ -118,7 +127,7 @@ describe('RenderingCache with cacheSize', () => { let renderingCache: RenderingCache; beforeEach(() => { - renderingCache = new RenderingCache({ cacheSize: 2 }); + renderingCache = new RenderingCache({ ...options, cacheSize: 2 }); }); describe('get', () => { @@ -151,4 +160,75 @@ describe('RenderingCache with cacheSize', () => { expect(renderingCache.get('c')).toBeTruthy(); }); }); + + describe('RenderingCache and shouldCacheRenderingResult', () => { + let renderingCache: RenderingCache; + + describe('if default shouldCacheRenderingResult', () => { + it('should cache HTML if avoidCachingErrors is false', () => { + renderingCache = new RenderingCache({ + ...options, + ssrFeatureToggles: { + avoidCachingErrors: false, + }, + }); + renderingCache.store('a', undefined, 'a'); + expect(renderingCache.get('a')).toEqual({ html: 'a', err: undefined }); + }); + + it('should cache HTML if avoidCachingErrors is true', () => { + renderingCache = new RenderingCache({ + ...options, + ssrFeatureToggles: { + avoidCachingErrors: false, + }, + }); + renderingCache.store('a', undefined, 'a'); + expect(renderingCache.get('a')).toEqual({ html: 'a', err: undefined }); + }); + + it('should cache errors if avoidCachingErrors is false', () => { + renderingCache = new RenderingCache({ + ...options, + ssrFeatureToggles: { + avoidCachingErrors: false, + }, + }); + renderingCache.store('a', new Error('err'), 'a'); + expect(renderingCache.get('a')).toEqual({ + html: 'a', + err: new Error('err'), + }); + }); + + it('should not cache errors if avoidCachingErrors is true', () => { + renderingCache = new RenderingCache({ + ...options, + ssrFeatureToggles: { + avoidCachingErrors: true, + }, + }); + renderingCache.store('a', new Error('err'), 'a'); + expect(renderingCache.get('a')).toBeFalsy(); + }); + }); + + describe('if shouldCacheRenderingResult is not defined', () => { + beforeEach(() => { + renderingCache = new RenderingCache({ + ...options, + shouldCacheRenderingResult: undefined, + }); + }); + it('should not cache a html', () => { + renderingCache.store('a', undefined, 'a'); + expect(renderingCache.get('a')).toBeFalsy(); + }); + + it('should not cache an error', () => { + renderingCache.store('a', new Error('err'), 'a'); + expect(renderingCache.get('a')).toBeFalsy(); + }); + }); + }); }); diff --git a/core-libs/setup/ssr/optimized-engine/rendering-cache.ts b/core-libs/setup/ssr/optimized-engine/rendering-cache.ts index 88833775be2..f7700687fb7 100644 --- a/core-libs/setup/ssr/optimized-engine/rendering-cache.ts +++ b/core-libs/setup/ssr/optimized-engine/rendering-cache.ts @@ -4,15 +4,9 @@ * SPDX-License-Identifier: Apache-2.0 */ +import { RenderingEntry } from './rendering-cache.model'; import { SsrOptimizationOptions } from './ssr-optimization-options'; -export interface RenderingEntry { - html?: any; - err?: any; - time?: number; - rendering?: boolean; -} - export class RenderingCache { protected renders = new Map(); @@ -37,7 +31,15 @@ export class RenderingCache { this.renders.delete(this.renders.keys().next().value); } } - this.renders.set(key, entry); + // cache only if shouldCacheRenderingResult return true + if ( + this.options?.shouldCacheRenderingResult?.({ + options: this.options, + entry, + }) + ) { + this.renders.set(key, entry); + } } get(key: string): RenderingEntry | undefined { diff --git a/core-libs/setup/ssr/optimized-engine/ssr-optimization-options.ts b/core-libs/setup/ssr/optimized-engine/ssr-optimization-options.ts index d0fc77f6884..9f3610ef5da 100644 --- a/core-libs/setup/ssr/optimized-engine/ssr-optimization-options.ts +++ b/core-libs/setup/ssr/optimized-engine/ssr-optimization-options.ts @@ -6,6 +6,7 @@ import { Request } from 'express'; import { DefaultExpressServerLogger, ExpressServerLogger } from '../logger'; +import { RenderingEntry } from './rendering-cache.model'; import { defaultRenderingStrategyResolver } from './rendering-strategy-resolver'; import { defaultRenderingStrategyResolverOptions } from './rendering-strategy-resolver-options'; @@ -136,6 +137,42 @@ export interface SsrOptimizationOptions { * By default, the DefaultExpressServerLogger is used. */ logger?: ExpressServerLogger; + + /** + * When caching is enabled, this function tells whether the given rendering result + * (html or error) should be cached. + * + * By default, all html rendering results are cached. By default, also all errors are cached + * unless the separate option `avoidCachingErrors` is enabled. + */ + shouldCacheRenderingResult?: ({ + options, + entry, + }: { + options: SsrOptimizationOptions; + entry: Pick; + }) => boolean; + + /** + * Toggles providing granular adaptation to breaking changes in OptimizedSsrEngine. + * They are temporary and will be removed in the future. + * Each toggle has its own lifespan. + * + * Note: They are related only to the `OptimizedSsrEngine`. In particular, they + * are different from Spartacus's regular feature toggles provided in the Angular app. + */ + ssrFeatureToggles?: { + /** + * Determines if rendering errors should be skipped from caching. + * + * It's recommended to set to `true` (i.e. errors are skipped from caching), + * which will become the default behavior, when this feature toggle is removed. + * + * It only affects the default `shouldCacheRenderingResult`. + * Custom implementations of `shouldCacheRenderingResult` may ignore this setting. + */ + avoidCachingErrors?: boolean; + }; } export enum RenderingStrategy { @@ -144,7 +181,16 @@ export enum RenderingStrategy { ALWAYS_SSR = 1, } -export const defaultSsrOptimizationOptions: SsrOptimizationOptions = { +/** + * Deeply required type for `featureToggles` property. + */ +type DeepRequired = { + [P in keyof T]-?: DeepRequired; +}; + +export const defaultSsrOptimizationOptions: SsrOptimizationOptions & + // To not forget adding default values, when adding new feature toggles in the type in the future + DeepRequired> = { cacheSize: 3000, concurrency: 10, timeout: 3_000, @@ -155,4 +201,12 @@ export const defaultSsrOptimizationOptions: SsrOptimizationOptions = { defaultRenderingStrategyResolverOptions ), logger: new DefaultExpressServerLogger(), + shouldCacheRenderingResult: ({ options, entry }) => + !( + options.ssrFeatureToggles?.avoidCachingErrors === true && + Boolean(entry.err) + ), + ssrFeatureToggles: { + avoidCachingErrors: false, + }, }; diff --git a/core-libs/setup/ssr/providers/ssr-providers.ts b/core-libs/setup/ssr/providers/ssr-providers.ts index 9293c835d19..b6af3698cef 100644 --- a/core-libs/setup/ssr/providers/ssr-providers.ts +++ b/core-libs/setup/ssr/providers/ssr-providers.ts @@ -4,12 +4,15 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { StaticProvider } from '@angular/core'; +import { Provider, StaticProvider } from '@angular/core'; import { LoggerService, + MULTI_ERROR_HANDLER, SERVER_REQUEST_ORIGIN, SERVER_REQUEST_URL, } from '@spartacus/core'; + +import { PropagatingToServerErrorHandler } from '../error-handling/multi-error-handlers'; import { getRequestOrigin } from '../express-utils/express-request-origin'; import { getRequestUrl } from '../express-utils/express-request-url'; import { serverLoggerServiceFactory } from '../logger'; @@ -21,7 +24,7 @@ import { serverRequestUrlFactory } from './server-request-url'; /** * Returns the providers used for SSR and pre-rendering processes. */ -export function provideServer(options?: ServerOptions): StaticProvider[] { +export function provideServer(options?: ServerOptions): Provider[] { return [ { provide: SERVER_REQUEST_ORIGIN, @@ -35,6 +38,11 @@ export function provideServer(options?: ServerOptions): StaticProvider[] { provide: LoggerService, useFactory: serverLoggerServiceFactory, }, + { + provide: MULTI_ERROR_HANDLER, + useExisting: PropagatingToServerErrorHandler, + multi: true, + }, ]; } /** diff --git a/core-libs/setup/ssr/public_api.ts b/core-libs/setup/ssr/public_api.ts index 6ead7398692..b1a6d0e428e 100644 --- a/core-libs/setup/ssr/public_api.ts +++ b/core-libs/setup/ssr/public_api.ts @@ -5,7 +5,9 @@ */ export * from './engine-decorator/index'; +export * from './engine/cx-common-engine'; export * from './engine/ng-express-engine'; +export * from './error-handling/index'; export * from './logger/index'; export * from './optimized-engine/index'; export * from './providers/index'; diff --git a/feature-libs/asm/core/store/actions/customer.action.spec.ts b/feature-libs/asm/core/store/actions/customer.action.spec.ts index 293df969d30..06486b7ceea 100644 --- a/feature-libs/asm/core/store/actions/customer.action.spec.ts +++ b/feature-libs/asm/core/store/actions/customer.action.spec.ts @@ -18,6 +18,7 @@ const mockUser: User = { const mockCustomerSearchPage: CustomerSearchPage = { entries: [mockUser], } as CustomerSearchPage; +const error = new Error('anError'); describe('Customer Actions', () => { describe('Customer Search Actions', () => { @@ -32,13 +33,13 @@ describe('Customer Actions', () => { }); it('should create the CustomerSearchFail action', () => { - const error = 'anError'; const action = new AsmActions.CustomerSearchFail(error); expect({ ...action }).toEqual({ type: AsmActions.CUSTOMER_SEARCH_FAIL, - meta: StateUtils.failMeta(CUSTOMER_SEARCH_DATA), + meta: StateUtils.failMeta(CUSTOMER_SEARCH_DATA, error), payload: error, + error, }); }); @@ -76,13 +77,13 @@ describe('Customer Actions', () => { }); it('should create the CustomerListCustomersSearchFail action', () => { - const error = 'anError'; const action = new AsmActions.CustomerListCustomersSearchFail(error); expect({ ...action }).toEqual({ type: AsmActions.CUSTOMER_LIST_CUSTOMERS_SEARCH_FAIL, - meta: StateUtils.failMeta(CUSTOMER_LIST_CUSTOMERS_SEARCH_DATA), + meta: StateUtils.failMeta(CUSTOMER_LIST_CUSTOMERS_SEARCH_DATA, error), payload: error, + error, }); }); diff --git a/feature-libs/asm/core/store/actions/customer.action.ts b/feature-libs/asm/core/store/actions/customer.action.ts index 0f13450c7a8..bcadde1e86c 100644 --- a/feature-libs/asm/core/store/actions/customer.action.ts +++ b/feature-libs/asm/core/store/actions/customer.action.ts @@ -5,7 +5,7 @@ */ import { CustomerSearchOptions, CustomerSearchPage } from '@spartacus/asm/root'; -import { StateUtils } from '@spartacus/core'; +import { ErrorAction, StateUtils } from '@spartacus/core'; import { CUSTOMER_LIST_CUSTOMERS_SEARCH_DATA, CUSTOMER_SEARCH_DATA, @@ -32,10 +32,13 @@ export class CustomerSearch extends StateUtils.LoaderLoadAction { } } -export class CustomerSearchFail extends StateUtils.LoaderFailAction { +export class CustomerSearchFail + extends StateUtils.LoaderFailAction + implements ErrorAction +{ readonly type = CUSTOMER_SEARCH_FAIL; constructor(public payload: any) { - super(CUSTOMER_SEARCH_DATA); + super(CUSTOMER_SEARCH_DATA, payload); } } @@ -60,10 +63,13 @@ export class CustomerListCustomersSearch extends StateUtils.LoaderLoadAction { } } -export class CustomerListCustomersSearchFail extends StateUtils.LoaderFailAction { +export class CustomerListCustomersSearchFail + extends StateUtils.LoaderFailAction + implements ErrorAction +{ readonly type = CUSTOMER_LIST_CUSTOMERS_SEARCH_FAIL; constructor(public payload: any) { - super(CUSTOMER_LIST_CUSTOMERS_SEARCH_DATA); + super(CUSTOMER_LIST_CUSTOMERS_SEARCH_DATA, payload); } } diff --git a/feature-libs/asm/core/store/effects/customer.effect.ts b/feature-libs/asm/core/store/effects/customer.effect.ts index 4595c500e63..3db1d1487fd 100644 --- a/feature-libs/asm/core/store/effects/customer.effect.ts +++ b/feature-libs/asm/core/store/effects/customer.effect.ts @@ -4,10 +4,10 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { Injectable, inject } from '@angular/core'; +import { inject, Injectable } from '@angular/core'; import { Actions, createEffect, ofType } from '@ngrx/effects'; import { CustomerSearchPage } from '@spartacus/asm/root'; -import { LoggerService, normalizeHttpError } from '@spartacus/core'; +import { LoggerService, tryNormalizeHttpError } from '@spartacus/core'; import { Observable, of } from 'rxjs'; import { catchError, map, switchMap } from 'rxjs/operators'; import { AsmConnector } from '../../connectors/asm.connector'; @@ -35,7 +35,7 @@ export class CustomerEffects { } return of( new AsmActions.CustomerSearchFail( - normalizeHttpError(error, this.logger) + tryNormalizeHttpError(error, this.logger) ) ); }) @@ -59,7 +59,7 @@ export class CustomerEffects { catchError((error) => of( new AsmActions.CustomerListCustomersSearchFail( - normalizeHttpError(error, this.logger) + tryNormalizeHttpError(error, this.logger) ) ) ) diff --git a/feature-libs/cart/base/core/event/cart-event.builder.spec.ts b/feature-libs/cart/base/core/event/cart-event.builder.spec.ts index 8cf1b5e98d0..45207ccadc0 100644 --- a/feature-libs/cart/base/core/event/cart-event.builder.spec.ts +++ b/feature-libs/cart/base/core/event/cart-event.builder.spec.ts @@ -46,6 +46,7 @@ const MOCK_ACTIVE_CART: Cart = { guid: MOCK_ACTIVE_CART_ID, code: MOCK_ID, }; +const error = new Error('error'); class MockActiveCartService implements Partial { getActive = () => of(MOCK_ACTIVE_CART); getActiveCartId = () => getActiveCartIdSubject; @@ -202,11 +203,11 @@ describe('CartEventBuilder', () => { event: createFrom(CartAddEntryFailEvent, eventData), actionActive: new CartActions.CartAddEntryFail({ ...eventData, - error: 'error', + error, }), actionNotActive: new CartActions.CartAddEntryFail({ ...eventData, - error: 'error', + error, ...MOCK_NOT_ACTIVE_CART_EVENT, }), }); @@ -284,14 +285,14 @@ describe('CartEventBuilder', () => { actions$.next( new CartActions.CartRemoveEntryFail({ - error: 'remove failed', + error, entryNumber: '0', ...MOCK_ACTIVE_CART_EVENT, }) ); actions$.next( new CartActions.CartRemoveEntryFail({ - error: 'remove failed', + error, entryNumber: '0', ...MOCK_NOT_ACTIVE_CART_EVENT, }) @@ -299,7 +300,7 @@ describe('CartEventBuilder', () => { actions$.next( new CartActions.CartRemoveEntryFail({ - error: 'remove failed', + error, entryNumber: '1', ...MOCK_ACTIVE_CART_EVENT, }) @@ -361,7 +362,7 @@ describe('CartEventBuilder', () => { actions$.next( new CartActions.CartUpdateEntryFail({ - error: 'update failed', + error: new Error('update failed'), entryNumber: '0', quantity: 2, ...MOCK_ACTIVE_CART_EVENT, diff --git a/feature-libs/cart/base/core/facade/cart-voucher.service.spec.ts b/feature-libs/cart/base/core/facade/cart-voucher.service.spec.ts index 5f0d4c8ae47..1fbd5adab04 100644 --- a/feature-libs/cart/base/core/facade/cart-voucher.service.spec.ts +++ b/feature-libs/cart/base/core/facade/cart-voucher.service.spec.ts @@ -76,9 +76,10 @@ describe('CartVoucherService', () => { }); it('should return the error flag', () => { + const error = new Error('error'); store.dispatch( new CartActions.CartAddVoucherFail({ - error: 'error', + error, userId, cartId: cart.code, voucherId, diff --git a/feature-libs/cart/base/core/store/actions/cart-entry.action.spec.ts b/feature-libs/cart/base/core/store/actions/cart-entry.action.spec.ts index 4e7581a36be..85404b00412 100644 --- a/feature-libs/cart/base/core/store/actions/cart-entry.action.spec.ts +++ b/feature-libs/cart/base/core/store/actions/cart-entry.action.spec.ts @@ -45,7 +45,7 @@ describe('Cart-entry Actions', () => { describe('CartAddEntryFail', () => { it('should create the action', () => { - const error = 'anError'; + const error = { message: 'anError' }; const payload = { error, cartId, @@ -56,6 +56,7 @@ describe('Cart-entry Actions', () => { const action = new CartActions.CartAddEntryFail(payload); expect({ ...action }).toEqual({ + error, type: CartActions.CART_ADD_ENTRY_FAIL, payload, meta: StateUtils.entityProcessesDecrementMeta( @@ -105,10 +106,11 @@ describe('Cart-entry Actions', () => { describe('CartRemoveEntryFail', () => { it('should create the action', () => { - const error = 'anError'; + const error = { message: 'anError' }; const payload = { error, cartId, userId, entryNumber }; const action = new CartActions.CartRemoveEntryFail(payload); expect({ ...action }).toEqual({ + error, type: CartActions.CART_REMOVE_ENTRY_FAIL, payload, meta: StateUtils.entityProcessesDecrementMeta( @@ -164,10 +166,11 @@ describe('Cart-entry Actions', () => { describe('CartUpdateEntryFail', () => { it('should create the action', () => { - const error = 'anError'; + const error = { message: 'anError' }; const payload = { error, cartId, userId, entryNumber, quantity: 2 }; const action = new CartActions.CartUpdateEntryFail(payload); expect({ ...action }).toEqual({ + error, type: CartActions.CART_UPDATE_ENTRY_FAIL, payload, meta: StateUtils.entityProcessesDecrementMeta( diff --git a/feature-libs/cart/base/core/store/actions/cart-entry.action.ts b/feature-libs/cart/base/core/store/actions/cart-entry.action.ts index 81d5117171f..ffe6e7ae199 100644 --- a/feature-libs/cart/base/core/store/actions/cart-entry.action.ts +++ b/feature-libs/cart/base/core/store/actions/cart-entry.action.ts @@ -5,7 +5,7 @@ */ import { OrderEntry } from '@spartacus/cart/base/root'; -import { StateUtils } from '@spartacus/core'; +import { ErrorAction, StateUtils } from '@spartacus/core'; import { MULTI_CART_DATA } from '../multi-cart-state'; export const CART_ADD_ENTRY = '[Cart-entry] Add Entry'; @@ -21,6 +21,7 @@ export const CART_UPDATE_ENTRY_FAIL = '[Cart-entry] Update Entry Fail'; export class CartAddEntry extends StateUtils.EntityProcessesIncrementAction { readonly type = CART_ADD_ENTRY; + constructor( public payload: { cartId: string; @@ -36,6 +37,7 @@ export class CartAddEntry extends StateUtils.EntityProcessesIncrementAction { export class CartAddEntrySuccess extends StateUtils.EntityProcessesDecrementAction { readonly type = CART_ADD_ENTRY_SUCCESS; + constructor( public payload: { userId: string; @@ -54,8 +56,13 @@ export class CartAddEntrySuccess extends StateUtils.EntityProcessesDecrementActi } } -export class CartAddEntryFail extends StateUtils.EntityProcessesDecrementAction { +export class CartAddEntryFail + extends StateUtils.EntityProcessesDecrementAction + implements ErrorAction +{ + public error: any; readonly type = CART_ADD_ENTRY_FAIL; + constructor( public payload: { error: any; @@ -67,11 +74,13 @@ export class CartAddEntryFail extends StateUtils.EntityProcessesDecrementAction } ) { super(MULTI_CART_DATA, payload.cartId); + this.error = payload.error; } } export class CartRemoveEntry extends StateUtils.EntityProcessesIncrementAction { readonly type = CART_REMOVE_ENTRY; + constructor( public payload: { cartId: string; userId: string; entryNumber: string } ) { @@ -81,6 +90,7 @@ export class CartRemoveEntry extends StateUtils.EntityProcessesIncrementAction { export class CartRemoveEntrySuccess extends StateUtils.EntityProcessesDecrementAction { readonly type = CART_REMOVE_ENTRY_SUCCESS; + constructor( public payload: { userId: string; cartId: string; entryNumber: string } ) { @@ -88,8 +98,13 @@ export class CartRemoveEntrySuccess extends StateUtils.EntityProcessesDecrementA } } -export class CartRemoveEntryFail extends StateUtils.EntityProcessesDecrementAction { +export class CartRemoveEntryFail + extends StateUtils.EntityProcessesDecrementAction + implements ErrorAction +{ + public error: any; readonly type = CART_REMOVE_ENTRY_FAIL; + constructor( public payload: { error: any; @@ -99,11 +114,13 @@ export class CartRemoveEntryFail extends StateUtils.EntityProcessesDecrementActi } ) { super(MULTI_CART_DATA, payload.cartId); + this.error = payload.error; } } export class CartUpdateEntry extends StateUtils.EntityProcessesIncrementAction { readonly type = CART_UPDATE_ENTRY; + constructor( public payload: { userId: string; @@ -120,6 +137,7 @@ export class CartUpdateEntry extends StateUtils.EntityProcessesIncrementAction { export class CartUpdateEntrySuccess extends StateUtils.EntityProcessesDecrementAction { readonly type = CART_UPDATE_ENTRY_SUCCESS; + constructor( public payload: { userId: string; @@ -134,8 +152,13 @@ export class CartUpdateEntrySuccess extends StateUtils.EntityProcessesDecrementA } } -export class CartUpdateEntryFail extends StateUtils.EntityProcessesDecrementAction { +export class CartUpdateEntryFail + extends StateUtils.EntityProcessesDecrementAction + implements ErrorAction +{ + public error: any; readonly type = CART_UPDATE_ENTRY_FAIL; + constructor( public payload: { error: any; @@ -148,6 +171,7 @@ export class CartUpdateEntryFail extends StateUtils.EntityProcessesDecrementActi } ) { super(MULTI_CART_DATA, payload.cartId); + this.error = payload.error; } } diff --git a/feature-libs/cart/base/core/store/actions/cart-voucher.action.spec.ts b/feature-libs/cart/base/core/store/actions/cart-voucher.action.spec.ts index 1efdd08ac1c..c6903013dbe 100644 --- a/feature-libs/cart/base/core/store/actions/cart-voucher.action.spec.ts +++ b/feature-libs/cart/base/core/store/actions/cart-voucher.action.spec.ts @@ -29,7 +29,7 @@ describe('Cart-voucher Actions', () => { describe('AddVoucherFail', () => { it('should create the action', () => { - const error = 'anError'; + const error = { message: 'anError' }; const payload = { error, voucherId, @@ -39,6 +39,7 @@ describe('Cart-voucher Actions', () => { const action = new CartActions.CartAddVoucherFail(payload); expect({ ...action }).toEqual({ + error, type: CartActions.CART_ADD_VOUCHER_FAIL, payload, meta: StateUtils.entityFailMeta( @@ -105,11 +106,12 @@ describe('Cart-voucher Actions', () => { describe('RemoveVoucherFail', () => { it('should create the action', () => { - const error = 'anError'; + const error = { message: 'anError' }; const payload = { error, userId, cartId, voucherId }; const action = new CartActions.CartRemoveVoucherFail(payload); expect({ ...action }).toEqual({ + error, type: CartActions.CART_REMOVE_VOUCHER_FAIL, payload, meta: StateUtils.entityProcessesDecrementMeta( diff --git a/feature-libs/cart/base/core/store/actions/cart-voucher.action.ts b/feature-libs/cart/base/core/store/actions/cart-voucher.action.ts index 2aaef5507ac..5fcd6c55c32 100644 --- a/feature-libs/cart/base/core/store/actions/cart-voucher.action.ts +++ b/feature-libs/cart/base/core/store/actions/cart-voucher.action.ts @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { PROCESS_FEATURE, StateUtils } from '@spartacus/core'; +import { ErrorAction, PROCESS_FEATURE, StateUtils } from '@spartacus/core'; import { ADD_VOUCHER_PROCESS_ID, MULTI_CART_DATA } from '../multi-cart-state'; export const CART_ADD_VOUCHER = '[Cart-voucher] Add Cart Vouchers'; @@ -22,6 +22,7 @@ export const CART_REMOVE_VOUCHER_SUCCESS = // Adding cart voucher actions export class CartAddVoucher extends StateUtils.EntityLoadAction { readonly type = CART_ADD_VOUCHER; + constructor( public payload: { userId: string; cartId: string; voucherId: string } ) { @@ -29,8 +30,12 @@ export class CartAddVoucher extends StateUtils.EntityLoadAction { } } -export class CartAddVoucherFail extends StateUtils.EntityFailAction { +export class CartAddVoucherFail + extends StateUtils.EntityFailAction + implements ErrorAction +{ readonly type = CART_ADD_VOUCHER_FAIL; + constructor( public payload: { userId: string; @@ -45,6 +50,7 @@ export class CartAddVoucherFail extends StateUtils.EntityFailAction { export class CartAddVoucherSuccess extends StateUtils.EntitySuccessAction { readonly type = CART_ADD_VOUCHER_SUCCESS; + constructor( public payload: { userId: string; cartId: string; voucherId: string } ) { @@ -57,6 +63,7 @@ export class CartAddVoucherSuccess extends StateUtils.EntitySuccessAction { */ export class CartResetAddVoucher extends StateUtils.EntityLoaderResetAction { readonly type = CART_RESET_ADD_VOUCHER; + constructor() { super(PROCESS_FEATURE, ADD_VOUCHER_PROCESS_ID); } @@ -65,6 +72,7 @@ export class CartResetAddVoucher extends StateUtils.EntityLoaderResetAction { // Deleting cart voucher export class CartRemoveVoucher extends StateUtils.EntityProcessesIncrementAction { readonly type = CART_REMOVE_VOUCHER; + constructor( public payload: { userId: string; cartId: string; voucherId: string } ) { @@ -72,8 +80,13 @@ export class CartRemoveVoucher extends StateUtils.EntityProcessesIncrementAction } } -export class CartRemoveVoucherFail extends StateUtils.EntityProcessesDecrementAction { +export class CartRemoveVoucherFail + extends StateUtils.EntityProcessesDecrementAction + implements ErrorAction +{ + public error: any; readonly type = CART_REMOVE_VOUCHER_FAIL; + constructor( public payload: { error: any; @@ -83,11 +96,13 @@ export class CartRemoveVoucherFail extends StateUtils.EntityProcessesDecrementAc } ) { super(MULTI_CART_DATA, payload.cartId); + this.error = payload.error; } } export class CartRemoveVoucherSuccess extends StateUtils.EntityProcessesDecrementAction { readonly type = CART_REMOVE_VOUCHER_SUCCESS; + constructor( public payload: { userId: string; cartId: string; voucherId: string } ) { diff --git a/feature-libs/cart/base/core/store/actions/cart.action.spec.ts b/feature-libs/cart/base/core/store/actions/cart.action.spec.ts index c99fef43a24..dc997e468f0 100644 --- a/feature-libs/cart/base/core/store/actions/cart.action.spec.ts +++ b/feature-libs/cart/base/core/store/actions/cart.action.spec.ts @@ -39,12 +39,18 @@ describe('Cart Actions', () => { describe('CreateCartFail', () => { it('should create the action', () => { - const payload = { tempCartId, userId: 'userId', error: 'error' }; + const error = new Error('error'); + const payload = { + tempCartId, + userId: 'userId', + error, + }; const action = new CartActions.CreateCartFail(payload); expect({ ...action }).toEqual({ + error: payload.error, type: CartActions.CREATE_CART_FAIL, payload, - meta: StateUtils.entityFailMeta(MULTI_CART_DATA, tempCartId), + meta: StateUtils.entityFailMeta(MULTI_CART_DATA, tempCartId, error), }); }); }); @@ -82,9 +88,14 @@ describe('Cart Actions', () => { describe('LoadCartFail', () => { it('should create the action', () => { - const payload = { cartId: 'cartId', error: 'error', userId: 'userId' }; + const payload = { + cartId: 'cartId', + error: { message: 'error' }, + userId: 'userId', + }; const action = new CartActions.LoadCartFail(payload); expect({ ...action }).toEqual({ + error: payload.error, type: CartActions.LOAD_CART_FAIL, payload, meta: StateUtils.entityFailMeta( @@ -155,7 +166,7 @@ describe('Cart Actions', () => { describe('AddEmailToCartFail', () => { it('should create the action', () => { const payload = { - error: 'anError', + error: { message: 'anError' }, cartId: 'cartId', userId: 'userId', email: 'email@email.com', @@ -163,6 +174,7 @@ describe('Cart Actions', () => { const action = new CartActions.AddEmailToCartFail(payload); expect({ ...action }).toEqual({ + error: payload.error, type: CartActions.ADD_EMAIL_TO_CART_FAIL, payload, meta: StateUtils.entityProcessesDecrementMeta( @@ -262,7 +274,7 @@ describe('Cart Actions', () => { describe('DeleteCartFail', () => { it('should create the action', () => { - const error = 'anError'; + const error = { message: 'anError' }; const userId = 'xxx@xxx.xxx'; const cartId = 'testCartId'; const payload = { @@ -273,6 +285,7 @@ describe('Cart Actions', () => { const action = new CartActions.DeleteCartFail(payload); expect({ ...action }).toEqual({ + error, type: CartActions.DELETE_CART_FAIL, payload, }); diff --git a/feature-libs/cart/base/core/store/actions/cart.action.ts b/feature-libs/cart/base/core/store/actions/cart.action.ts index 87e5317038f..d71f4e41602 100644 --- a/feature-libs/cart/base/core/store/actions/cart.action.ts +++ b/feature-libs/cart/base/core/store/actions/cart.action.ts @@ -6,7 +6,7 @@ import { Action } from '@ngrx/store'; import { Cart } from '@spartacus/cart/base/root'; -import { StateUtils } from '@spartacus/core'; +import { ErrorAction, StateUtils } from '@spartacus/core'; import { MULTI_CART_DATA } from '../multi-cart-state'; export const CREATE_CART = '[Cart] Create Cart'; @@ -58,10 +58,13 @@ interface CreateCartFailPayload extends CreateCartPayload { error: any; } -export class CreateCartFail extends StateUtils.EntityFailAction { +export class CreateCartFail + extends StateUtils.EntityFailAction + implements ErrorAction +{ readonly type = CREATE_CART_FAIL; constructor(public payload: CreateCartFailPayload) { - super(MULTI_CART_DATA, payload.tempCartId); + super(MULTI_CART_DATA, payload.tempCartId, payload.error); } } @@ -86,7 +89,11 @@ export class AddEmailToCart extends StateUtils.EntityProcessesIncrementAction { } } -export class AddEmailToCartFail extends StateUtils.EntityProcessesDecrementAction { +export class AddEmailToCartFail + extends StateUtils.EntityProcessesDecrementAction + implements ErrorAction +{ + public error: any; readonly type = ADD_EMAIL_TO_CART_FAIL; constructor( public payload: { @@ -97,6 +104,7 @@ export class AddEmailToCartFail extends StateUtils.EntityProcessesDecrementActio } ) { super(MULTI_CART_DATA, payload.cartId); + this.error = payload.error; } } @@ -128,7 +136,10 @@ interface LoadCartFailPayload extends LoadCartPayload { error: any; } -export class LoadCartFail extends StateUtils.EntityFailAction { +export class LoadCartFail + extends StateUtils.EntityFailAction + implements ErrorAction +{ readonly type = LOAD_CART_FAIL; constructor(public payload: LoadCartFailPayload) { super(MULTI_CART_DATA, payload.cartId, payload.error); @@ -220,9 +231,12 @@ export class DeleteCartSuccess extends StateUtils.EntityRemoveAction { } } -export class DeleteCartFail implements Action { +export class DeleteCartFail implements ErrorAction { + public error: any; readonly type = DELETE_CART_FAIL; - constructor(public payload: { userId: string; cartId: string; error: any }) {} + constructor(public payload: { userId: string; cartId: string; error: any }) { + this.error = payload.error; + } } export type CartAction = diff --git a/feature-libs/cart/base/core/store/effects/cart-entry.effect.ts b/feature-libs/cart/base/core/store/effects/cart-entry.effect.ts index 326e67637fb..6d88190ca12 100644 --- a/feature-libs/cart/base/core/store/effects/cart-entry.effect.ts +++ b/feature-libs/cart/base/core/store/effects/cart-entry.effect.ts @@ -10,7 +10,7 @@ import { CartModification } from '@spartacus/cart/base/root'; import { LoggerService, SiteContextActions, - normalizeHttpError, + tryNormalizeHttpError, withdrawOn, } from '@spartacus/core'; import { Observable, from } from 'rxjs'; @@ -58,7 +58,7 @@ export class CartEntryEffects { from([ new CartActions.CartAddEntryFail({ ...payload, - error: normalizeHttpError(error, this.logger), + error: tryNormalizeHttpError(error, this.logger), }), new CartActions.LoadCart({ cartId: payload.cartId, @@ -93,7 +93,7 @@ export class CartEntryEffects { from([ new CartActions.CartRemoveEntryFail({ ...payload, - error: normalizeHttpError(error, this.logger), + error: tryNormalizeHttpError(error, this.logger), }), new CartActions.LoadCart({ cartId: payload.cartId, @@ -135,7 +135,7 @@ export class CartEntryEffects { from([ new CartActions.CartUpdateEntryFail({ ...payload, - error: normalizeHttpError(error, this.logger), + error: tryNormalizeHttpError(error, this.logger), }), new CartActions.LoadCart({ cartId: payload.cartId, diff --git a/feature-libs/cart/base/core/store/effects/cart-voucher.effect.spec.ts b/feature-libs/cart/base/core/store/effects/cart-voucher.effect.spec.ts index 4b06a745dab..65e6c7b3185 100644 --- a/feature-libs/cart/base/core/store/effects/cart-voucher.effect.spec.ts +++ b/feature-libs/cart/base/core/store/effects/cart-voucher.effect.spec.ts @@ -5,8 +5,8 @@ import { provideMockActions } from '@ngrx/effects/testing'; import { GlobalMessageService, LoggerService, - normalizeHttpError, OccConfig, + tryNormalizeHttpError, } from '@spartacus/core'; import { cold, hot } from 'jasmine-marbles'; import { Observable, of, throwError } from 'rxjs'; @@ -104,7 +104,7 @@ describe('Cart Voucher effect', () => { userId, cartId, voucherId, - error: normalizeHttpError(error, new MockLoggerService()), + error: tryNormalizeHttpError(error, new MockLoggerService()), }); const completion2 = new CartActions.CartProcessesDecrement(cartId); const completion3 = new CartActions.LoadCart({ diff --git a/feature-libs/cart/base/core/store/effects/cart-voucher.effect.ts b/feature-libs/cart/base/core/store/effects/cart-voucher.effect.ts index 8e4b1ee254e..5c925c80711 100644 --- a/feature-libs/cart/base/core/store/effects/cart-voucher.effect.ts +++ b/feature-libs/cart/base/core/store/effects/cart-voucher.effect.ts @@ -10,7 +10,7 @@ import { GlobalMessageService, GlobalMessageType, LoggerService, - normalizeHttpError, + tryNormalizeHttpError, } from '@spartacus/core'; import { Observable, from } from 'rxjs'; import { catchError, map, mergeMap } from 'rxjs/operators'; @@ -53,7 +53,7 @@ export class CartVoucherEffects { from([ new CartActions.CartAddVoucherFail({ ...payload, - error: normalizeHttpError(error, this.logger), + error: tryNormalizeHttpError(error, this.logger), }), new CartActions.CartProcessesDecrement(payload.cartId), new CartActions.LoadCart({ @@ -92,7 +92,7 @@ export class CartVoucherEffects { catchError((error) => from([ new CartActions.CartRemoveVoucherFail({ - error: normalizeHttpError(error, this.logger), + error: tryNormalizeHttpError(error, this.logger), cartId: payload.cartId, userId: payload.userId, voucherId: payload.voucherId, diff --git a/feature-libs/cart/base/core/store/effects/cart.effect.spec.ts b/feature-libs/cart/base/core/store/effects/cart.effect.spec.ts index 622472f47e5..eff7ce32594 100644 --- a/feature-libs/cart/base/core/store/effects/cart.effect.spec.ts +++ b/feature-libs/cart/base/core/store/effects/cart.effect.spec.ts @@ -11,7 +11,7 @@ import { OccConfig, SiteContextActions, USER_FEATURE, - normalizeHttpError, + tryNormalizeHttpError, } from '@spartacus/core'; import { cold, hot } from 'jasmine-marbles'; import * as fromClientAuthReducers from 'projects/core/src/auth/client-auth/store/reducers/index'; @@ -212,7 +212,7 @@ describe('Cart effect', () => { loadMock.and.returnValue(throwError(() => httpError)); const removeCartCompletion = new CartActions.LoadCartFail({ ...payload, - error: normalizeHttpError(httpError, new MockLoggerService()), + error: tryNormalizeHttpError(httpError, new MockLoggerService()), }); actions$ = hot('-a', { a: action }); const expected = cold('-b', { diff --git a/feature-libs/cart/base/core/store/effects/cart.effect.ts b/feature-libs/cart/base/core/store/effects/cart.effect.ts index 8ecffc3e6f0..cc795e3e973 100644 --- a/feature-libs/cart/base/core/store/effects/cart.effect.ts +++ b/feature-libs/cart/base/core/store/effects/cart.effect.ts @@ -13,7 +13,7 @@ import { OCC_CART_ID_CURRENT, SiteContextActions, isNotUndefined, - normalizeHttpError, + tryNormalizeHttpError, withdrawOn, } from '@spartacus/core'; import { Observable, from, of } from 'rxjs'; @@ -132,7 +132,7 @@ export class CartEffects { return of( new CartActions.LoadCartFail({ ...payload, - error: normalizeHttpError(error, this.logger), + error: tryNormalizeHttpError(error, this.logger), }) ); } @@ -177,7 +177,7 @@ export class CartEffects { of( new CartActions.CreateCartFail({ ...payload, - error: normalizeHttpError(error, this.logger), + error: tryNormalizeHttpError(error, this.logger), }) ) ) @@ -301,7 +301,7 @@ export class CartEffects { from([ new CartActions.AddEmailToCartFail({ ...payload, - error: normalizeHttpError(error, this.logger), + error: tryNormalizeHttpError(error, this.logger), }), new CartActions.LoadCart({ userId: payload.userId, @@ -332,7 +332,7 @@ export class CartEffects { from([ new CartActions.DeleteCartFail({ ...payload, - error: normalizeHttpError(error, this.logger), + error: tryNormalizeHttpError(error, this.logger), }), // Error might happen in higher backend layer and cart could still be removed. // When load fail with NotFound error then RemoveCart action will kick in and clear that cart in our state. diff --git a/feature-libs/cart/saved-cart/core/facade/saved-cart.service.spec.ts b/feature-libs/cart/saved-cart/core/facade/saved-cart.service.spec.ts index 3f591c8e315..31c1a631c44 100644 --- a/feature-libs/cart/saved-cart/core/facade/saved-cart.service.spec.ts +++ b/feature-libs/cart/saved-cart/core/facade/saved-cart.service.spec.ts @@ -12,14 +12,13 @@ import { UserAccountFacade } from '@spartacus/user/account/root'; import { of } from 'rxjs'; import { SavedCartActions } from '../store/actions/index'; import { SavedCartService } from './saved-cart.service'; - import createSpy = jasmine.createSpy; const mockUserId = 'test-user'; const mockCartId = 'test-cart'; const mockCartName = 'test-cart-name'; const mockCartDescription = 'test-cart-description'; -const mockError = 'test-error'; +const mockError = new Error('test-error'); const mockUser: User = { customerId: 'test-customer', diff --git a/feature-libs/cart/saved-cart/core/store/actions/saved-cart.action.spec.ts b/feature-libs/cart/saved-cart/core/store/actions/saved-cart.action.spec.ts index 4f1b346eda9..2cebbfafa2d 100644 --- a/feature-libs/cart/saved-cart/core/store/actions/saved-cart.action.spec.ts +++ b/feature-libs/cart/saved-cart/core/store/actions/saved-cart.action.spec.ts @@ -12,7 +12,7 @@ const mockUserId = 'test-user'; const mockCartId = 'test-cart'; const mockCartName = 'test-cart-name'; const mockCartDescription = 'test-cart-description'; -const error = 'anError'; +const error = { message: 'anError' }; describe('SavedCart Actions', () => { describe('LoadSavedCart Actions', () => { @@ -54,6 +54,7 @@ describe('SavedCart Actions', () => { }); expect({ ...action }).toEqual({ + error, type: SavedCartActions.LOAD_SAVED_CART_FAIL, payload: { userId: mockUserId, cartId: mockCartId, error }, meta: StateUtils.entityFailMeta(MULTI_CART_DATA, mockCartId, error), @@ -103,6 +104,7 @@ describe('SavedCart Actions', () => { }); expect({ ...action }).toEqual({ + error, type: SavedCartActions.LOAD_SAVED_CARTS_FAIL, payload: { userId: mockUserId, error }, meta: StateUtils.entityFailMeta( @@ -175,6 +177,7 @@ describe('SavedCart Actions', () => { }); expect({ ...action }).toEqual({ + error, type: SavedCartActions.RESTORE_SAVED_CART_FAIL, payload: { userId: mockUserId, cartId: mockCartId, error }, meta: StateUtils.entityFailMeta( @@ -254,6 +257,7 @@ describe('SavedCart Actions', () => { }); expect({ ...action }).toEqual({ + error, type: SavedCartActions.SAVE_CART_FAIL, payload: { userId: mockUserId, @@ -339,6 +343,7 @@ describe('SavedCart Actions', () => { }); expect({ ...action }).toEqual({ + error, type: SavedCartActions.EDIT_SAVED_CART_FAIL, payload: { userId: mockUserId, @@ -412,6 +417,7 @@ describe('SavedCart Actions', () => { }); expect({ ...action }).toEqual({ + error, type: SavedCartActions.CLONE_SAVED_CART_FAIL, payload: { userId: mockUserId, diff --git a/feature-libs/cart/saved-cart/core/store/actions/saved-cart.action.ts b/feature-libs/cart/saved-cart/core/store/actions/saved-cart.action.ts index a68935b36ab..8cfae6d1c2e 100644 --- a/feature-libs/cart/saved-cart/core/store/actions/saved-cart.action.ts +++ b/feature-libs/cart/saved-cart/core/store/actions/saved-cart.action.ts @@ -5,7 +5,7 @@ */ import { MULTI_CART_DATA } from '@spartacus/cart/base/core'; -import { PROCESS_FEATURE, StateUtils } from '@spartacus/core'; +import { ErrorAction, PROCESS_FEATURE, StateUtils } from '@spartacus/core'; import { SAVED_CART_CLONE_CART_PROCESS_ID, SAVED_CART_LIST_PROCESS_ID, @@ -44,6 +44,7 @@ export const CLEAR_CLONE_SAVED_CART = '[Saved Cart] Clear Clone Saved Cart'; export class LoadSavedCart extends StateUtils.EntityLoadAction { readonly type = LOAD_SAVED_CART; + constructor( public payload: { userId: string; @@ -56,6 +57,7 @@ export class LoadSavedCart extends StateUtils.EntityLoadAction { export class LoadSavedCartSuccess extends StateUtils.EntitySuccessAction { readonly type = LOAD_SAVED_CART_SUCCESS; + constructor( public payload: { userId: string; @@ -66,8 +68,12 @@ export class LoadSavedCartSuccess extends StateUtils.EntitySuccessAction { } } -export class LoadSavedCartFail extends StateUtils.EntityFailAction { +export class LoadSavedCartFail + extends StateUtils.EntityFailAction + implements ErrorAction +{ readonly type = LOAD_SAVED_CART_FAIL; + constructor(public payload: { userId: string; cartId: string; error: any }) { super(MULTI_CART_DATA, payload.cartId, payload?.error); } @@ -75,6 +81,7 @@ export class LoadSavedCartFail extends StateUtils.EntityFailAction { export class LoadSavedCarts extends StateUtils.EntityLoadAction { readonly type = LOAD_SAVED_CARTS; + constructor( public payload: { userId: string; @@ -86,6 +93,7 @@ export class LoadSavedCarts extends StateUtils.EntityLoadAction { export class LoadSavedCartsSuccess extends StateUtils.EntitySuccessAction { readonly type = LOAD_SAVED_CARTS_SUCCESS; + constructor( public payload: { userId: string; @@ -95,8 +103,12 @@ export class LoadSavedCartsSuccess extends StateUtils.EntitySuccessAction { } } -export class LoadSavedCartsFail extends StateUtils.EntityFailAction { +export class LoadSavedCartsFail + extends StateUtils.EntityFailAction + implements ErrorAction +{ readonly type = LOAD_SAVED_CARTS_FAIL; + constructor( public payload: { userId: string; @@ -109,6 +121,7 @@ export class LoadSavedCartsFail extends StateUtils.EntityFailAction { export class ClearSavedCarts extends StateUtils.EntityLoaderResetAction { readonly type = CLEAR_SAVED_CARTS; + constructor() { super(PROCESS_FEATURE, SAVED_CART_LIST_PROCESS_ID); } @@ -116,6 +129,7 @@ export class ClearSavedCarts extends StateUtils.EntityLoaderResetAction { export class RestoreSavedCart extends StateUtils.EntityLoadAction { readonly type = RESTORE_SAVED_CART; + constructor( public payload: { userId: string; @@ -128,6 +142,7 @@ export class RestoreSavedCart extends StateUtils.EntityLoadAction { export class RestoreSavedCartSuccess extends StateUtils.EntitySuccessAction { readonly type = RESTORE_SAVED_CART_SUCCESS; + constructor( public payload: { userId: string; @@ -138,8 +153,12 @@ export class RestoreSavedCartSuccess extends StateUtils.EntitySuccessAction { } } -export class RestoreSavedCartFail extends StateUtils.EntityFailAction { +export class RestoreSavedCartFail + extends StateUtils.EntityFailAction + implements ErrorAction +{ readonly type = RESTORE_SAVED_CART_FAIL; + constructor( public payload: { userId: string; @@ -153,6 +172,7 @@ export class RestoreSavedCartFail extends StateUtils.EntityFailAction { export class ClearRestoreSavedCart extends StateUtils.EntityLoaderResetAction { readonly type = CLEAR_RESTORE_SAVED_CART; + constructor() { super(PROCESS_FEATURE, SAVED_CART_RESTORE_CART_PROCESS_ID); } @@ -160,6 +180,7 @@ export class ClearRestoreSavedCart extends StateUtils.EntityLoaderResetAction { export class SaveCart extends StateUtils.EntityLoadAction { readonly type = SAVE_CART; + constructor( public payload: { userId: string; @@ -174,6 +195,7 @@ export class SaveCart extends StateUtils.EntityLoadAction { export class SaveCartSuccess extends StateUtils.EntitySuccessAction { readonly type = SAVE_CART_SUCCESS; + constructor( public payload: { userId: string; @@ -186,8 +208,12 @@ export class SaveCartSuccess extends StateUtils.EntitySuccessAction { } } -export class SaveCartFail extends StateUtils.EntityFailAction { +export class SaveCartFail + extends StateUtils.EntityFailAction + implements ErrorAction +{ readonly type = SAVE_CART_FAIL; + constructor( public payload: { userId: string; @@ -203,6 +229,7 @@ export class SaveCartFail extends StateUtils.EntityFailAction { export class ClearSaveCart extends StateUtils.EntityLoaderResetAction { readonly type = CLEAR_SAVE_CART; + constructor() { super(PROCESS_FEATURE, SAVED_CART_SAVE_CART_PROCESS_ID); } @@ -210,6 +237,7 @@ export class ClearSaveCart extends StateUtils.EntityLoaderResetAction { export class EditSavedCart extends StateUtils.EntityLoadAction { readonly type = EDIT_SAVED_CART; + constructor( public payload: { userId: string; @@ -224,6 +252,7 @@ export class EditSavedCart extends StateUtils.EntityLoadAction { export class EditSavedCartSuccess extends StateUtils.EntitySuccessAction { readonly type = EDIT_SAVED_CART_SUCCESS; + constructor( public payload: { userId: string; @@ -236,8 +265,12 @@ export class EditSavedCartSuccess extends StateUtils.EntitySuccessAction { } } -export class EditSavedCartFail extends StateUtils.EntityFailAction { +export class EditSavedCartFail + extends StateUtils.EntityFailAction + implements ErrorAction +{ readonly type = EDIT_SAVED_CART_FAIL; + constructor( public payload: { userId: string; @@ -253,6 +286,7 @@ export class EditSavedCartFail extends StateUtils.EntityFailAction { export class CloneSavedCart extends StateUtils.EntityLoadAction { readonly type = CLONE_SAVED_CART; + constructor( public payload: { userId: string; @@ -266,6 +300,7 @@ export class CloneSavedCart extends StateUtils.EntityLoadAction { export class CloneSavedCartSuccess extends StateUtils.EntitySuccessAction { readonly type = CLONE_SAVED_CART_SUCCESS; + constructor( public payload: { userId: string; @@ -277,8 +312,12 @@ export class CloneSavedCartSuccess extends StateUtils.EntitySuccessAction { } } -export class CloneSavedCartFail extends StateUtils.EntityFailAction { +export class CloneSavedCartFail + extends StateUtils.EntityFailAction + implements ErrorAction +{ readonly type = CLONE_SAVED_CART_FAIL; + constructor( public payload: { userId: string; @@ -293,6 +332,7 @@ export class CloneSavedCartFail extends StateUtils.EntityFailAction { export class ClearCloneSavedCart extends StateUtils.EntityLoaderResetAction { readonly type = CLEAR_CLONE_SAVED_CART; + constructor() { super(PROCESS_FEATURE, SAVED_CART_CLONE_CART_PROCESS_ID); } diff --git a/feature-libs/cart/saved-cart/core/store/effects/saved-cart.effect.ts b/feature-libs/cart/saved-cart/core/store/effects/saved-cart.effect.ts index e6cef4315ab..65f9b795b77 100644 --- a/feature-libs/cart/saved-cart/core/store/effects/saved-cart.effect.ts +++ b/feature-libs/cart/saved-cart/core/store/effects/saved-cart.effect.ts @@ -13,7 +13,7 @@ import { GlobalMessageService, GlobalMessageType, LoggerService, - normalizeHttpError, + tryNormalizeHttpError, } from '@spartacus/core'; import { Observable, of } from 'rxjs'; import { catchError, map, switchMap, withLatestFrom } from 'rxjs/operators'; @@ -49,7 +49,7 @@ export class SavedCartEffects { new SavedCartActions.LoadSavedCartFail({ userId, cartId, - error: normalizeHttpError(error, this.logger), + error: tryNormalizeHttpError(error, this.logger), }) ) ) @@ -78,7 +78,7 @@ export class SavedCartEffects { of( new SavedCartActions.LoadSavedCartsFail({ userId, - error: normalizeHttpError(error, this.logger), + error: tryNormalizeHttpError(error, this.logger), }) ) ) @@ -155,7 +155,7 @@ export class SavedCartEffects { new SavedCartActions.RestoreSavedCartFail({ userId, cartId, - error: normalizeHttpError(error, this.logger), + error: tryNormalizeHttpError(error, this.logger), }) ) ) @@ -201,7 +201,7 @@ export class SavedCartEffects { cartId, saveCartName, saveCartDescription, - error: normalizeHttpError(error, this.logger), + error: tryNormalizeHttpError(error, this.logger), }) ) ) @@ -245,7 +245,7 @@ export class SavedCartEffects { cartId, saveCartName, saveCartDescription, - error: normalizeHttpError(error, this.logger), + error: tryNormalizeHttpError(error, this.logger), }) ) ) @@ -288,7 +288,7 @@ export class SavedCartEffects { userId, cartId, saveCartName, - error: normalizeHttpError(error, this.logger), + error: tryNormalizeHttpError(error, this.logger), }) ) ) diff --git a/feature-libs/cart/wish-list/core/store/actions/wish-list.action.spec.ts b/feature-libs/cart/wish-list/core/store/actions/wish-list.action.spec.ts index 6a5a4525e55..6edf737cde4 100644 --- a/feature-libs/cart/wish-list/core/store/actions/wish-list.action.spec.ts +++ b/feature-libs/cart/wish-list/core/store/actions/wish-list.action.spec.ts @@ -50,13 +50,18 @@ describe('WishList Actions', () => { const payload = { userId, cartId, - error: 'anyError', + error: { message: 'anyError' }, }; const action = new WishListActions.LoadWishListFail(payload); expect({ ...action }).toEqual({ + error: payload.error, type: WishListActions.LOAD_WISH_LIST_FAIL, payload, - meta: StateUtils.entityFailMeta(MULTI_CART_DATA, cartId, 'anyError'), + meta: StateUtils.entityFailMeta( + MULTI_CART_DATA, + cartId, + payload.error + ), }); }); }); @@ -91,9 +96,10 @@ describe('WishList Actions', () => { describe('CreateWishListFail', () => { it('should create the action', () => { - const payload = { cartId, error: 'error' }; + const payload = { cartId, error: { message: 'error' } }; const action = new WishListActions.CreateWishListFail(payload); expect({ ...action }).toEqual({ + error: payload.error, type: WishListActions.CREATE_WISH_LIST_FAIL, payload, meta: StateUtils.entityFailMeta( diff --git a/feature-libs/cart/wish-list/core/store/actions/wish-list.action.ts b/feature-libs/cart/wish-list/core/store/actions/wish-list.action.ts index 8afeb14e0e9..8212d378db9 100644 --- a/feature-libs/cart/wish-list/core/store/actions/wish-list.action.ts +++ b/feature-libs/cart/wish-list/core/store/actions/wish-list.action.ts @@ -7,7 +7,7 @@ import { Action } from '@ngrx/store'; import { MULTI_CART_DATA } from '@spartacus/cart/base/core'; import { Cart } from '@spartacus/cart/base/root'; -import { StateUtils } from '@spartacus/core'; +import { ErrorAction, StateUtils } from '@spartacus/core'; export const CREATE_WISH_LIST = '[Wish List] Create Wish List'; export const CREATE_WISH_LIST_FAIL = '[Wish List] Create Wish List Fail'; @@ -19,6 +19,7 @@ export const LOAD_WISH_LIST_FAIL = '[Wish List] Load Wish List Fail'; export class CreateWishList implements Action { readonly type = CREATE_WISH_LIST; + constructor( public payload: { userId: string; @@ -30,13 +31,27 @@ export class CreateWishList implements Action { export class CreateWishListSuccess extends StateUtils.EntitySuccessAction { readonly type = CREATE_WISH_LIST_SUCCESS; + constructor(public payload: { cart: Cart; cartId: string }) { super(MULTI_CART_DATA, payload.cartId); } } -export class CreateWishListFail extends StateUtils.EntityFailAction { +export class CreateWishListFail + extends StateUtils.EntityFailAction + implements ErrorAction +{ readonly type = CREATE_WISH_LIST_FAIL; + constructor(payload: { cartId: string; error: any }); + /** + * @deprecated Please pass the argument `error`. + * It will become mandatory along with removing + * the feature toggle `ssrStrictErrorHandlingForHttpAndNgrx`. + */ + constructor( + // eslint-disable-next-line @typescript-eslint/unified-signatures + payload: { cartId: string } + ); constructor(public payload: { cartId: string; error?: any }) { super(MULTI_CART_DATA, payload.cartId, payload.error); } @@ -56,12 +71,15 @@ interface LoadWishListPayload { */ export class LoadWishList extends StateUtils.EntityLoadAction { readonly type = LOAD_WISH_LIST; + constructor(public payload: LoadWishListPayload) { super(MULTI_CART_DATA, payload.cartId); } } + export class LoadWishListSuccess extends StateUtils.EntitySuccessAction { readonly type = LOAD_WISH_LIST_SUCCESS; + constructor(public payload: { cart: Cart; cartId: string }) { super(MULTI_CART_DATA, payload.cartId); } @@ -76,8 +94,12 @@ interface LoadWishListFailPayload { error: any; } -export class LoadWishListFail extends StateUtils.EntityFailAction { +export class LoadWishListFail + extends StateUtils.EntityFailAction + implements ErrorAction +{ readonly type = LOAD_WISH_LIST_FAIL; + constructor(public payload: LoadWishListFailPayload) { super(MULTI_CART_DATA, payload.cartId, payload.error); } diff --git a/feature-libs/cart/wish-list/core/store/effects/wish-list.effect.ts b/feature-libs/cart/wish-list/core/store/effects/wish-list.effect.ts index 25ca2f7091d..46ed6b4bc2d 100644 --- a/feature-libs/cart/wish-list/core/store/effects/wish-list.effect.ts +++ b/feature-libs/cart/wish-list/core/store/effects/wish-list.effect.ts @@ -21,7 +21,7 @@ import { StateUtils, UserIdService, isNotUndefined, - normalizeHttpError, + tryNormalizeHttpError, } from '@spartacus/core'; import { EMPTY, Observable, from } from 'rxjs'; import { @@ -65,7 +65,7 @@ export class WishListEffects { from([ new WishListActions.CreateWishListFail({ cartId: cart.code ?? '', - error: normalizeHttpError(error, this.logger), + error: tryNormalizeHttpError(error, this.logger), }), ]) ) @@ -111,7 +111,7 @@ export class WishListEffects { from([ new WishListActions.LoadWishListFail({ cartId: cartId, - error: normalizeHttpError(error, this.logger), + error: tryNormalizeHttpError(error, this.logger), }), ]) ) @@ -148,7 +148,7 @@ export class WishListEffects { from([ new WishListActions.LoadWishListFail({ cartId: wishListId, - error: normalizeHttpError(error, this.logger), + error: tryNormalizeHttpError(error, this.logger), }), ]) ) diff --git a/feature-libs/order/core/facade/replenishment-order-history.service.spec.ts b/feature-libs/order/core/facade/replenishment-order-history.service.spec.ts index ff791305072..8a5e090d7d0 100644 --- a/feature-libs/order/core/facade/replenishment-order-history.service.spec.ts +++ b/feature-libs/order/core/facade/replenishment-order-history.service.spec.ts @@ -19,7 +19,7 @@ import { ReplenishmentOrderHistoryService } from './replenishment-order-history. const mockUserId = OCC_USER_ID_CURRENT; const mockReplenishmentOrderCode = 'test-repl-code'; -const mockError = 'test-error'; +const mockError = new Error('test-error'); const mockReplenishmentOrder: ReplenishmentOrder = { active: true, diff --git a/feature-libs/order/core/store/actions/consignment-tracking-by-id.action.spec.ts b/feature-libs/order/core/store/actions/consignment-tracking-by-id.action.spec.ts index c6f8ddb641e..550cbfe15c1 100644 --- a/feature-libs/order/core/store/actions/consignment-tracking-by-id.action.spec.ts +++ b/feature-libs/order/core/store/actions/consignment-tracking-by-id.action.spec.ts @@ -52,6 +52,7 @@ describe('Consignment Tracking By Id Actions', () => { 'order1,cons1', 'error' ), + error: 'error', }); }); }); diff --git a/feature-libs/order/core/store/actions/consignment-tracking-by-id.action.ts b/feature-libs/order/core/store/actions/consignment-tracking-by-id.action.ts index f4c58c306c0..821c2078ffc 100644 --- a/feature-libs/order/core/store/actions/consignment-tracking-by-id.action.ts +++ b/feature-libs/order/core/store/actions/consignment-tracking-by-id.action.ts @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { StateUtils } from '@spartacus/core'; +import { ErrorAction, StateUtils } from '@spartacus/core'; import { ConsignmentTracking } from '@spartacus/order/root'; import { CONSIGNMENT_TRACKING_BY_ID_ENTITIES, @@ -37,7 +37,10 @@ export class LoadConsignmentTrackingById extends StateUtils.EntityLoadAction { } } -export class LoadConsignmentTrackingByIdFail extends StateUtils.EntityFailAction { +export class LoadConsignmentTrackingByIdFail + extends StateUtils.EntityFailAction + implements ErrorAction +{ readonly type = LOAD_CONSIGNMENT_TRACKING_BY_ID_FAIL; constructor( public payload: { orderCode: string; consignmentCode: string; error: any } diff --git a/feature-libs/order/core/store/actions/consignment-tracking.action.spec.ts b/feature-libs/order/core/store/actions/consignment-tracking.action.spec.ts index 837637ee700..79c8169cb6f 100644 --- a/feature-libs/order/core/store/actions/consignment-tracking.action.spec.ts +++ b/feature-libs/order/core/store/actions/consignment-tracking.action.spec.ts @@ -23,12 +23,13 @@ describe('Consignment Tracking Actions', () => { describe('LoadConsignmentTrackingFail Action', () => { it('should create the action', () => { - const error = 'mockError'; + const error = new Error('mockError'); const action = new fromAction.LoadConsignmentTrackingFail(error); expect({ ...action }).toEqual({ type: fromAction.LOAD_CONSIGNMENT_TRACKING_FAIL, payload: error, + error, }); }); }); diff --git a/feature-libs/order/core/store/actions/consignment-tracking.action.ts b/feature-libs/order/core/store/actions/consignment-tracking.action.ts index 26866946166..81cfd900edf 100644 --- a/feature-libs/order/core/store/actions/consignment-tracking.action.ts +++ b/feature-libs/order/core/store/actions/consignment-tracking.action.ts @@ -5,6 +5,7 @@ */ import { Action } from '@ngrx/store'; +import { ErrorAction } from '@spartacus/core'; import { ConsignmentTracking } from '@spartacus/order/root'; export const LOAD_CONSIGNMENT_TRACKING = '[Order] Load Consignment Tracking'; @@ -16,6 +17,7 @@ export const CLEAR_CONSIGNMENT_TRACKING = '[Order] Clear Consignment Tracking'; export class LoadConsignmentTracking implements Action { readonly type = LOAD_CONSIGNMENT_TRACKING; + constructor( public payload: { userId?: string; @@ -25,18 +27,24 @@ export class LoadConsignmentTracking implements Action { ) {} } -export class LoadConsignmentTrackingFail implements Action { +export class LoadConsignmentTrackingFail implements ErrorAction { readonly type = LOAD_CONSIGNMENT_TRACKING_FAIL; - constructor(public payload: any) {} + public error: any; + + constructor(public payload: any) { + this.error = payload; + } } export class LoadConsignmentTrackingSuccess implements Action { readonly type = LOAD_CONSIGNMENT_TRACKING_SUCCESS; + constructor(public payload: ConsignmentTracking) {} } export class ClearConsignmentTracking { readonly type = CLEAR_CONSIGNMENT_TRACKING; + constructor() { // Intentional empty constructor } diff --git a/feature-libs/order/core/store/actions/order-by-id.action.spec.ts b/feature-libs/order/core/store/actions/order-by-id.action.spec.ts index 3d4f4ac646d..ca6cb963205 100644 --- a/feature-libs/order/core/store/actions/order-by-id.action.spec.ts +++ b/feature-libs/order/core/store/actions/order-by-id.action.spec.ts @@ -43,6 +43,7 @@ describe('Order By Id Actions', () => { 'order1', 'error' ), + error: 'error', }); }); }); diff --git a/feature-libs/order/core/store/actions/order-by-id.action.ts b/feature-libs/order/core/store/actions/order-by-id.action.ts index 1ee032efdf5..ce60bac8198 100644 --- a/feature-libs/order/core/store/actions/order-by-id.action.ts +++ b/feature-libs/order/core/store/actions/order-by-id.action.ts @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { StateUtils } from '@spartacus/core'; +import { ErrorAction, StateUtils } from '@spartacus/core'; import { Order } from '@spartacus/order/root'; import { ORDER_BY_ID_ENTITIES } from '../order-state'; @@ -18,7 +18,10 @@ export class LoadOrderById extends StateUtils.EntityLoadAction { } } -export class LoadOrderByIdFail extends StateUtils.EntityFailAction { +export class LoadOrderByIdFail + extends StateUtils.EntityFailAction + implements ErrorAction +{ readonly type = LOAD_ORDER_BY_ID_FAIL; constructor(public payload: { code: string; error: any }) { super(ORDER_BY_ID_ENTITIES, payload.code, payload.error); diff --git a/feature-libs/order/core/store/actions/order-details.action.spec.ts b/feature-libs/order/core/store/actions/order-details.action.spec.ts index 8ff22f17c94..d6fa53c9687 100644 --- a/feature-libs/order/core/store/actions/order-details.action.spec.ts +++ b/feature-libs/order/core/store/actions/order-details.action.spec.ts @@ -25,12 +25,13 @@ describe('Order Details Actions', () => { describe('LoadOrderDetailsFail Action', () => { it('should create the action', () => { - const error = 'mockError'; + const error = new Error('mockError'); const action = new OrderActions.LoadOrderDetailsFail(error); expect({ ...action }).toEqual({ type: OrderActions.LOAD_ORDER_DETAILS_FAIL, payload: error, + error, meta: StateUtils.failMeta(ORDER_DETAILS, error), }); }); @@ -81,12 +82,13 @@ describe('Order Details Actions', () => { describe('CancelOrderFail Action', () => { it('should create the action', () => { - const error = 'mockError'; + const error = new Error('mockError'); const action = new OrderActions.CancelOrderFail(error); expect({ ...action }).toEqual({ type: OrderActions.CANCEL_ORDER_FAIL, payload: error, + error, meta: StateUtils.entityFailMeta( PROCESS_FEATURE, CANCEL_ORDER_PROCESS_ID, diff --git a/feature-libs/order/core/store/actions/order-details.action.ts b/feature-libs/order/core/store/actions/order-details.action.ts index a17248e326f..87b6db9347e 100644 --- a/feature-libs/order/core/store/actions/order-details.action.ts +++ b/feature-libs/order/core/store/actions/order-details.action.ts @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { PROCESS_FEATURE, StateUtils } from '@spartacus/core'; +import { ErrorAction, PROCESS_FEATURE, StateUtils } from '@spartacus/core'; import { CancellationRequestEntryInputList, Order, @@ -33,7 +33,10 @@ export class LoadOrderDetails extends StateUtils.LoaderLoadAction { } } -export class LoadOrderDetailsFail extends StateUtils.LoaderFailAction { +export class LoadOrderDetailsFail + extends StateUtils.LoaderFailAction + implements ErrorAction +{ readonly type = LOAD_ORDER_DETAILS_FAIL; constructor(public payload: any) { super(ORDER_DETAILS, payload); @@ -67,7 +70,10 @@ export class CancelOrder extends StateUtils.EntityLoadAction { } } -export class CancelOrderFail extends StateUtils.EntityFailAction { +export class CancelOrderFail + extends StateUtils.EntityFailAction + implements ErrorAction +{ readonly type = CANCEL_ORDER_FAIL; constructor(public payload: any) { super(PROCESS_FEATURE, CANCEL_ORDER_PROCESS_ID, payload); diff --git a/feature-libs/order/core/store/actions/order-return-request.action.spec.ts b/feature-libs/order/core/store/actions/order-return-request.action.spec.ts index 0fd628827cb..850ead25b13 100644 --- a/feature-libs/order/core/store/actions/order-return-request.action.spec.ts +++ b/feature-libs/order/core/store/actions/order-return-request.action.spec.ts @@ -56,12 +56,13 @@ describe('Order Return Request actions', () => { describe('CreateOrderReturnRequestFail Action', () => { it('should create the action', () => { - const error = 'mockError'; + const error = new Error('mockError'); const action = new OrderActions.CreateOrderReturnRequestFail(error); expect({ ...action }).toEqual({ type: OrderActions.CREATE_ORDER_RETURN_REQUEST_FAIL, payload: error, + error, meta: StateUtils.failMeta(RETURN_REQUEST_DETAILS, error), }); }); @@ -101,12 +102,13 @@ describe('Order Return Request actions', () => { describe('LoadOrderReturnRequestFail Action', () => { it('should create the action', () => { - const error = 'mockError'; + const error = new Error('mockError'); const action = new OrderActions.LoadOrderReturnRequestFail(error); expect({ ...action }).toEqual({ type: OrderActions.LOAD_ORDER_RETURN_REQUEST_FAIL, payload: error, + error, meta: StateUtils.failMeta(RETURN_REQUEST_DETAILS, error), }); }); @@ -151,12 +153,13 @@ describe('Order Return Request actions', () => { describe('CancelOrderReturnRequestFail Action', () => { it('should create the action', () => { - const error = 'mockError'; + const error = new Error('mockError'); const action = new OrderActions.CancelOrderReturnRequestFail(error); expect({ ...action }).toEqual({ type: OrderActions.CANCEL_ORDER_RETURN_REQUEST_FAIL, payload: error, + error, meta: StateUtils.entityFailMeta( PROCESS_FEATURE, CANCEL_RETURN_PROCESS_ID, @@ -197,12 +200,13 @@ describe('Order Return Request actions', () => { describe('LoadOrderReturnRequestListFail Action', () => { it('should create the action', () => { - const error = 'mockError'; + const error = new Error('mockError'); const action = new OrderActions.LoadOrderReturnRequestListFail(error); expect({ ...action }).toEqual({ type: OrderActions.LOAD_ORDER_RETURN_REQUEST_LIST_FAIL, payload: error, + error, meta: StateUtils.failMeta(RETURN_REQUESTS, error), }); }); diff --git a/feature-libs/order/core/store/actions/order-return-request.action.ts b/feature-libs/order/core/store/actions/order-return-request.action.ts index 127c9334adf..e8bcef25884 100644 --- a/feature-libs/order/core/store/actions/order-return-request.action.ts +++ b/feature-libs/order/core/store/actions/order-return-request.action.ts @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { PROCESS_FEATURE, StateUtils } from '@spartacus/core'; +import { ErrorAction, PROCESS_FEATURE, StateUtils } from '@spartacus/core'; import { ReturnRequest, ReturnRequestEntryInputList, @@ -54,6 +54,7 @@ export const RESET_CANCEL_RETURN_PROCESS = export class CreateOrderReturnRequest extends StateUtils.LoaderLoadAction { readonly type = CREATE_ORDER_RETURN_REQUEST; + constructor( public payload: { userId: string; @@ -64,8 +65,12 @@ export class CreateOrderReturnRequest extends StateUtils.LoaderLoadAction { } } -export class CreateOrderReturnRequestFail extends StateUtils.LoaderFailAction { +export class CreateOrderReturnRequestFail + extends StateUtils.LoaderFailAction + implements ErrorAction +{ readonly type = CREATE_ORDER_RETURN_REQUEST_FAIL; + constructor(public payload: any) { super(RETURN_REQUEST_DETAILS, payload); } @@ -73,6 +78,7 @@ export class CreateOrderReturnRequestFail extends StateUtils.LoaderFailAction { export class CreateOrderReturnRequestSuccess extends StateUtils.LoaderSuccessAction { readonly type = CREATE_ORDER_RETURN_REQUEST_SUCCESS; + constructor(public payload: ReturnRequest) { super(RETURN_REQUEST_DETAILS); } @@ -80,6 +86,7 @@ export class CreateOrderReturnRequestSuccess extends StateUtils.LoaderSuccessAct export class LoadOrderReturnRequest extends StateUtils.LoaderLoadAction { readonly type = LOAD_ORDER_RETURN_REQUEST; + constructor( public payload: { userId: string; @@ -90,8 +97,12 @@ export class LoadOrderReturnRequest extends StateUtils.LoaderLoadAction { } } -export class LoadOrderReturnRequestFail extends StateUtils.LoaderFailAction { +export class LoadOrderReturnRequestFail + extends StateUtils.LoaderFailAction + implements ErrorAction +{ readonly type = LOAD_ORDER_RETURN_REQUEST_FAIL; + constructor(public payload: any) { super(RETURN_REQUEST_DETAILS, payload); } @@ -99,6 +110,7 @@ export class LoadOrderReturnRequestFail extends StateUtils.LoaderFailAction { export class LoadOrderReturnRequestSuccess extends StateUtils.LoaderSuccessAction { readonly type = LOAD_ORDER_RETURN_REQUEST_SUCCESS; + constructor(public payload: ReturnRequest) { super(RETURN_REQUEST_DETAILS); } @@ -106,6 +118,7 @@ export class LoadOrderReturnRequestSuccess extends StateUtils.LoaderSuccessActio export class CancelOrderReturnRequest extends StateUtils.EntityLoadAction { readonly type = CANCEL_ORDER_RETURN_REQUEST; + constructor( public payload: { userId: string; @@ -117,8 +130,12 @@ export class CancelOrderReturnRequest extends StateUtils.EntityLoadAction { } } -export class CancelOrderReturnRequestFail extends StateUtils.EntityFailAction { +export class CancelOrderReturnRequestFail + extends StateUtils.EntityFailAction + implements ErrorAction +{ readonly type = CANCEL_ORDER_RETURN_REQUEST_FAIL; + constructor(public payload: any) { super(PROCESS_FEATURE, CANCEL_RETURN_PROCESS_ID, payload); } @@ -126,6 +143,7 @@ export class CancelOrderReturnRequestFail extends StateUtils.EntityFailAction { export class CancelOrderReturnRequestSuccess extends StateUtils.EntitySuccessAction { readonly type = CANCEL_ORDER_RETURN_REQUEST_SUCCESS; + constructor() { super(PROCESS_FEATURE, CANCEL_RETURN_PROCESS_ID); } @@ -133,6 +151,7 @@ export class CancelOrderReturnRequestSuccess extends StateUtils.EntitySuccessAct export class LoadOrderReturnRequestList extends StateUtils.LoaderLoadAction { readonly type = LOAD_ORDER_RETURN_REQUEST_LIST; + constructor( public payload: { userId: string; @@ -145,8 +164,12 @@ export class LoadOrderReturnRequestList extends StateUtils.LoaderLoadAction { } } -export class LoadOrderReturnRequestListFail extends StateUtils.LoaderFailAction { +export class LoadOrderReturnRequestListFail + extends StateUtils.LoaderFailAction + implements ErrorAction +{ readonly type = LOAD_ORDER_RETURN_REQUEST_LIST_FAIL; + constructor(public payload: any) { super(RETURN_REQUESTS, payload); } @@ -154,6 +177,7 @@ export class LoadOrderReturnRequestListFail extends StateUtils.LoaderFailAction export class LoadOrderReturnRequestListSuccess extends StateUtils.LoaderSuccessAction { readonly type = LOAD_ORDER_RETURN_REQUEST_LIST_SUCCESS; + constructor(public payload: ReturnRequestList) { super(RETURN_REQUESTS); } @@ -161,6 +185,7 @@ export class LoadOrderReturnRequestListSuccess extends StateUtils.LoaderSuccessA export class ClearOrderReturnRequest extends StateUtils.LoaderResetAction { readonly type = CLEAR_ORDER_RETURN_REQUEST; + constructor() { super(RETURN_REQUEST_DETAILS); } @@ -168,6 +193,7 @@ export class ClearOrderReturnRequest extends StateUtils.LoaderResetAction { export class ClearOrderReturnRequestList extends StateUtils.LoaderResetAction { readonly type = CLEAR_ORDER_RETURN_REQUEST_LIST; + constructor() { super(RETURN_REQUESTS); } @@ -175,6 +201,7 @@ export class ClearOrderReturnRequestList extends StateUtils.LoaderResetAction { export class ResetCancelReturnProcess extends StateUtils.EntityLoaderResetAction { readonly type = RESET_CANCEL_RETURN_PROCESS; + constructor() { super(PROCESS_FEATURE, CANCEL_RETURN_PROCESS_ID); } diff --git a/feature-libs/order/core/store/actions/orders.action.spec.ts b/feature-libs/order/core/store/actions/orders.action.spec.ts index c6a97bfb08d..02c93b013cd 100644 --- a/feature-libs/order/core/store/actions/orders.action.spec.ts +++ b/feature-libs/order/core/store/actions/orders.action.spec.ts @@ -32,12 +32,13 @@ describe('OrdersActions', () => { describe('LoadUserOrdersFail Action', () => { it('should create the action', () => { - const error = 'mockError'; + const error = new Error('mockError'); const action = new OrderActions.LoadUserOrdersFail(error); expect({ ...action }).toEqual({ type: OrderActions.LOAD_USER_ORDERS_FAIL, payload: error, + error, meta: StateUtils.failMeta(ORDERS, error), }); }); diff --git a/feature-libs/order/core/store/actions/orders.action.ts b/feature-libs/order/core/store/actions/orders.action.ts index 465393a5729..3a05a2b0401 100644 --- a/feature-libs/order/core/store/actions/orders.action.ts +++ b/feature-libs/order/core/store/actions/orders.action.ts @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { StateUtils } from '@spartacus/core'; +import { ErrorAction, StateUtils } from '@spartacus/core'; import { OrderHistoryList } from '@spartacus/order/root'; import { ORDERS } from '../order-state'; @@ -15,6 +15,7 @@ export const CLEAR_USER_ORDERS = '[Order] Clear User Orders'; export class LoadUserOrders extends StateUtils.LoaderLoadAction { readonly type = LOAD_USER_ORDERS; + constructor( public payload: { userId: string; @@ -28,8 +29,12 @@ export class LoadUserOrders extends StateUtils.LoaderLoadAction { } } -export class LoadUserOrdersFail extends StateUtils.LoaderFailAction { +export class LoadUserOrdersFail + extends StateUtils.LoaderFailAction + implements ErrorAction +{ readonly type = LOAD_USER_ORDERS_FAIL; + constructor(public payload: any) { super(ORDERS, payload); } @@ -37,6 +42,7 @@ export class LoadUserOrdersFail extends StateUtils.LoaderFailAction { export class LoadUserOrdersSuccess extends StateUtils.LoaderSuccessAction { readonly type = LOAD_USER_ORDERS_SUCCESS; + constructor(public payload: OrderHistoryList) { super(ORDERS); } @@ -44,6 +50,7 @@ export class LoadUserOrdersSuccess extends StateUtils.LoaderSuccessAction { export class ClearUserOrders extends StateUtils.LoaderResetAction { readonly type = CLEAR_USER_ORDERS; + constructor() { super(ORDERS); } diff --git a/feature-libs/order/core/store/actions/replenishment-order-details.action.spec.ts b/feature-libs/order/core/store/actions/replenishment-order-details.action.spec.ts index 929882c90e2..5ed4edb4989 100644 --- a/feature-libs/order/core/store/actions/replenishment-order-details.action.spec.ts +++ b/feature-libs/order/core/store/actions/replenishment-order-details.action.spec.ts @@ -7,7 +7,7 @@ import { import { OrderActions } from './index'; const mockUserId = 'test-user'; const mockReplenishmentOrderCode = 'test-repl-code'; -const mockError = 'test-error'; +const error = { message: 'test-error' }; const mockReplenishmentOrder: ReplenishmentOrder = { active: true, @@ -55,15 +55,15 @@ describe('ReplenishmentOrderActions', () => { describe('LoadReplenishmentOrderDetailsFail action', () => { it('should create an action', () => { - const payload = mockError; const action = new OrderActions.LoadReplenishmentOrderDetailsFail( - payload + error ); expect({ ...action }).toEqual({ type: OrderActions.LOAD_REPLENISHMENT_ORDER_DETAILS_FAIL, - payload, - meta: StateUtils.failMeta(REPLENISHMENT_ORDER_DETAILS, payload), + payload: error, + error, + meta: StateUtils.failMeta(REPLENISHMENT_ORDER_DETAILS, error), }); }); }); @@ -123,16 +123,16 @@ describe('ReplenishmentOrderActions', () => { describe('CancelReplenishmentOrderFail action', () => { it('should create an action', () => { - const payload = mockError; - const action = new OrderActions.CancelReplenishmentOrderFail(payload); + const action = new OrderActions.CancelReplenishmentOrderFail(error); expect({ ...action }).toEqual({ type: OrderActions.CANCEL_REPLENISHMENT_ORDER_FAIL, - payload, + payload: error, + error, meta: StateUtils.entityFailMeta( PROCESS_FEATURE, CANCEL_REPLENISHMENT_ORDER_PROCESS_ID, - payload + error ), }); }); diff --git a/feature-libs/order/core/store/actions/replenishment-order-details.action.ts b/feature-libs/order/core/store/actions/replenishment-order-details.action.ts index 9fc45715438..5f3645fe3af 100644 --- a/feature-libs/order/core/store/actions/replenishment-order-details.action.ts +++ b/feature-libs/order/core/store/actions/replenishment-order-details.action.ts @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { PROCESS_FEATURE, StateUtils } from '@spartacus/core'; +import { ErrorAction, PROCESS_FEATURE, StateUtils } from '@spartacus/core'; import { ReplenishmentOrder } from '@spartacus/order/root'; import { CANCEL_REPLENISHMENT_ORDER_PROCESS_ID, @@ -30,6 +30,7 @@ export const CLEAR_CANCEL_REPLENISHMENT_ORDER = export class LoadReplenishmentOrderDetails extends StateUtils.LoaderLoadAction { readonly type = LOAD_REPLENISHMENT_ORDER_DETAILS; + constructor( public payload: { userId: string; @@ -42,13 +43,18 @@ export class LoadReplenishmentOrderDetails extends StateUtils.LoaderLoadAction { export class LoadReplenishmentOrderDetailsSuccess extends StateUtils.LoaderSuccessAction { readonly type = LOAD_REPLENISHMENT_ORDER_DETAILS_SUCCESS; + constructor(public payload: ReplenishmentOrder) { super(REPLENISHMENT_ORDER_DETAILS); } } -export class LoadReplenishmentOrderDetailsFail extends StateUtils.LoaderFailAction { +export class LoadReplenishmentOrderDetailsFail + extends StateUtils.LoaderFailAction + implements ErrorAction +{ readonly type = LOAD_REPLENISHMENT_ORDER_DETAILS_FAIL; + constructor(public payload: any) { super(REPLENISHMENT_ORDER_DETAILS, payload); } @@ -56,6 +62,7 @@ export class LoadReplenishmentOrderDetailsFail extends StateUtils.LoaderFailActi export class ClearReplenishmentOrderDetails extends StateUtils.LoaderResetAction { readonly type = ClEAR_REPLENISHMENT_ORDER_DETAILS; + constructor() { super(REPLENISHMENT_ORDER_DETAILS); } @@ -63,6 +70,7 @@ export class ClearReplenishmentOrderDetails extends StateUtils.LoaderResetAction export class CancelReplenishmentOrder extends StateUtils.EntityLoadAction { readonly type = CANCEL_REPLENISHMENT_ORDER; + constructor( public payload: { userId: string; @@ -75,13 +83,18 @@ export class CancelReplenishmentOrder extends StateUtils.EntityLoadAction { export class CancelReplenishmentOrderSuccess extends StateUtils.EntitySuccessAction { readonly type = CANCEL_REPLENISHMENT_ORDER_SUCCESS; + constructor(public payload: ReplenishmentOrder) { super(PROCESS_FEATURE, CANCEL_REPLENISHMENT_ORDER_PROCESS_ID); } } -export class CancelReplenishmentOrderFail extends StateUtils.EntityFailAction { +export class CancelReplenishmentOrderFail + extends StateUtils.EntityFailAction + implements ErrorAction +{ readonly type = CANCEL_REPLENISHMENT_ORDER_FAIL; + constructor(public payload: any) { super(PROCESS_FEATURE, CANCEL_REPLENISHMENT_ORDER_PROCESS_ID, payload); } @@ -89,6 +102,7 @@ export class CancelReplenishmentOrderFail extends StateUtils.EntityFailAction { export class ClearCancelReplenishmentOrder extends StateUtils.EntityLoaderResetAction { readonly type = CLEAR_CANCEL_REPLENISHMENT_ORDER; + constructor() { super(PROCESS_FEATURE, CANCEL_REPLENISHMENT_ORDER_PROCESS_ID); } diff --git a/feature-libs/order/core/store/actions/replenishment-orders.action.spec.ts b/feature-libs/order/core/store/actions/replenishment-orders.action.spec.ts index 00e8c36b628..9182a61fb43 100644 --- a/feature-libs/order/core/store/actions/replenishment-orders.action.spec.ts +++ b/feature-libs/order/core/store/actions/replenishment-orders.action.spec.ts @@ -31,12 +31,13 @@ describe('Replenishment Orders Actions', () => { describe('LoadUserReplenishmentOrdersFail Action', () => { it('should create the action', () => { - const error = 'mockError'; + const error = new Error('mockError'); const action = new OrderActions.LoadUserReplenishmentOrdersFail(error); expect({ ...action }).toEqual({ type: OrderActions.LOAD_USER_REPLENISHMENT_ORDERS_FAIL, payload: error, + error, meta: StateUtils.failMeta(REPLENISHMENT_ORDERS, error), }); }); diff --git a/feature-libs/order/core/store/actions/replenishment-orders.action.ts b/feature-libs/order/core/store/actions/replenishment-orders.action.ts index a011db32c87..9753e57bbdf 100644 --- a/feature-libs/order/core/store/actions/replenishment-orders.action.ts +++ b/feature-libs/order/core/store/actions/replenishment-orders.action.ts @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { StateUtils } from '@spartacus/core'; +import { ErrorAction, StateUtils } from '@spartacus/core'; import { ReplenishmentOrderList } from '@spartacus/order/root'; import { REPLENISHMENT_ORDERS } from '../order-state'; @@ -19,6 +19,7 @@ export const CLEAR_USER_REPLENISHMENT_ORDERS = export class LoadUserReplenishmentOrders extends StateUtils.LoaderLoadAction { readonly type = LOAD_USER_REPLENISHMENT_ORDERS; + constructor( public payload: { userId: string; @@ -31,8 +32,12 @@ export class LoadUserReplenishmentOrders extends StateUtils.LoaderLoadAction { } } -export class LoadUserReplenishmentOrdersFail extends StateUtils.LoaderFailAction { +export class LoadUserReplenishmentOrdersFail + extends StateUtils.LoaderFailAction + implements ErrorAction +{ readonly type = LOAD_USER_REPLENISHMENT_ORDERS_FAIL; + constructor(public payload: any) { super(REPLENISHMENT_ORDERS, payload); } @@ -40,6 +45,7 @@ export class LoadUserReplenishmentOrdersFail extends StateUtils.LoaderFailAction export class LoadUserReplenishmentOrdersSuccess extends StateUtils.LoaderSuccessAction { readonly type = LOAD_USER_REPLENISHMENT_ORDERS_SUCCESS; + constructor(public payload: ReplenishmentOrderList) { super(REPLENISHMENT_ORDERS); } @@ -47,6 +53,7 @@ export class LoadUserReplenishmentOrdersSuccess extends StateUtils.LoaderSuccess export class ClearUserReplenishmentOrders extends StateUtils.LoaderResetAction { readonly type = CLEAR_USER_REPLENISHMENT_ORDERS; + constructor() { super(REPLENISHMENT_ORDERS); } diff --git a/feature-libs/order/core/store/effects/consignment-tracking-by-id.effect.spec.ts b/feature-libs/order/core/store/effects/consignment-tracking-by-id.effect.spec.ts index 0d58937ab8d..f6bd572a29b 100644 --- a/feature-libs/order/core/store/effects/consignment-tracking-by-id.effect.spec.ts +++ b/feature-libs/order/core/store/effects/consignment-tracking-by-id.effect.spec.ts @@ -2,7 +2,11 @@ import { HttpClientTestingModule } from '@angular/common/http/testing'; import { TestBed } from '@angular/core/testing'; import { Actions } from '@ngrx/effects'; import { provideMockActions } from '@ngrx/effects/testing'; -import { LoggerService, normalizeHttpError, OccConfig } from '@spartacus/core'; +import { + LoggerService, + OccConfig, + tryNormalizeHttpError, +} from '@spartacus/core'; import { ConsignmentTracking } from '@spartacus/order/root'; import { cold, hot } from 'jasmine-marbles'; import { Observable, of, throwError } from 'rxjs'; @@ -74,8 +78,9 @@ describe('Consignment Tracking By Id effect', () => { }); it('should handle failures for load consignment tracking by id', () => { + const error = new Error('error'); spyOn(orderHistoryConnector, 'getConsignmentTracking').and.returnValue( - throwError('Error') + throwError(() => error) ); const action = new OrderActions.LoadConsignmentTrackingById( @@ -85,7 +90,7 @@ describe('Consignment Tracking By Id effect', () => { const completion = new OrderActions.LoadConsignmentTrackingByIdFail({ orderCode: mockTrackingParams.orderCode, consignmentCode: mockTrackingParams.consignmentCode, - error: normalizeHttpError('Error', new MockLoggerService()), + error: tryNormalizeHttpError(error, new MockLoggerService()), }); actions$ = hot('-a', { a: action }); diff --git a/feature-libs/order/core/store/effects/consignment-tracking-by-id.effect.ts b/feature-libs/order/core/store/effects/consignment-tracking-by-id.effect.ts index 9ad511ae733..67ba82444e3 100644 --- a/feature-libs/order/core/store/effects/consignment-tracking-by-id.effect.ts +++ b/feature-libs/order/core/store/effects/consignment-tracking-by-id.effect.ts @@ -6,7 +6,7 @@ import { inject, Injectable } from '@angular/core'; import { Actions, createEffect, ofType } from '@ngrx/effects'; -import { LoggerService, normalizeHttpError } from '@spartacus/core'; +import { LoggerService, tryNormalizeHttpError } from '@spartacus/core'; import { ConsignmentTracking } from '@spartacus/order/root'; import { Observable, of } from 'rxjs'; import { catchError, map, switchMap } from 'rxjs/operators'; @@ -47,7 +47,7 @@ export class ConsignmentTrackingByIdEffects { new OrderActions.LoadConsignmentTrackingByIdFail({ orderCode: payload.orderCode, consignmentCode: payload.consignmentCode, - error: normalizeHttpError(error, this.logger), + error: tryNormalizeHttpError(error, this.logger), }) ) ) diff --git a/feature-libs/order/core/store/effects/consignment-tracking.effect.spec.ts b/feature-libs/order/core/store/effects/consignment-tracking.effect.spec.ts index 2c1d2aa4681..704e19e8e3f 100644 --- a/feature-libs/order/core/store/effects/consignment-tracking.effect.spec.ts +++ b/feature-libs/order/core/store/effects/consignment-tracking.effect.spec.ts @@ -79,17 +79,16 @@ describe('Consignment Tracking effect', () => { }); it('should handle failures for load consignment tracking', () => { + const error = new Error('error'); spyOn(orderHistoryConnector, 'getConsignmentTracking').and.returnValue( - throwError(() => 'Error') + throwError(() => error) ); const action = new OrderActions.LoadConsignmentTracking( mockTrackingParams ); - const completion = new OrderActions.LoadConsignmentTrackingFail( - undefined - ); + const completion = new OrderActions.LoadConsignmentTrackingFail(error); actions$ = hot('-a', { a: action }); const expected = cold('-b', { b: completion }); diff --git a/feature-libs/order/core/store/effects/consignment-tracking.effect.ts b/feature-libs/order/core/store/effects/consignment-tracking.effect.ts index 603da8ae04b..fc47ba29160 100644 --- a/feature-libs/order/core/store/effects/consignment-tracking.effect.ts +++ b/feature-libs/order/core/store/effects/consignment-tracking.effect.ts @@ -6,7 +6,7 @@ import { Injectable, inject } from '@angular/core'; import { Actions, createEffect, ofType } from '@ngrx/effects'; -import { LoggerService, normalizeHttpError } from '@spartacus/core'; +import { LoggerService, tryNormalizeHttpError } from '@spartacus/core'; import { ConsignmentTracking } from '@spartacus/order/root'; import { Observable, of } from 'rxjs'; import { catchError, map, switchMap } from 'rxjs/operators'; @@ -37,7 +37,7 @@ export class ConsignmentTrackingEffects { catchError((error) => of( new OrderActions.LoadConsignmentTrackingFail( - normalizeHttpError(error, this.logger) + tryNormalizeHttpError(error, this.logger) ) ) ) diff --git a/feature-libs/order/core/store/effects/order-by-id.effect.spec.ts b/feature-libs/order/core/store/effects/order-by-id.effect.spec.ts index 1823a6119d9..93cf31a7a0d 100644 --- a/feature-libs/order/core/store/effects/order-by-id.effect.spec.ts +++ b/feature-libs/order/core/store/effects/order-by-id.effect.spec.ts @@ -2,7 +2,11 @@ import { HttpClientTestingModule } from '@angular/common/http/testing'; import { TestBed } from '@angular/core/testing'; import { Actions } from '@ngrx/effects'; import { provideMockActions } from '@ngrx/effects/testing'; -import { LoggerService, normalizeHttpError, OccConfig } from '@spartacus/core'; +import { + LoggerService, + OccConfig, + tryNormalizeHttpError, +} from '@spartacus/core'; import { Order } from '@spartacus/order/root'; import { cold, hot } from 'jasmine-marbles'; import { Observable, of, throwError } from 'rxjs'; @@ -67,13 +71,16 @@ describe('Order By Id effect', () => { }); it('should handle failures for load order by id', () => { - spyOn(orderHistoryConnector, 'get').and.returnValue(throwError('Error')); + const error = new Error('error'); + spyOn(orderHistoryConnector, 'get').and.returnValue( + throwError(() => error) + ); const action = new OrderActions.LoadOrderById(mockOrderParams); const completion = new OrderActions.LoadOrderByIdFail({ code: mockOrderParams.code, - error: normalizeHttpError('Error', new MockLoggerService()), + error: tryNormalizeHttpError(error, new MockLoggerService()), }); actions$ = hot('-a', { a: action }); diff --git a/feature-libs/order/core/store/effects/order-by-id.effect.ts b/feature-libs/order/core/store/effects/order-by-id.effect.ts index 6bb6d9c681b..826508025fe 100644 --- a/feature-libs/order/core/store/effects/order-by-id.effect.ts +++ b/feature-libs/order/core/store/effects/order-by-id.effect.ts @@ -7,7 +7,7 @@ import { HttpErrorResponse } from '@angular/common/http'; import { inject, Injectable } from '@angular/core'; import { Actions, createEffect, ofType } from '@ngrx/effects'; -import { LoggerService, normalizeHttpError } from '@spartacus/core'; +import { LoggerService, tryNormalizeHttpError } from '@spartacus/core'; import { Order } from '@spartacus/order/root'; import { Observable, of } from 'rxjs'; import { catchError, concatMap, map } from 'rxjs/operators'; @@ -34,7 +34,7 @@ export class OrderByIdEffect { return of( new OrderActions.LoadOrderByIdFail({ code, - error: normalizeHttpError(error, this.logger), + error: tryNormalizeHttpError(error, this.logger), }) ); }) diff --git a/feature-libs/order/core/store/effects/order-details.effect.spec.ts b/feature-libs/order/core/store/effects/order-details.effect.spec.ts index 604582eb2a0..d6a70c38153 100644 --- a/feature-libs/order/core/store/effects/order-details.effect.spec.ts +++ b/feature-libs/order/core/store/effects/order-details.effect.spec.ts @@ -49,6 +49,8 @@ class MockUserIdService implements Partial { } } +const error = new Error('error'); + describe('Order Details effect', () => { let orderDetailsEffect: fromOrderDetailsEffect.OrderDetailsEffect; let orderHistoryConnector: OrderHistoryConnector; @@ -100,12 +102,12 @@ describe('Order Details effect', () => { it('should handle failures for load order details', () => { spyOn(orderHistoryConnector, 'get').and.returnValue( - throwError(() => 'Error') + throwError(() => error) ); const action = new OrderActions.LoadOrderDetails(mockOrderDetailsParams); - const completion = new OrderActions.LoadOrderDetailsFail(undefined); + const completion = new OrderActions.LoadOrderDetailsFail(error); actions$ = hot('-a', { a: action }); const expected = cold('-b', { b: completion }); @@ -130,12 +132,12 @@ describe('Order Details effect', () => { it('should handle failures for cancel an order', () => { spyOn(orderHistoryConnector, 'cancel').and.returnValue( - throwError(() => 'Error') + throwError(() => error) ); const action = new OrderActions.CancelOrder(mockCancelOrderParams); - const completion = new OrderActions.CancelOrderFail(undefined); + const completion = new OrderActions.CancelOrderFail(error); actions$ = hot('-a', { a: action }); const expected = cold('-b', { b: completion }); diff --git a/feature-libs/order/core/store/effects/order-details.effect.ts b/feature-libs/order/core/store/effects/order-details.effect.ts index 526c2430351..c858bf09d39 100644 --- a/feature-libs/order/core/store/effects/order-details.effect.ts +++ b/feature-libs/order/core/store/effects/order-details.effect.ts @@ -13,7 +13,7 @@ import { LoggerService, SiteContextActions, UserIdService, - normalizeHttpError, + tryNormalizeHttpError, } from '@spartacus/core'; import { Order } from '@spartacus/order/root'; import { EMPTY, Observable, of } from 'rxjs'; @@ -47,7 +47,7 @@ export class OrderDetailsEffect { catchError((error) => of( new OrderActions.LoadOrderDetailsFail( - normalizeHttpError(error, this.logger) + tryNormalizeHttpError(error, this.logger) ) ) ) @@ -75,7 +75,7 @@ export class OrderDetailsEffect { return of( new OrderActions.CancelOrderFail( - normalizeHttpError(error, this.logger) + tryNormalizeHttpError(error, this.logger) ) ); }) @@ -108,7 +108,7 @@ export class OrderDetailsEffect { catchError((error) => of( new OrderActions.LoadOrderDetailsFail( - normalizeHttpError(error, this.logger) + tryNormalizeHttpError(error, this.logger) ) ) ) diff --git a/feature-libs/order/core/store/effects/order-return-request.effect.spec.ts b/feature-libs/order/core/store/effects/order-return-request.effect.spec.ts index dc5db9648a6..8127b7b1bf4 100644 --- a/feature-libs/order/core/store/effects/order-return-request.effect.spec.ts +++ b/feature-libs/order/core/store/effects/order-return-request.effect.spec.ts @@ -36,6 +36,7 @@ const mockCancelReturnRequest = { returnRequestModification: {}, }; +const error = new Error('error'); class MockLoggerService { log(): void {} warn(): void {} @@ -92,7 +93,7 @@ describe('Order Return Request effect', () => { it('should handle failures for create order return request', () => { spyOn(orderHistoryConnector, 'return').and.returnValue( - throwError(() => 'Error') + throwError(() => error) ); const action = new OrderActions.CreateOrderReturnRequest({ @@ -100,9 +101,7 @@ describe('Order Return Request effect', () => { returnRequestInput, }); - const completion = new OrderActions.CreateOrderReturnRequestFail( - undefined - ); + const completion = new OrderActions.CreateOrderReturnRequestFail(error); actions$ = hot('-a', { a: action }); const expected = cold('-b', { b: completion }); @@ -137,16 +136,14 @@ describe('Order Return Request effect', () => { it('should handle failures for load return request list', () => { spyOn(orderHistoryConnector, 'getReturnRequestList').and.returnValue( - throwError(() => 'Error') + throwError(() => error) ); const action = new OrderActions.LoadOrderReturnRequestList({ userId: 'test@sap.com', pageSize: 5, }); - const completion = new OrderActions.LoadOrderReturnRequestListFail( - undefined - ); + const completion = new OrderActions.LoadOrderReturnRequestListFail(error); actions$ = hot('-a', { a: action }); const expected = cold('-b', { b: completion }); @@ -181,7 +178,7 @@ describe('Order Return Request effect', () => { it('should handle failures for load an order return request', () => { spyOn(orderHistoryConnector, 'getReturnRequestDetail').and.returnValue( - throwError(() => 'Error') + throwError(() => error) ); const action = new OrderActions.LoadOrderReturnRequest({ @@ -189,7 +186,7 @@ describe('Order Return Request effect', () => { returnRequestCode: 'test', }); - const completion = new OrderActions.LoadOrderReturnRequestFail(undefined); + const completion = new OrderActions.LoadOrderReturnRequestFail(error); actions$ = hot('-a', { a: action }); const expected = cold('-b', { b: completion }); @@ -222,16 +219,14 @@ describe('Order Return Request effect', () => { it('should handle failures for cancel return request', () => { spyOn(orderHistoryConnector, 'cancelReturnRequest').and.returnValue( - throwError(() => 'Error') + throwError(() => error) ); const action = new OrderActions.CancelOrderReturnRequest( mockCancelReturnRequest ); - const completion = new OrderActions.CancelOrderReturnRequestFail( - undefined - ); + const completion = new OrderActions.CancelOrderReturnRequestFail(error); actions$ = hot('-a', { a: action }); const expected = cold('-b', { b: completion }); diff --git a/feature-libs/order/core/store/effects/order-return-request.effect.ts b/feature-libs/order/core/store/effects/order-return-request.effect.ts index 6543449f5d3..1cdc7e2d530 100644 --- a/feature-libs/order/core/store/effects/order-return-request.effect.ts +++ b/feature-libs/order/core/store/effects/order-return-request.effect.ts @@ -6,7 +6,7 @@ import { Injectable, inject } from '@angular/core'; import { Actions, createEffect, ofType } from '@ngrx/effects'; -import { LoggerService, normalizeHttpError } from '@spartacus/core'; +import { LoggerService, tryNormalizeHttpError } from '@spartacus/core'; import { ReturnRequest, ReturnRequestList } from '@spartacus/order/root'; import { Observable, of } from 'rxjs'; import { catchError, map, switchMap } from 'rxjs/operators'; @@ -35,7 +35,7 @@ export class OrderReturnRequestEffect { catchError((error) => of( new OrderActions.CreateOrderReturnRequestFail( - normalizeHttpError(error, this.logger) + tryNormalizeHttpError(error, this.logger) ) ) ) @@ -60,7 +60,7 @@ export class OrderReturnRequestEffect { catchError((error) => of( new OrderActions.LoadOrderReturnRequestFail( - normalizeHttpError(error, this.logger) + tryNormalizeHttpError(error, this.logger) ) ) ) @@ -86,7 +86,7 @@ export class OrderReturnRequestEffect { catchError((error) => of( new OrderActions.CancelOrderReturnRequestFail( - normalizeHttpError(error, this.logger) + tryNormalizeHttpError(error, this.logger) ) ) ) @@ -120,7 +120,7 @@ export class OrderReturnRequestEffect { catchError((error) => of( new OrderActions.LoadOrderReturnRequestListFail( - normalizeHttpError(error, this.logger) + tryNormalizeHttpError(error, this.logger) ) ) ) diff --git a/feature-libs/order/core/store/effects/orders.effect.spec.ts b/feature-libs/order/core/store/effects/orders.effect.spec.ts index 19c52372264..77dc0eacc9b 100644 --- a/feature-libs/order/core/store/effects/orders.effect.spec.ts +++ b/feature-libs/order/core/store/effects/orders.effect.spec.ts @@ -5,8 +5,8 @@ import { provideMockActions } from '@ngrx/effects/testing'; import { Action } from '@ngrx/store'; import { LoggerService, - normalizeHttpError, SiteContextActions, + tryNormalizeHttpError, } from '@spartacus/core'; import { OrderHistoryList } from '@spartacus/order/root'; import { cold, hot } from 'jasmine-marbles'; @@ -97,7 +97,7 @@ describe('Orders effect', () => { }); const completion = new OrderActions.LoadUserOrdersFail( - normalizeHttpError(mockError, new MockLoggerService()) + tryNormalizeHttpError(mockError, new MockLoggerService()) ); actions$ = hot('-a', { a: action }); @@ -143,7 +143,7 @@ describe('Orders effect', () => { }); const completion = new OrderActions.LoadUserOrdersFail( - normalizeHttpError(mockError, new MockLoggerService()) + tryNormalizeHttpError(mockError, new MockLoggerService()) ); actions$ = hot('-a', { a: action }); diff --git a/feature-libs/order/core/store/effects/orders.effect.ts b/feature-libs/order/core/store/effects/orders.effect.ts index 08a63fd5c22..02b8f182acb 100644 --- a/feature-libs/order/core/store/effects/orders.effect.ts +++ b/feature-libs/order/core/store/effects/orders.effect.ts @@ -9,7 +9,7 @@ import { Actions, createEffect, ofType } from '@ngrx/effects'; import { LoggerService, SiteContextActions, - normalizeHttpError, + tryNormalizeHttpError, } from '@spartacus/core'; import { OrderHistoryList } from '@spartacus/order/root'; import { Observable, of } from 'rxjs'; @@ -58,7 +58,7 @@ export class OrdersEffect { catchError((error) => of( new OrderActions.LoadUserOrdersFail( - normalizeHttpError(error, this.logger) + tryNormalizeHttpError(error, this.logger) ) ) ) diff --git a/feature-libs/order/core/store/effects/replenishment-order-details.effect.spec.ts b/feature-libs/order/core/store/effects/replenishment-order-details.effect.spec.ts index 4bdcea2fefe..25df50c89c5 100644 --- a/feature-libs/order/core/store/effects/replenishment-order-details.effect.spec.ts +++ b/feature-libs/order/core/store/effects/replenishment-order-details.effect.spec.ts @@ -5,8 +5,8 @@ import { GlobalMessageService, GlobalMessageType, LoggerService, - normalizeHttpError, Translatable, + tryNormalizeHttpError, } from '@spartacus/core'; import { ReplenishmentOrder } from '@spartacus/order/root'; import { cold, hot } from 'jasmine-marbles'; @@ -112,7 +112,7 @@ describe('ReplenishmentOrderDetailsEffect', () => { replenishmentOrderCode: mockReplenishmentCode, }); const completion = new OrderActions.LoadReplenishmentOrderDetailsFail( - normalizeHttpError(mockError, new MockLoggerService()) + tryNormalizeHttpError(mockError, new MockLoggerService()) ); actions$ = hot('-a', { a: action }); @@ -156,7 +156,7 @@ describe('ReplenishmentOrderDetailsEffect', () => { replenishmentOrderCode: mockReplenishmentCode, }); const completion = new OrderActions.CancelReplenishmentOrderFail( - normalizeHttpError(mockError, new MockLoggerService()) + tryNormalizeHttpError(mockError, new MockLoggerService()) ); actions$ = hot('-a', { a: action }); diff --git a/feature-libs/order/core/store/effects/replenishment-order-details.effect.ts b/feature-libs/order/core/store/effects/replenishment-order-details.effect.ts index d60a67d28af..e13e6ae996c 100644 --- a/feature-libs/order/core/store/effects/replenishment-order-details.effect.ts +++ b/feature-libs/order/core/store/effects/replenishment-order-details.effect.ts @@ -10,7 +10,7 @@ import { GlobalMessageService, GlobalMessageType, LoggerService, - normalizeHttpError, + tryNormalizeHttpError, } from '@spartacus/core'; import { ReplenishmentOrder } from '@spartacus/order/root'; import { Observable, of } from 'rxjs'; @@ -41,7 +41,7 @@ export class ReplenishmentOrderDetailsEffect { catchError((error) => of( new OrderActions.LoadReplenishmentOrderDetailsFail( - normalizeHttpError(error, this.logger) + tryNormalizeHttpError(error, this.logger) ) ) ) @@ -78,7 +78,7 @@ export class ReplenishmentOrderDetailsEffect { return of( new OrderActions.CancelReplenishmentOrderFail( - normalizeHttpError(error, this.logger) + tryNormalizeHttpError(error, this.logger) ) ); }) diff --git a/feature-libs/order/core/store/effects/replenishment-orders.effect.spec.ts b/feature-libs/order/core/store/effects/replenishment-orders.effect.spec.ts index bdc21be111f..bdb50799b3d 100644 --- a/feature-libs/order/core/store/effects/replenishment-orders.effect.spec.ts +++ b/feature-libs/order/core/store/effects/replenishment-orders.effect.spec.ts @@ -3,7 +3,7 @@ import { TestBed } from '@angular/core/testing'; import { Actions } from '@ngrx/effects'; import { provideMockActions } from '@ngrx/effects/testing'; import { Action } from '@ngrx/store'; -import { LoggerService, normalizeHttpError } from '@spartacus/core'; +import { LoggerService, tryNormalizeHttpError } from '@spartacus/core'; import { ReplenishmentOrderList } from '@spartacus/order/root'; import { cold, hot } from 'jasmine-marbles'; import { Observable, of, throwError } from 'rxjs'; @@ -75,8 +75,9 @@ describe('Replenishment Orders effect', () => { }); it('should handle failures for load user Replenishment Orders', () => { + const error = new Error('error'); spyOn(replenishmentOrderHistoryConnector, 'loadHistory').and.returnValue( - throwError(() => 'Error') + throwError(() => error) ); const action = new OrderActions.LoadUserReplenishmentOrders({ @@ -85,7 +86,7 @@ describe('Replenishment Orders effect', () => { }); const completion = new OrderActions.LoadUserReplenishmentOrdersFail( - normalizeHttpError('Error', new MockLoggerService()) + tryNormalizeHttpError(error, new MockLoggerService()) ); actions$ = hot('-a', { a: action }); diff --git a/feature-libs/order/core/store/effects/replenishment-orders.effect.ts b/feature-libs/order/core/store/effects/replenishment-orders.effect.ts index 0adecd6136b..5b12c6a092d 100644 --- a/feature-libs/order/core/store/effects/replenishment-orders.effect.ts +++ b/feature-libs/order/core/store/effects/replenishment-orders.effect.ts @@ -6,7 +6,7 @@ import { Injectable, inject } from '@angular/core'; import { Actions, createEffect, ofType } from '@ngrx/effects'; -import { LoggerService, normalizeHttpError } from '@spartacus/core'; +import { LoggerService, tryNormalizeHttpError } from '@spartacus/core'; import { ReplenishmentOrderList } from '@spartacus/order/root'; import { Observable, of } from 'rxjs'; import { catchError, map, switchMap } from 'rxjs/operators'; @@ -41,7 +41,7 @@ export class ReplenishmentOrdersEffect { catchError((error) => of( new OrderActions.LoadUserReplenishmentOrdersFail( - normalizeHttpError(error, this.logger) + tryNormalizeHttpError(error, this.logger) ) ) ) diff --git a/feature-libs/order/core/store/reducers/consignment-tracking-by-id.reducer.spec.ts b/feature-libs/order/core/store/reducers/consignment-tracking-by-id.reducer.spec.ts index ce76b144125..9ea316f0860 100644 --- a/feature-libs/order/core/store/reducers/consignment-tracking-by-id.reducer.spec.ts +++ b/feature-libs/order/core/store/reducers/consignment-tracking-by-id.reducer.spec.ts @@ -32,10 +32,11 @@ describe('Consignment Tracking By Id Reducer', () => { describe('for LOAD_CONSIGNMENT_TRACKING_BY_ID_FAIL action', () => { it('should return the default state', () => { + const error = new Error('error'); const action = new OrderActions.LoadConsignmentTrackingByIdFail({ orderCode: 'order1', consignmentCode: 'cons1', - error: 'there is error', + error, }); const state = fromTrackingReducer.reducer( initialStateOfConsignmentTrackingById, diff --git a/feature-libs/order/core/store/reducers/orders.reducer.spec.ts b/feature-libs/order/core/store/reducers/orders.reducer.spec.ts index 4466ba846d6..1d4f27f1106 100644 --- a/feature-libs/order/core/store/reducers/orders.reducer.spec.ts +++ b/feature-libs/order/core/store/reducers/orders.reducer.spec.ts @@ -40,7 +40,7 @@ describe('Orders Reducer', () => { describe('LOAD_USER_ORDERS_FAIL action', () => { it('should return the initial state', () => { const { initialState } = fromUserOrdersReducer; - const action = new OrderActions.LoadUserOrdersFail('error'); + const action = new OrderActions.LoadUserOrdersFail(new Error('error')); const state = fromUserOrdersReducer.reducer(initialState, action); expect(state).toEqual(initialState); }); diff --git a/feature-libs/order/core/store/selectors/replenishment-order-details.selectors.spec.ts b/feature-libs/order/core/store/selectors/replenishment-order-details.selectors.spec.ts index 5eb1d6a86ca..26d00376045 100644 --- a/feature-libs/order/core/store/selectors/replenishment-order-details.selectors.spec.ts +++ b/feature-libs/order/core/store/selectors/replenishment-order-details.selectors.spec.ts @@ -114,7 +114,7 @@ describe('ReplenishmentOrderDetailsSelectors', () => { describe('getReplenishmentOrderDetailsError', () => { it('should return the boolean value from the loader state error', () => { - const mockError = 'test-error'; + const mockError = new Error('test-error'); store.dispatch( new OrderActions.LoadReplenishmentOrderDetailsFail(mockError) diff --git a/feature-libs/order/core/store/selectors/replenishment-orders.selectors.spec.ts b/feature-libs/order/core/store/selectors/replenishment-orders.selectors.spec.ts index 4d1efc39371..e336f09deb0 100644 --- a/feature-libs/order/core/store/selectors/replenishment-orders.selectors.spec.ts +++ b/feature-libs/order/core/store/selectors/replenishment-orders.selectors.spec.ts @@ -114,7 +114,7 @@ describe('ReplenishmentOrdersSelectors', () => { describe('getReplenishmentOrdersError', () => { it('should return the boolean value from the loader state error', () => { - const mockError = 'test-error'; + const mockError = new Error('test-error'); store.dispatch( new OrderActions.LoadUserReplenishmentOrdersFail(mockError) diff --git a/feature-libs/organization/administration/core/store/actions/b2b-user.action.spec.ts b/feature-libs/organization/administration/core/store/actions/b2b-user.action.spec.ts index 16d68d40e5a..a1e0f89d11f 100644 --- a/feature-libs/organization/administration/core/store/actions/b2b-user.action.spec.ts +++ b/feature-libs/organization/administration/core/store/actions/b2b-user.action.spec.ts @@ -23,7 +23,7 @@ const approverId = 'approverId'; const userGroupId = 'userGroupId'; const permissionId = 'permissionId'; const selected = true; -const error = 'anError'; +const error = { message: 'anError' }; const params = { currentPage: 2 }; const query = '?pageSize=¤tPage=2&sort='; @@ -56,6 +56,7 @@ describe('B2BUser Actions', () => { }); expect({ ...action }).toEqual({ + error, type: B2BUserActions.LOAD_B2B_USER_FAIL, payload: { orgCustomerId, error }, meta: StateUtils.entityFailMeta( @@ -132,15 +133,15 @@ describe('B2BUser Actions', () => { it('should create the action', () => { const action = new B2BUserActions.LoadB2BUsersFail({ params, - error: { error }, + error, }); expect({ ...action }).toEqual({ + error, type: B2BUserActions.LOAD_B2B_USERS_FAIL, - payload: { params, error: { error } }, - meta: StateUtils.entityFailMeta(USER_LIST, query, { - error, - }), + payload: { params, error }, + + meta: StateUtils.entityFailMeta(USER_LIST, query, error), }); }); }); @@ -201,6 +202,7 @@ describe('B2BUser Actions', () => { }); expect({ ...action }).toEqual({ + error, type: B2BUserActions.CREATE_B2B_USER_FAIL, payload: { orgCustomerId, @@ -267,6 +269,7 @@ describe('B2BUser Actions', () => { }); expect({ ...action }).toEqual({ + error, type: B2BUserActions.UPDATE_B2B_USER_FAIL, payload: { orgCustomerId, @@ -323,6 +326,7 @@ describe('B2BUser Actions', () => { }); expect({ ...action }).toEqual({ + error, type: B2BUserActions.LOAD_B2B_USER_APPROVERS_FAIL, payload: { orgCustomerId, @@ -386,13 +390,14 @@ describe('B2BUser Actions', () => { }); expect({ ...action }).toEqual({ + error, type: B2BUserActions.ASSIGN_B2B_USER_APPROVER_FAIL, payload: { orgCustomerId, approverId, error, }, - meta: StateUtils.entityFailMeta(B2B_USER_ENTITIES, approverId), + meta: StateUtils.entityFailMeta(B2B_USER_ENTITIES, approverId, error), }); }); }); @@ -437,13 +442,14 @@ describe('B2BUser Actions', () => { }); expect({ ...action }).toEqual({ + error, type: B2BUserActions.UNASSIGN_B2B_USER_APPROVER_FAIL, payload: { orgCustomerId, approverId, error, }, - meta: StateUtils.entityFailMeta(B2B_USER_ENTITIES, approverId), + meta: StateUtils.entityFailMeta(B2B_USER_ENTITIES, approverId, error), }); }); }); @@ -493,6 +499,7 @@ describe('B2BUser Actions', () => { }); expect({ ...action }).toEqual({ + error, type: B2BUserActions.LOAD_B2B_USER_PERMISSIONS_FAIL, payload: { orgCustomerId, @@ -556,6 +563,7 @@ describe('B2BUser Actions', () => { }); expect({ ...action }).toEqual({ + error, type: B2BUserActions.ASSIGN_B2B_USER_PERMISSION_FAIL, payload: { orgCustomerId, @@ -611,6 +619,7 @@ describe('B2BUser Actions', () => { }); expect({ ...action }).toEqual({ + error, type: B2BUserActions.UNASSIGN_B2B_USER_PERMISSION_FAIL, payload: { orgCustomerId, @@ -671,6 +680,7 @@ describe('B2BUser Actions', () => { }); expect({ ...action }).toEqual({ + error, type: B2BUserActions.LOAD_B2B_USER_USER_GROUPS_FAIL, payload: { orgCustomerId, @@ -734,6 +744,7 @@ describe('B2BUser Actions', () => { }); expect({ ...action }).toEqual({ + error, type: B2BUserActions.ASSIGN_B2B_USER_USER_GROUP_FAIL, payload: { orgCustomerId, @@ -789,6 +800,7 @@ describe('B2BUser Actions', () => { }); expect({ ...action }).toEqual({ + error, type: B2BUserActions.UNASSIGN_B2B_USER_USER_GROUP_FAIL, payload: { orgCustomerId, diff --git a/feature-libs/organization/administration/core/store/actions/b2b-user.action.ts b/feature-libs/organization/administration/core/store/actions/b2b-user.action.ts index a6bfd8ef0ee..bc1e8ab7a2e 100644 --- a/feature-libs/organization/administration/core/store/actions/b2b-user.action.ts +++ b/feature-libs/organization/administration/core/store/actions/b2b-user.action.ts @@ -4,7 +4,13 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { B2BUser, ListModel, SearchConfig, StateUtils } from '@spartacus/core'; +import { + B2BUser, + ErrorAction, + ListModel, + SearchConfig, + StateUtils, +} from '@spartacus/core'; import { B2B_USER_APPROVERS, B2B_USER_ENTITIES, @@ -89,13 +95,18 @@ export const UNASSIGN_B2B_USER_USER_GROUP_SUCCESS = export class LoadB2BUser extends StateUtils.EntityLoadAction { readonly type = LOAD_B2B_USER; + constructor(public payload: { userId: string; orgCustomerId: string }) { super(B2B_USER_ENTITIES, payload.orgCustomerId); } } -export class LoadB2BUserFail extends StateUtils.EntityFailAction { +export class LoadB2BUserFail + extends StateUtils.EntityFailAction + implements ErrorAction +{ readonly type = LOAD_B2B_USER_FAIL; + constructor(public payload: { orgCustomerId: string; error: any }) { super(B2B_USER_ENTITIES, payload.orgCustomerId, payload.error); } @@ -103,6 +114,7 @@ export class LoadB2BUserFail extends StateUtils.EntityFailAction { export class LoadB2BUserSuccess extends StateUtils.EntitySuccessAction { readonly type = LOAD_B2B_USER_SUCCESS; + constructor(public payload: B2BUser | B2BUser[]) { super( B2B_USER_ENTITIES, @@ -115,13 +127,18 @@ export class LoadB2BUserSuccess extends StateUtils.EntitySuccessAction { export class CreateB2BUser extends StateUtils.EntityLoadAction { readonly type = CREATE_B2B_USER; + constructor(public payload: { userId: string; orgCustomer: B2BUser }) { super(B2B_USER_ENTITIES, payload.orgCustomer.customerId ?? null); } } -export class CreateB2BUserFail extends StateUtils.EntityFailAction { +export class CreateB2BUserFail + extends StateUtils.EntityFailAction + implements ErrorAction +{ readonly type = CREATE_B2B_USER_FAIL; + constructor(public payload: { orgCustomerId: string; error: any }) { super(B2B_USER_ENTITIES, payload.orgCustomerId, payload.error); } @@ -129,6 +146,7 @@ export class CreateB2BUserFail extends StateUtils.EntityFailAction { export class CreateB2BUserSuccess extends StateUtils.EntitySuccessAction { readonly type = CREATE_B2B_USER_SUCCESS; + constructor(public payload: B2BUser) { super(B2B_USER_ENTITIES, payload.customerId ?? null, payload); } @@ -136,6 +154,7 @@ export class CreateB2BUserSuccess extends StateUtils.EntitySuccessAction { export class UpdateB2BUser extends StateUtils.EntityLoadAction { readonly type = UPDATE_B2B_USER; + constructor( public payload: { userId: string; @@ -147,8 +166,12 @@ export class UpdateB2BUser extends StateUtils.EntityLoadAction { } } -export class UpdateB2BUserFail extends StateUtils.EntityFailAction { +export class UpdateB2BUserFail + extends StateUtils.EntityFailAction + implements ErrorAction +{ readonly type = UPDATE_B2B_USER_FAIL; + constructor(public payload: { orgCustomerId: string; error: any }) { super(B2B_USER_ENTITIES, payload.orgCustomerId, payload.error); } @@ -156,6 +179,7 @@ export class UpdateB2BUserFail extends StateUtils.EntityFailAction { export class UpdateB2BUserSuccess extends StateUtils.EntitySuccessAction { readonly type = UPDATE_B2B_USER_SUCCESS; + constructor(public payload: B2BUser) { super(B2B_USER_ENTITIES, payload.customerId ?? '', payload); } @@ -163,6 +187,7 @@ export class UpdateB2BUserSuccess extends StateUtils.EntitySuccessAction { export class LoadB2BUsers extends StateUtils.EntityLoadAction { readonly type = LOAD_B2B_USERS; + constructor( public payload: { userId: string; @@ -173,8 +198,12 @@ export class LoadB2BUsers extends StateUtils.EntityLoadAction { } } -export class LoadB2BUsersFail extends StateUtils.EntityFailAction { +export class LoadB2BUsersFail + extends StateUtils.EntityFailAction + implements ErrorAction +{ readonly type = LOAD_B2B_USERS_FAIL; + constructor(public payload: { params: SearchConfig; error: any }) { super( USER_LIST, @@ -186,6 +215,7 @@ export class LoadB2BUsersFail extends StateUtils.EntityFailAction { export class LoadB2BUsersSuccess extends StateUtils.EntitySuccessAction { readonly type = LOAD_B2B_USERS_SUCCESS; + constructor( public payload: { page: ListModel; @@ -198,6 +228,7 @@ export class LoadB2BUsersSuccess extends StateUtils.EntitySuccessAction { export class LoadB2BUserApprovers extends StateUtils.EntityLoadAction { readonly type = LOAD_B2B_USER_APPROVERS; + constructor( public payload: { userId: string; @@ -212,8 +243,12 @@ export class LoadB2BUserApprovers extends StateUtils.EntityLoadAction { } } -export class LoadB2BUserApproversFail extends StateUtils.EntityFailAction { +export class LoadB2BUserApproversFail + extends StateUtils.EntityFailAction + implements ErrorAction +{ readonly type = LOAD_B2B_USER_APPROVERS_FAIL; + constructor( public payload: { orgCustomerId: string; @@ -231,6 +266,7 @@ export class LoadB2BUserApproversFail extends StateUtils.EntityFailAction { export class LoadB2BUserApproversSuccess extends StateUtils.EntitySuccessAction { readonly type = LOAD_B2B_USER_APPROVERS_SUCCESS; + constructor( public payload: { orgCustomerId: string; @@ -247,6 +283,7 @@ export class LoadB2BUserApproversSuccess extends StateUtils.EntitySuccessAction export class AssignB2BUserApprover extends StateUtils.EntityLoadAction { readonly type = ASSIGN_B2B_USER_APPROVER; + constructor( public payload: { userId: string; @@ -258,8 +295,12 @@ export class AssignB2BUserApprover extends StateUtils.EntityLoadAction { } } -export class AssignB2BUserApproverFail extends StateUtils.EntityFailAction { +export class AssignB2BUserApproverFail + extends StateUtils.EntityFailAction + implements ErrorAction +{ readonly type = ASSIGN_B2B_USER_APPROVER_FAIL; + constructor( public payload: { orgCustomerId: string; @@ -267,12 +308,13 @@ export class AssignB2BUserApproverFail extends StateUtils.EntityFailAction { error: any; } ) { - super(B2B_USER_ENTITIES, payload.approverId); + super(B2B_USER_ENTITIES, payload.approverId, payload.error); } } export class AssignB2BUserApproverSuccess extends StateUtils.EntitySuccessAction { readonly type = ASSIGN_B2B_USER_APPROVER_SUCCESS; + constructor( public payload: { approverId: string; @@ -285,6 +327,7 @@ export class AssignB2BUserApproverSuccess extends StateUtils.EntitySuccessAction export class UnassignB2BUserApprover extends StateUtils.EntityLoadAction { readonly type = UNASSIGN_B2B_USER_APPROVER; + constructor( public payload: { userId: string; @@ -296,8 +339,12 @@ export class UnassignB2BUserApprover extends StateUtils.EntityLoadAction { } } -export class UnassignB2BUserApproverFail extends StateUtils.EntityFailAction { +export class UnassignB2BUserApproverFail + extends StateUtils.EntityFailAction + implements ErrorAction +{ readonly type = UNASSIGN_B2B_USER_APPROVER_FAIL; + constructor( public payload: { orgCustomerId: string; @@ -305,12 +352,13 @@ export class UnassignB2BUserApproverFail extends StateUtils.EntityFailAction { error: any; } ) { - super(B2B_USER_ENTITIES, payload.approverId); + super(B2B_USER_ENTITIES, payload.approverId, payload.error); } } export class UnassignB2BUserApproverSuccess extends StateUtils.EntitySuccessAction { readonly type = UNASSIGN_B2B_USER_APPROVER_SUCCESS; + constructor( public payload: { approverId: string; @@ -323,6 +371,7 @@ export class UnassignB2BUserApproverSuccess extends StateUtils.EntitySuccessActi export class LoadB2BUserPermissions extends StateUtils.EntityLoadAction { readonly type = LOAD_B2B_USER_PERMISSIONS; + constructor( public payload: { userId: string; @@ -337,8 +386,12 @@ export class LoadB2BUserPermissions extends StateUtils.EntityLoadAction { } } -export class LoadB2BUserPermissionsFail extends StateUtils.EntityFailAction { +export class LoadB2BUserPermissionsFail + extends StateUtils.EntityFailAction + implements ErrorAction +{ readonly type = LOAD_B2B_USER_PERMISSIONS_FAIL; + constructor( public payload: { orgCustomerId: string; @@ -352,6 +405,7 @@ export class LoadB2BUserPermissionsFail extends StateUtils.EntityFailAction { export class LoadB2BUserPermissionsSuccess extends StateUtils.EntitySuccessAction { readonly type = LOAD_B2B_USER_PERMISSIONS_SUCCESS; + constructor( public payload: { orgCustomerId: string; @@ -368,6 +422,7 @@ export class LoadB2BUserPermissionsSuccess extends StateUtils.EntitySuccessActio export class AssignB2BUserPermission extends StateUtils.EntityLoadAction { readonly type = ASSIGN_B2B_USER_PERMISSION; + constructor( public payload: { userId: string; @@ -379,8 +434,12 @@ export class AssignB2BUserPermission extends StateUtils.EntityLoadAction { } } -export class AssignB2BUserPermissionFail extends StateUtils.EntityFailAction { +export class AssignB2BUserPermissionFail + extends StateUtils.EntityFailAction + implements ErrorAction +{ readonly type = ASSIGN_B2B_USER_PERMISSION_FAIL; + constructor( public payload: { orgCustomerId: string; @@ -394,6 +453,7 @@ export class AssignB2BUserPermissionFail extends StateUtils.EntityFailAction { export class AssignB2BUserPermissionSuccess extends StateUtils.EntitySuccessAction { readonly type = ASSIGN_B2B_USER_PERMISSION_SUCCESS; + constructor( public payload: { permissionId: string; @@ -406,6 +466,7 @@ export class AssignB2BUserPermissionSuccess extends StateUtils.EntitySuccessActi export class UnassignB2BUserPermission extends StateUtils.EntityLoadAction { readonly type = UNASSIGN_B2B_USER_PERMISSION; + constructor( public payload: { userId: string; @@ -417,8 +478,12 @@ export class UnassignB2BUserPermission extends StateUtils.EntityLoadAction { } } -export class UnassignB2BUserPermissionFail extends StateUtils.EntityFailAction { +export class UnassignB2BUserPermissionFail + extends StateUtils.EntityFailAction + implements ErrorAction +{ readonly type = UNASSIGN_B2B_USER_PERMISSION_FAIL; + constructor( public payload: { orgCustomerId: string; @@ -432,6 +497,7 @@ export class UnassignB2BUserPermissionFail extends StateUtils.EntityFailAction { export class UnassignB2BUserPermissionSuccess extends StateUtils.EntitySuccessAction { readonly type = UNASSIGN_B2B_USER_PERMISSION_SUCCESS; + constructor( public payload: { permissionId: string; @@ -444,6 +510,7 @@ export class UnassignB2BUserPermissionSuccess extends StateUtils.EntitySuccessAc export class LoadB2BUserUserGroups extends StateUtils.EntityLoadAction { readonly type = LOAD_B2B_USER_USER_GROUPS; + constructor( public payload: { userId: string; @@ -458,8 +525,12 @@ export class LoadB2BUserUserGroups extends StateUtils.EntityLoadAction { } } -export class LoadB2BUserUserGroupsFail extends StateUtils.EntityFailAction { +export class LoadB2BUserUserGroupsFail + extends StateUtils.EntityFailAction + implements ErrorAction +{ readonly type = LOAD_B2B_USER_USER_GROUPS_FAIL; + constructor( public payload: { orgCustomerId: string; @@ -477,6 +548,7 @@ export class LoadB2BUserUserGroupsFail extends StateUtils.EntityFailAction { export class LoadB2BUserUserGroupsSuccess extends StateUtils.EntitySuccessAction { readonly type = LOAD_B2B_USER_USER_GROUPS_SUCCESS; + constructor( public payload: { orgCustomerId: string; @@ -493,6 +565,7 @@ export class LoadB2BUserUserGroupsSuccess extends StateUtils.EntitySuccessAction export class AssignB2BUserUserGroup extends StateUtils.EntityLoadAction { readonly type = ASSIGN_B2B_USER_USER_GROUP; + constructor( public payload: { userId: string; @@ -504,8 +577,12 @@ export class AssignB2BUserUserGroup extends StateUtils.EntityLoadAction { } } -export class AssignB2BUserUserGroupFail extends StateUtils.EntityFailAction { +export class AssignB2BUserUserGroupFail + extends StateUtils.EntityFailAction + implements ErrorAction +{ readonly type = ASSIGN_B2B_USER_USER_GROUP_FAIL; + constructor( public payload: { orgCustomerId: string; @@ -519,6 +596,7 @@ export class AssignB2BUserUserGroupFail extends StateUtils.EntityFailAction { export class AssignB2BUserUserGroupSuccess extends StateUtils.EntitySuccessAction { readonly type = ASSIGN_B2B_USER_USER_GROUP_SUCCESS; + constructor( public payload: { uid: string; @@ -531,6 +609,7 @@ export class AssignB2BUserUserGroupSuccess extends StateUtils.EntitySuccessActio export class UnassignB2BUserUserGroup extends StateUtils.EntityLoadAction { readonly type = UNASSIGN_B2B_USER_USER_GROUP; + constructor( public payload: { userId: string; @@ -542,8 +621,12 @@ export class UnassignB2BUserUserGroup extends StateUtils.EntityLoadAction { } } -export class UnassignB2BUserUserGroupFail extends StateUtils.EntityFailAction { +export class UnassignB2BUserUserGroupFail + extends StateUtils.EntityFailAction + implements ErrorAction +{ readonly type = UNASSIGN_B2B_USER_USER_GROUP_FAIL; + constructor( public payload: { orgCustomerId: string; @@ -557,6 +640,7 @@ export class UnassignB2BUserUserGroupFail extends StateUtils.EntityFailAction { export class UnassignB2BUserUserGroupSuccess extends StateUtils.EntitySuccessAction { readonly type = UNASSIGN_B2B_USER_USER_GROUP_SUCCESS; + constructor( public payload: { uid: string; diff --git a/feature-libs/organization/administration/core/store/actions/budget.action.spec.ts b/feature-libs/organization/administration/core/store/actions/budget.action.spec.ts index c31df73d604..20d4eac47f7 100644 --- a/feature-libs/organization/administration/core/store/actions/budget.action.spec.ts +++ b/feature-libs/organization/administration/core/store/actions/budget.action.spec.ts @@ -8,7 +8,7 @@ const budget: Budget = { code: budgetCode, }; const userId = 'xxx@xxx.xxx'; -const error = 'anError'; +const error = { message: 'anError' }; const params = { currentPage: 2 }; const query = '?pageSize=¤tPage=2&sort='; @@ -38,6 +38,7 @@ describe('Budget Actions', () => { const action = new BudgetActions.LoadBudgetFail({ budgetCode, error }); expect({ ...action }).toEqual({ + error, type: BudgetActions.LOAD_BUDGET_FAIL, payload: { budgetCode, error }, meta: StateUtils.entityFailMeta(BUDGET_ENTITIES, budgetCode, error), @@ -108,15 +109,14 @@ describe('Budget Actions', () => { it('should create the action', () => { const action = new BudgetActions.LoadBudgetsFail({ params, - error: { error }, + error, }); expect({ ...action }).toEqual({ + error, type: BudgetActions.LOAD_BUDGETS_FAIL, - payload: { params, error: { error } }, - meta: StateUtils.entityFailMeta(BUDGET_LIST, query, { - error, - }), + payload: { params, error }, + meta: StateUtils.entityFailMeta(BUDGET_LIST, query, error), }); }); }); @@ -158,6 +158,7 @@ describe('Budget Actions', () => { }); expect({ ...action }).toEqual({ + error, type: BudgetActions.CREATE_BUDGET_FAIL, payload: { budgetCode, @@ -206,6 +207,7 @@ describe('Budget Actions', () => { }); expect({ ...action }).toEqual({ + error, type: BudgetActions.UPDATE_BUDGET_FAIL, payload: { budgetCode, diff --git a/feature-libs/organization/administration/core/store/actions/budget.action.ts b/feature-libs/organization/administration/core/store/actions/budget.action.ts index 6a80d05d986..24581c5d301 100644 --- a/feature-libs/organization/administration/core/store/actions/budget.action.ts +++ b/feature-libs/organization/administration/core/store/actions/budget.action.ts @@ -4,7 +4,12 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { ListModel, SearchConfig, StateUtils } from '@spartacus/core'; +import { + ErrorAction, + ListModel, + SearchConfig, + StateUtils, +} from '@spartacus/core'; import { Budget } from '../../model/budget.model'; import { BUDGET_ENTITIES, BUDGET_LIST } from '../organization-state'; @@ -26,13 +31,18 @@ export const UPDATE_BUDGET_SUCCESS = '[Budget] Update Budget Success'; export class LoadBudget extends StateUtils.EntityLoadAction { readonly type = LOAD_BUDGET; + constructor(public payload: { userId: string; budgetCode: string }) { super(BUDGET_ENTITIES, payload.budgetCode); } } -export class LoadBudgetFail extends StateUtils.EntityFailAction { +export class LoadBudgetFail + extends StateUtils.EntityFailAction + implements ErrorAction +{ readonly type = LOAD_BUDGET_FAIL; + constructor(public payload: { budgetCode: string; error: any }) { super(BUDGET_ENTITIES, payload.budgetCode, payload.error); } @@ -40,6 +50,7 @@ export class LoadBudgetFail extends StateUtils.EntityFailAction { export class LoadBudgetSuccess extends StateUtils.EntitySuccessAction { readonly type = LOAD_BUDGET_SUCCESS; + constructor(public payload: Budget | Budget[]) { super( BUDGET_ENTITIES, @@ -52,6 +63,7 @@ export class LoadBudgetSuccess extends StateUtils.EntitySuccessAction { export class LoadBudgets extends StateUtils.EntityLoadAction { readonly type = LOAD_BUDGETS; + constructor( public payload: { userId: string; @@ -62,8 +74,12 @@ export class LoadBudgets extends StateUtils.EntityLoadAction { } } -export class LoadBudgetsFail extends StateUtils.EntityFailAction { +export class LoadBudgetsFail + extends StateUtils.EntityFailAction + implements ErrorAction +{ readonly type = LOAD_BUDGETS_FAIL; + constructor(public payload: { params: SearchConfig; error: any }) { super( BUDGET_LIST, @@ -75,6 +91,7 @@ export class LoadBudgetsFail extends StateUtils.EntityFailAction { export class LoadBudgetsSuccess extends StateUtils.EntitySuccessAction { readonly type = LOAD_BUDGETS_SUCCESS; + constructor( public payload: { page: ListModel; @@ -87,13 +104,18 @@ export class LoadBudgetsSuccess extends StateUtils.EntitySuccessAction { export class CreateBudget extends StateUtils.EntityLoadAction { readonly type = CREATE_BUDGET; + constructor(public payload: { userId: string; budget: Budget }) { super(BUDGET_ENTITIES, payload.budget.code ?? null); } } -export class CreateBudgetFail extends StateUtils.EntityFailAction { +export class CreateBudgetFail + extends StateUtils.EntityFailAction + implements ErrorAction +{ readonly type = CREATE_BUDGET_FAIL; + constructor(public payload: { budgetCode: string; error: any }) { super(BUDGET_ENTITIES, payload.budgetCode, payload.error); } @@ -101,6 +123,7 @@ export class CreateBudgetFail extends StateUtils.EntityFailAction { export class CreateBudgetSuccess extends StateUtils.EntitySuccessAction { readonly type = CREATE_BUDGET_SUCCESS; + constructor(public payload: Budget) { super(BUDGET_ENTITIES, payload.code ?? null, payload); } @@ -108,6 +131,7 @@ export class CreateBudgetSuccess extends StateUtils.EntitySuccessAction { export class UpdateBudget extends StateUtils.EntityLoadAction { readonly type = UPDATE_BUDGET; + constructor( public payload: { userId: string; budgetCode: string; budget: Budget } ) { @@ -115,8 +139,12 @@ export class UpdateBudget extends StateUtils.EntityLoadAction { } } -export class UpdateBudgetFail extends StateUtils.EntityFailAction { +export class UpdateBudgetFail + extends StateUtils.EntityFailAction + implements ErrorAction +{ readonly type = UPDATE_BUDGET_FAIL; + constructor(public payload: { budgetCode: string; error: any }) { super(BUDGET_ENTITIES, payload.budgetCode, payload.error); } @@ -124,6 +152,7 @@ export class UpdateBudgetFail extends StateUtils.EntityFailAction { export class UpdateBudgetSuccess extends StateUtils.EntitySuccessAction { readonly type = UPDATE_BUDGET_SUCCESS; + constructor(public payload: Budget) { super(BUDGET_ENTITIES, payload.code ?? '', payload); } diff --git a/feature-libs/organization/administration/core/store/actions/cost-center.action.spec.ts b/feature-libs/organization/administration/core/store/actions/cost-center.action.spec.ts index a1b63c68813..98108e9bfe9 100644 --- a/feature-libs/organization/administration/core/store/actions/cost-center.action.spec.ts +++ b/feature-libs/organization/administration/core/store/actions/cost-center.action.spec.ts @@ -13,7 +13,7 @@ const costCenter: CostCenter = { code: costCenterCode, }; const userId = 'xxx@xxx.xxx'; -const error = 'anError'; +const error = { message: 'anError' }; const params = { currentPage: 2 }; const query = '?pageSize=¤tPage=2&sort='; @@ -46,6 +46,7 @@ describe('CostCenter Actions', () => { }); expect({ ...action }).toEqual({ + error, type: CostCenterActions.LOAD_COST_CENTER_FAIL, payload: { costCenterCode, error }, meta: StateUtils.entityFailMeta( @@ -127,15 +128,14 @@ describe('CostCenter Actions', () => { it('should create the action', () => { const action = new CostCenterActions.LoadCostCentersFail({ params, - error: { error }, + error, }); expect({ ...action }).toEqual({ + error, type: CostCenterActions.LOAD_COST_CENTERS_FAIL, - payload: { params, error: { error } }, - meta: StateUtils.entityFailMeta(COST_CENTER_LIST, query, { - error, - }), + payload: { params, error }, + meta: StateUtils.entityFailMeta(COST_CENTER_LIST, query, error), }); }); }); @@ -180,6 +180,7 @@ describe('CostCenter Actions', () => { }); expect({ ...action }).toEqual({ + error, type: CostCenterActions.CREATE_COST_CENTER_FAIL, payload: { costCenterCode, @@ -237,6 +238,7 @@ describe('CostCenter Actions', () => { }); expect({ ...action }).toEqual({ + error, type: CostCenterActions.UPDATE_COST_CENTER_FAIL, payload: { costCenterCode, @@ -298,6 +300,7 @@ describe('CostCenter Actions', () => { }); expect({ ...action }).toEqual({ + error, type: CostCenterActions.LOAD_ASSIGNED_BUDGETS_FAIL, payload: { costCenterCode, @@ -362,6 +365,7 @@ describe('CostCenter Actions', () => { }); expect({ ...action }).toEqual({ + error, type: CostCenterActions.ASSIGN_BUDGET_FAIL, payload: { budgetCode, @@ -413,6 +417,7 @@ describe('CostCenter Actions', () => { }); expect({ ...action }).toEqual({ + error, type: CostCenterActions.UNASSIGN_BUDGET_FAIL, payload: { budgetCode, diff --git a/feature-libs/organization/administration/core/store/actions/cost-center.action.ts b/feature-libs/organization/administration/core/store/actions/cost-center.action.ts index b6396808c91..81ea3c3a4f3 100644 --- a/feature-libs/organization/administration/core/store/actions/cost-center.action.ts +++ b/feature-libs/organization/administration/core/store/actions/cost-center.action.ts @@ -6,6 +6,7 @@ import { CostCenter, + ErrorAction, ListModel, SearchConfig, StateUtils, @@ -52,13 +53,18 @@ export const UNASSIGN_BUDGET_FAIL = '[CostCenter] Unassign Budget fail'; export class LoadCostCenter extends StateUtils.EntityLoadAction { readonly type = LOAD_COST_CENTER; + constructor(public payload: { userId: string; costCenterCode: string }) { super(COST_CENTER_ENTITIES, payload.costCenterCode); } } -export class LoadCostCenterFail extends StateUtils.EntityFailAction { +export class LoadCostCenterFail + extends StateUtils.EntityFailAction + implements ErrorAction +{ readonly type = LOAD_COST_CENTER_FAIL; + constructor(public payload: { costCenterCode: string; error: any }) { super(COST_CENTER_ENTITIES, payload.costCenterCode, payload.error); } @@ -66,6 +72,7 @@ export class LoadCostCenterFail extends StateUtils.EntityFailAction { export class LoadCostCenterSuccess extends StateUtils.EntitySuccessAction { readonly type = LOAD_COST_CENTER_SUCCESS; + constructor(public payload: CostCenter | CostCenter[]) { super( COST_CENTER_ENTITIES, @@ -78,6 +85,7 @@ export class LoadCostCenterSuccess extends StateUtils.EntitySuccessAction { export class LoadCostCenters extends StateUtils.EntityLoadAction { readonly type = LOAD_COST_CENTERS; + constructor( public payload: { userId: string; @@ -88,8 +96,12 @@ export class LoadCostCenters extends StateUtils.EntityLoadAction { } } -export class LoadCostCentersFail extends StateUtils.EntityFailAction { +export class LoadCostCentersFail + extends StateUtils.EntityFailAction + implements ErrorAction +{ readonly type = LOAD_COST_CENTERS_FAIL; + constructor(public payload: { params: SearchConfig; error: any }) { super( COST_CENTER_LIST, @@ -101,6 +113,7 @@ export class LoadCostCentersFail extends StateUtils.EntityFailAction { export class LoadCostCentersSuccess extends StateUtils.EntitySuccessAction { readonly type = LOAD_COST_CENTERS_SUCCESS; + constructor( public payload: { page: ListModel; @@ -113,13 +126,18 @@ export class LoadCostCentersSuccess extends StateUtils.EntitySuccessAction { export class CreateCostCenter extends StateUtils.EntityLoadAction { readonly type = CREATE_COST_CENTER; + constructor(public payload: { userId: string; costCenter: CostCenter }) { super(COST_CENTER_ENTITIES, payload.costCenter.code ?? null); } } -export class CreateCostCenterFail extends StateUtils.EntityFailAction { +export class CreateCostCenterFail + extends StateUtils.EntityFailAction + implements ErrorAction +{ readonly type = CREATE_COST_CENTER_FAIL; + constructor(public payload: { costCenterCode: string; error: any }) { super(COST_CENTER_ENTITIES, payload.costCenterCode, payload.error); } @@ -127,6 +145,7 @@ export class CreateCostCenterFail extends StateUtils.EntityFailAction { export class CreateCostCenterSuccess extends StateUtils.EntitySuccessAction { readonly type = CREATE_COST_CENTER_SUCCESS; + constructor(public payload: CostCenter) { super(COST_CENTER_ENTITIES, payload.code ?? null, payload); } @@ -134,6 +153,7 @@ export class CreateCostCenterSuccess extends StateUtils.EntitySuccessAction { export class UpdateCostCenter extends StateUtils.EntityLoadAction { readonly type = UPDATE_COST_CENTER; + constructor( public payload: { userId: string; @@ -145,8 +165,12 @@ export class UpdateCostCenter extends StateUtils.EntityLoadAction { } } -export class UpdateCostCenterFail extends StateUtils.EntityFailAction { +export class UpdateCostCenterFail + extends StateUtils.EntityFailAction + implements ErrorAction +{ readonly type = UPDATE_COST_CENTER_FAIL; + constructor(public payload: { costCenterCode: string; error: any }) { super(COST_CENTER_ENTITIES, payload.costCenterCode, payload.error); } @@ -154,6 +178,7 @@ export class UpdateCostCenterFail extends StateUtils.EntityFailAction { export class UpdateCostCenterSuccess extends StateUtils.EntitySuccessAction { readonly type = UPDATE_COST_CENTER_SUCCESS; + constructor(public payload: CostCenter) { super(COST_CENTER_ENTITIES, payload.code ?? '', payload); } @@ -161,6 +186,7 @@ export class UpdateCostCenterSuccess extends StateUtils.EntitySuccessAction { export class LoadAssignedBudgets extends StateUtils.EntityLoadAction { readonly type = LOAD_ASSIGNED_BUDGETS; + constructor( public payload: { userId: string; @@ -175,8 +201,12 @@ export class LoadAssignedBudgets extends StateUtils.EntityLoadAction { } } -export class LoadAssignedBudgetsFail extends StateUtils.EntityFailAction { +export class LoadAssignedBudgetsFail + extends StateUtils.EntityFailAction + implements ErrorAction +{ readonly type = LOAD_ASSIGNED_BUDGETS_FAIL; + constructor( public payload: { costCenterCode: string; @@ -194,6 +224,7 @@ export class LoadAssignedBudgetsFail extends StateUtils.EntityFailAction { export class LoadAssignedBudgetsSuccess extends StateUtils.EntitySuccessAction { readonly type = LOAD_ASSIGNED_BUDGETS_SUCCESS; + constructor( public payload: { costCenterCode: string; @@ -210,6 +241,7 @@ export class LoadAssignedBudgetsSuccess extends StateUtils.EntitySuccessAction { export class AssignBudget extends StateUtils.EntityLoadAction { readonly type = ASSIGN_BUDGET; + constructor( public payload: { userId: string; @@ -221,8 +253,12 @@ export class AssignBudget extends StateUtils.EntityLoadAction { } } -export class AssignBudgetFail extends StateUtils.EntityFailAction { +export class AssignBudgetFail + extends StateUtils.EntityFailAction + implements ErrorAction +{ readonly type = ASSIGN_BUDGET_FAIL; + constructor(public payload: { budgetCode: string; error: any }) { super(BUDGET_ENTITIES, payload.budgetCode, payload.error); } @@ -230,6 +266,7 @@ export class AssignBudgetFail extends StateUtils.EntityFailAction { export class AssignBudgetSuccess extends StateUtils.EntitySuccessAction { readonly type = ASSIGN_BUDGET_SUCCESS; + constructor(public payload: { code: string; selected: boolean }) { super(BUDGET_ENTITIES, payload.code, payload); } @@ -237,6 +274,7 @@ export class AssignBudgetSuccess extends StateUtils.EntitySuccessAction { export class UnassignBudget extends StateUtils.EntityLoadAction { readonly type = UNASSIGN_BUDGET; + constructor( public payload: { userId: string; @@ -248,8 +286,12 @@ export class UnassignBudget extends StateUtils.EntityLoadAction { } } -export class UnassignBudgetFail extends StateUtils.EntityFailAction { +export class UnassignBudgetFail + extends StateUtils.EntityFailAction + implements ErrorAction +{ readonly type = UNASSIGN_BUDGET_FAIL; + constructor(public payload: { budgetCode: string; error: any }) { super(BUDGET_ENTITIES, payload.budgetCode, payload.error); } @@ -257,6 +299,7 @@ export class UnassignBudgetFail extends StateUtils.EntityFailAction { export class UnassignBudgetSuccess extends StateUtils.EntitySuccessAction { readonly type = UNASSIGN_BUDGET_SUCCESS; + constructor(public payload: { code: string; selected: boolean }) { super(BUDGET_ENTITIES, payload.code, payload); } diff --git a/feature-libs/organization/administration/core/store/actions/org-unit.action.spec.ts b/feature-libs/organization/administration/core/store/actions/org-unit.action.spec.ts index e84816507ff..f5465e8253c 100644 --- a/feature-libs/organization/administration/core/store/actions/org-unit.action.spec.ts +++ b/feature-libs/organization/administration/core/store/actions/org-unit.action.spec.ts @@ -50,7 +50,7 @@ const unit: B2BUnit = { uid: 'testUid' }; const unitCode: string = unit.uid; const userId = 'xxx@xxx.xxx'; -const error = 'anError'; +const error = { message: 'anError' }; describe('OrgUnit Actions', () => { describe('LoadOrgUnit Actions', () => { @@ -74,6 +74,7 @@ describe('OrgUnit Actions', () => { const action = new OrgUnitActions.LoadOrgUnitFail({ orgUnitId, error }); expect({ ...action }).toEqual({ + error, type: OrgUnitActions.LOAD_ORG_UNIT_FAIL, payload: { orgUnitId, error }, meta: StateUtils.entityFailMeta(ORG_UNIT_ENTITIES, orgUnitId, error), @@ -142,15 +143,18 @@ describe('OrgUnit Actions', () => { describe('LoadOrgUnitNodesFail', () => { it('should create the action', () => { const action = new OrgUnitActions.LoadOrgUnitNodesFail({ - error: { error }, + error, }); expect({ ...action }).toEqual({ + error, type: OrgUnitActions.LOAD_UNIT_NODES_FAIL, - payload: { error: { error } }, - meta: StateUtils.entityFailMeta(ORG_UNIT_NODE_LIST, ORG_UNIT_NODES, { - error, - }), + payload: { error }, + meta: StateUtils.entityFailMeta( + ORG_UNIT_NODE_LIST, + ORG_UNIT_NODES, + error + ), }); }); }); @@ -191,6 +195,7 @@ describe('OrgUnit Actions', () => { const action = new OrgUnitActions.LoadApprovalProcessesFail({ error }); expect({ ...action }).toEqual({ + error, type: OrgUnitActions.LOAD_APPROVAL_PROCESSES_FAIL, payload: { error }, meta: StateUtils.entityFailMeta( @@ -238,6 +243,7 @@ describe('OrgUnit Actions', () => { }); expect({ ...action }).toEqual({ + error, type: OrgUnitActions.CREATE_ORG_UNIT_FAIL, payload: { unitCode, error }, meta: StateUtils.entityFailMeta(ORG_UNIT_ENTITIES, unitCode, error), @@ -277,6 +283,7 @@ describe('OrgUnit Actions', () => { }); expect({ ...action }).toEqual({ + error, type: OrgUnitActions.UPDATE_ORG_UNIT_FAIL, payload: { unitCode, error }, meta: StateUtils.entityFailMeta(ORG_UNIT_ENTITIES, unitCode, error), @@ -316,6 +323,7 @@ describe('OrgUnit Actions', () => { }); expect({ ...action }).toEqual({ + error, type: OrgUnitActions.CREATE_ADDRESS_FAIL, payload: { addressId, error }, meta: StateUtils.entityFailMeta(ADDRESS_ENTITIES, addressId, error), @@ -356,6 +364,7 @@ describe('OrgUnit Actions', () => { }); expect({ ...action }).toEqual({ + error, type: OrgUnitActions.UPDATE_ADDRESS_FAIL, payload: { addressId, error }, meta: StateUtils.entityFailMeta(ADDRESS_ENTITIES, addressId, error), @@ -395,6 +404,7 @@ describe('OrgUnit Actions', () => { }); expect({ ...action }).toEqual({ + error, type: OrgUnitActions.DELETE_ADDRESS_FAIL, payload: { addressId, error }, meta: StateUtils.entityFailMeta(ADDRESS_ENTITIES, addressId, error), @@ -433,6 +443,7 @@ describe('OrgUnit Actions', () => { }); expect({ ...action }).toEqual({ + error, type: OrgUnitActions.LOAD_ADDRESSES_FAIL, payload: { orgUnitId, error }, meta: StateUtils.entityFailMeta(ADDRESS_LIST, orgUnitId, error), @@ -485,6 +496,7 @@ describe('OrgUnit Actions', () => { }); expect({ ...action }).toEqual({ + error, type: OrgUnitActions.ASSIGN_ROLE_FAIL, payload: { orgCustomerId, error }, meta: StateUtils.entityFailMeta( @@ -532,6 +544,7 @@ describe('OrgUnit Actions', () => { }); expect({ ...action }).toEqual({ + error, type: OrgUnitActions.UNASSIGN_ROLE_FAIL, payload: { orgCustomerId, error }, meta: StateUtils.entityFailMeta( @@ -576,6 +589,7 @@ describe('OrgUnit Actions', () => { }); expect({ ...action }).toEqual({ + error, type: OrgUnitActions.LOAD_UNIT_TREE_FAIL, payload: { error }, meta: StateUtils.entityFailMeta( @@ -621,6 +635,7 @@ describe('OrgUnit Actions', () => { }); expect({ ...action }).toEqual({ + error, type: OrgUnitActions.ASSIGN_APPROVER_FAIL, payload: { orgCustomerId, error }, meta: StateUtils.entityFailMeta( @@ -669,6 +684,7 @@ describe('OrgUnit Actions', () => { }); expect({ ...action }).toEqual({ + error, type: OrgUnitActions.UNASSIGN_APPROVER_FAIL, payload: { orgCustomerId, error }, meta: StateUtils.entityFailMeta( @@ -722,6 +738,7 @@ describe('OrgUnit Actions', () => { }); expect({ ...action }).toEqual({ + error, type: OrgUnitActions.LOAD_ASSIGNED_USERS_FAIL, payload: { orgUnitId, roleId, params, error }, meta: StateUtils.entityFailMeta( diff --git a/feature-libs/organization/administration/core/store/actions/org-unit.action.ts b/feature-libs/organization/administration/core/store/actions/org-unit.action.ts index 5a7633d7f0b..b414f9eeb40 100644 --- a/feature-libs/organization/administration/core/store/actions/org-unit.action.ts +++ b/feature-libs/organization/administration/core/store/actions/org-unit.action.ts @@ -8,6 +8,7 @@ import { Address, B2BApprovalProcess, B2BUnit, + ErrorAction, ListModel, SearchConfig, StateUtils, @@ -102,13 +103,18 @@ export const CLEAR_ASSIGNED_USERS = '[B2BUnit] Clear Assigned Users'; export class LoadOrgUnit extends StateUtils.EntityLoadAction { readonly type = LOAD_ORG_UNIT; + constructor(public payload: { userId: string; orgUnitId: string }) { super(ORG_UNIT_ENTITIES, payload.orgUnitId); } } -export class LoadOrgUnitFail extends StateUtils.EntityFailAction { +export class LoadOrgUnitFail + extends StateUtils.EntityFailAction + implements ErrorAction +{ readonly type = LOAD_ORG_UNIT_FAIL; + constructor(public payload: { orgUnitId: string; error: any }) { super(ORG_UNIT_ENTITIES, payload.orgUnitId, payload.error); } @@ -129,6 +135,7 @@ export class LoadOrgUnitSuccess extends StateUtils.EntitySuccessAction { export class LoadOrgUnitNodes extends StateUtils.EntityLoadAction { readonly type = LOAD_UNIT_NODES; + constructor( public payload: { userId: string; @@ -138,8 +145,12 @@ export class LoadOrgUnitNodes extends StateUtils.EntityLoadAction { } } -export class LoadOrgUnitNodesFail extends StateUtils.EntityFailAction { +export class LoadOrgUnitNodesFail + extends StateUtils.EntityFailAction + implements ErrorAction +{ readonly type = LOAD_UNIT_NODES_FAIL; + constructor(public payload: any) { super(ORG_UNIT_NODE_LIST, ORG_UNIT_NODES, payload.error); } @@ -147,6 +158,7 @@ export class LoadOrgUnitNodesFail extends StateUtils.EntityFailAction { export class LoadOrgUnitNodesSuccess extends StateUtils.EntitySuccessAction { readonly type = LOAD_UNIT_NODES_SUCCESS; + constructor(public payload: B2BUnitNode[]) { super(ORG_UNIT_NODE_LIST, ORG_UNIT_NODES); } @@ -154,13 +166,18 @@ export class LoadOrgUnitNodesSuccess extends StateUtils.EntitySuccessAction { export class CreateUnit extends StateUtils.EntityLoadAction { readonly type = CREATE_ORG_UNIT; + constructor(public payload: { userId: string; unit: B2BUnit }) { super(ORG_UNIT_ENTITIES, payload.unit.uid ?? null); } } -export class CreateUnitFail extends StateUtils.EntityFailAction { +export class CreateUnitFail + extends StateUtils.EntityFailAction + implements ErrorAction +{ readonly type = CREATE_ORG_UNIT_FAIL; + constructor(public payload: { unitCode: string; error: any }) { super(ORG_UNIT_ENTITIES, payload.unitCode, payload.error); } @@ -168,6 +185,7 @@ export class CreateUnitFail extends StateUtils.EntityFailAction { export class CreateUnitSuccess extends StateUtils.EntitySuccessAction { readonly type = CREATE_ORG_UNIT_SUCCESS; + constructor(public payload: B2BUnit) { super(ORG_UNIT_ENTITIES, payload.uid ?? null, payload); } @@ -175,6 +193,7 @@ export class CreateUnitSuccess extends StateUtils.EntitySuccessAction { export class UpdateUnit extends StateUtils.EntityLoadAction { readonly type = UPDATE_ORG_UNIT; + constructor( public payload: { userId: string; unitCode: string; unit: B2BUnit } ) { @@ -182,8 +201,12 @@ export class UpdateUnit extends StateUtils.EntityLoadAction { } } -export class UpdateUnitFail extends StateUtils.EntityFailAction { +export class UpdateUnitFail + extends StateUtils.EntityFailAction + implements ErrorAction +{ readonly type = UPDATE_ORG_UNIT_FAIL; + constructor(public payload: { unitCode: string; error: any }) { super(ORG_UNIT_ENTITIES, payload.unitCode, payload.error); } @@ -191,6 +214,7 @@ export class UpdateUnitFail extends StateUtils.EntityFailAction { export class UpdateUnitSuccess extends StateUtils.EntitySuccessAction { readonly type = UPDATE_ORG_UNIT_SUCCESS; + constructor(public payload: B2BUnit) { super(ORG_UNIT_ENTITIES, payload.uid ?? '', payload); } @@ -198,13 +222,18 @@ export class UpdateUnitSuccess extends StateUtils.EntitySuccessAction { export class LoadTree extends StateUtils.EntityLoadAction { readonly type = LOAD_UNIT_TREE; + constructor(public payload: { userId: string }) { super(ORG_UNIT_TREE_ENTITY, ORG_UNIT_TREE); } } -export class LoadTreeFail extends StateUtils.EntityFailAction { +export class LoadTreeFail + extends StateUtils.EntityFailAction + implements ErrorAction +{ readonly type = LOAD_UNIT_TREE_FAIL; + constructor(public payload: { error: any }) { super(ORG_UNIT_TREE_ENTITY, ORG_UNIT_TREE, payload.error); } @@ -220,13 +249,18 @@ export class LoadTreeSuccess extends StateUtils.EntitySuccessAction { export class LoadApprovalProcesses extends StateUtils.EntityLoadAction { readonly type = LOAD_APPROVAL_PROCESSES; + constructor(public payload: { userId: string }) { super(ORG_UNIT_APPROVAL_PROCESSES_ENTITIES, ORG_UNIT_APPROVAL_PROCESSES); } } -export class LoadApprovalProcessesFail extends StateUtils.EntityFailAction { +export class LoadApprovalProcessesFail + extends StateUtils.EntityFailAction + implements ErrorAction +{ readonly type = LOAD_APPROVAL_PROCESSES_FAIL; + constructor(public payload: { error: any }) { super( ORG_UNIT_APPROVAL_PROCESSES_ENTITIES, @@ -246,6 +280,7 @@ export class LoadApprovalProcessesSuccess extends StateUtils.EntitySuccessAction export class LoadAssignedUsers extends StateUtils.EntityLoadAction { readonly type = LOAD_ASSIGNED_USERS; + constructor( public payload: { userId: string; @@ -266,6 +301,7 @@ export class LoadAssignedUsers extends StateUtils.EntityLoadAction { export class ClearAssignedUsers extends StateUtils.EntityRemoveAction { readonly type = CLEAR_ASSIGNED_USERS; + constructor( public payload: { orgUnitId: string; @@ -283,8 +319,12 @@ export class ClearAssignedUsers extends StateUtils.EntityRemoveAction { } } -export class LoadAssignedUsersFail extends StateUtils.EntityFailAction { +export class LoadAssignedUsersFail + extends StateUtils.EntityFailAction + implements ErrorAction +{ readonly type = LOAD_ASSIGNED_USERS_FAIL; + constructor( public payload: { orgUnitId: string; @@ -306,6 +346,7 @@ export class LoadAssignedUsersFail extends StateUtils.EntityFailAction { export class LoadAssignedUsersSuccess extends StateUtils.EntitySuccessAction { readonly type = LOAD_ASSIGNED_USERS_SUCCESS; + constructor( public payload: { orgUnitId: string; @@ -326,6 +367,7 @@ export class LoadAssignedUsersSuccess extends StateUtils.EntitySuccessAction { export class AssignRole extends StateUtils.EntityLoadAction { readonly type = ASSIGN_ROLE; + constructor( public payload: { userId: string; @@ -337,8 +379,12 @@ export class AssignRole extends StateUtils.EntityLoadAction { } } -export class AssignRoleFail extends StateUtils.EntityFailAction { +export class AssignRoleFail + extends StateUtils.EntityFailAction + implements ErrorAction +{ readonly type = ASSIGN_ROLE_FAIL; + constructor( public payload: { orgCustomerId: string; @@ -351,6 +397,7 @@ export class AssignRoleFail extends StateUtils.EntityFailAction { export class AssignRoleSuccess extends StateUtils.EntitySuccessAction { readonly type = ASSIGN_ROLE_SUCCESS; + constructor( public payload: { uid: string; roleId: string; selected: boolean } ) { @@ -360,6 +407,7 @@ export class AssignRoleSuccess extends StateUtils.EntitySuccessAction { export class UnassignRole extends StateUtils.EntityLoadAction { readonly type = UNASSIGN_ROLE; + constructor( public payload: { userId: string; @@ -371,8 +419,12 @@ export class UnassignRole extends StateUtils.EntityLoadAction { } } -export class UnassignRoleFail extends StateUtils.EntityFailAction { +export class UnassignRoleFail + extends StateUtils.EntityFailAction + implements ErrorAction +{ readonly type = UNASSIGN_ROLE_FAIL; + constructor( public payload: { orgCustomerId: string; @@ -385,6 +437,7 @@ export class UnassignRoleFail extends StateUtils.EntityFailAction { export class UnassignRoleSuccess extends StateUtils.EntitySuccessAction { readonly type = UNASSIGN_ROLE_SUCCESS; + constructor( public payload: { uid: string; roleId: string; selected: boolean } ) { @@ -394,6 +447,7 @@ export class UnassignRoleSuccess extends StateUtils.EntitySuccessAction { export class AssignApprover extends StateUtils.EntityLoadAction { readonly type = ASSIGN_APPROVER; + constructor( public payload: { userId: string; @@ -406,8 +460,12 @@ export class AssignApprover extends StateUtils.EntityLoadAction { } } -export class AssignApproverFail extends StateUtils.EntityFailAction { +export class AssignApproverFail + extends StateUtils.EntityFailAction + implements ErrorAction +{ readonly type = ASSIGN_APPROVER_FAIL; + constructor( public payload: { orgCustomerId: string; @@ -420,6 +478,7 @@ export class AssignApproverFail extends StateUtils.EntityFailAction { export class AssignApproverSuccess extends StateUtils.EntitySuccessAction { readonly type = ASSIGN_APPROVER_SUCCESS; + constructor( public payload: { uid: string; roleId: string; selected: boolean } ) { @@ -429,6 +488,7 @@ export class AssignApproverSuccess extends StateUtils.EntitySuccessAction { export class UnassignApprover extends StateUtils.EntityLoadAction { readonly type = UNASSIGN_APPROVER; + constructor( public payload: { userId: string; @@ -441,8 +501,12 @@ export class UnassignApprover extends StateUtils.EntityLoadAction { } } -export class UnassignApproverFail extends StateUtils.EntityFailAction { +export class UnassignApproverFail + extends StateUtils.EntityFailAction + implements ErrorAction +{ readonly type = UNASSIGN_APPROVER_FAIL; + constructor( public payload: { orgCustomerId: string; @@ -455,6 +519,7 @@ export class UnassignApproverFail extends StateUtils.EntityFailAction { export class UnassignApproverSuccess extends StateUtils.EntitySuccessAction { readonly type = UNASSIGN_APPROVER_SUCCESS; + constructor( public payload: { uid: string; roleId: string; selected: boolean } ) { @@ -464,6 +529,7 @@ export class UnassignApproverSuccess extends StateUtils.EntitySuccessAction { export class CreateAddress extends StateUtils.EntityLoadAction { readonly type = CREATE_ADDRESS; + constructor( public payload: { userId: string; orgUnitId: string; address: Address } ) { @@ -471,8 +537,12 @@ export class CreateAddress extends StateUtils.EntityLoadAction { } } -export class CreateAddressFail extends StateUtils.EntityFailAction { +export class CreateAddressFail + extends StateUtils.EntityFailAction + implements ErrorAction +{ readonly type = CREATE_ADDRESS_FAIL; + constructor(public payload: { addressId: string; error: any }) { super(ADDRESS_ENTITIES, payload.addressId, payload.error); } @@ -480,6 +550,7 @@ export class CreateAddressFail extends StateUtils.EntityFailAction { export class CreateAddressSuccess extends StateUtils.EntitySuccessAction { readonly type = CREATE_ADDRESS_SUCCESS; + constructor(public payload: Address) { super(ADDRESS_ENTITIES, payload.id ?? null, payload); } @@ -487,6 +558,7 @@ export class CreateAddressSuccess extends StateUtils.EntitySuccessAction { export class UpdateAddress extends StateUtils.EntityLoadAction { readonly type = UPDATE_ADDRESS; + constructor( public payload: { userId: string; @@ -499,8 +571,12 @@ export class UpdateAddress extends StateUtils.EntityLoadAction { } } -export class UpdateAddressFail extends StateUtils.EntityFailAction { +export class UpdateAddressFail + extends StateUtils.EntityFailAction + implements ErrorAction +{ readonly type = UPDATE_ADDRESS_FAIL; + constructor(public payload: { addressId: string; error: any }) { super(ADDRESS_ENTITIES, payload.addressId, payload.error); } @@ -508,6 +584,7 @@ export class UpdateAddressFail extends StateUtils.EntityFailAction { export class UpdateAddressSuccess extends StateUtils.EntitySuccessAction { readonly type = UPDATE_ADDRESS_SUCCESS; + constructor(public payload: Address) { super(ADDRESS_ENTITIES, payload.id ?? '', payload); } @@ -515,6 +592,7 @@ export class UpdateAddressSuccess extends StateUtils.EntitySuccessAction { export class DeleteAddress extends StateUtils.EntityLoadAction { readonly type = DELETE_ADDRESS; + constructor( public payload: { userId: string; @@ -526,8 +604,12 @@ export class DeleteAddress extends StateUtils.EntityLoadAction { } } -export class DeleteAddressFail extends StateUtils.EntityFailAction { +export class DeleteAddressFail + extends StateUtils.EntityFailAction + implements ErrorAction +{ readonly type = DELETE_ADDRESS_FAIL; + constructor(public payload: { addressId: string; error: any }) { super(ADDRESS_ENTITIES, payload.addressId, payload.error); } @@ -535,6 +617,7 @@ export class DeleteAddressFail extends StateUtils.EntityFailAction { export class DeleteAddressSuccess extends StateUtils.EntityRemoveAction { readonly type = DELETE_ADDRESS_SUCCESS; + constructor(public payload: Address) { super(ADDRESS_ENTITIES, payload.id ?? ''); } @@ -542,6 +625,7 @@ export class DeleteAddressSuccess extends StateUtils.EntityRemoveAction { export class LoadAddressSuccess extends StateUtils.EntitySuccessAction { readonly type = LOAD_ADDRESS_SUCCESS; + constructor(public payload: Address | Address[]) { super( ADDRESS_ENTITIES, @@ -554,13 +638,18 @@ export class LoadAddressSuccess extends StateUtils.EntitySuccessAction { export class LoadAddresses extends StateUtils.EntityLoadAction { readonly type = LOAD_ADDRESSES; + constructor(public payload: { userId: string; orgUnitId: string }) { super(ADDRESS_LIST, payload.orgUnitId); } } -export class LoadAddressesFail extends StateUtils.EntityFailAction { +export class LoadAddressesFail + extends StateUtils.EntityFailAction + implements ErrorAction +{ readonly type = LOAD_ADDRESSES_FAIL; + constructor(public payload: { orgUnitId: string; error: any }) { super(ADDRESS_LIST, payload.orgUnitId, payload.error); } @@ -568,6 +657,7 @@ export class LoadAddressesFail extends StateUtils.EntityFailAction { export class LoadAddressesSuccess extends StateUtils.EntitySuccessAction { readonly type = LOAD_ADDRESSES_SUCCESS; + constructor( public payload: { page: ListModel; diff --git a/feature-libs/organization/administration/core/store/actions/permission.action.spec.ts b/feature-libs/organization/administration/core/store/actions/permission.action.spec.ts index 399948d7ce9..9c8e7913acc 100644 --- a/feature-libs/organization/administration/core/store/actions/permission.action.spec.ts +++ b/feature-libs/organization/administration/core/store/actions/permission.action.spec.ts @@ -20,7 +20,7 @@ const permissionType: OrderApprovalPermissionType = { const permissionTypes: OrderApprovalPermissionType[] = [permissionType]; const userId = 'xxx@xxx.xxx'; -const error = 'anError'; +const error = { message: 'anError' }; const params = { currentPage: 2 }; const query = '?pageSize=¤tPage=2&sort='; @@ -53,6 +53,7 @@ describe('Permission Actions', () => { }); expect({ ...action }).toEqual({ + error, type: PermissionActions.LOAD_PERMISSION_FAIL, payload: { permissionCode, @@ -137,15 +138,14 @@ describe('Permission Actions', () => { it('should create the action', () => { const action = new PermissionActions.LoadPermissionsFail({ params, - error: { error }, + error, }); expect({ ...action }).toEqual({ + error, type: PermissionActions.LOAD_PERMISSIONS_FAIL, - payload: { params, error: { error } }, - meta: StateUtils.entityFailMeta(PERMISSION_LIST, query, { - error, - }), + payload: { params, error }, + meta: StateUtils.entityFailMeta(PERMISSION_LIST, query, error), }); }); }); @@ -190,6 +190,7 @@ describe('Permission Actions', () => { }); expect({ ...action }).toEqual({ + error, type: PermissionActions.CREATE_PERMISSION_FAIL, payload: { permissionCode, @@ -247,6 +248,7 @@ describe('Permission Actions', () => { }); expect({ ...action }).toEqual({ + error, type: PermissionActions.UPDATE_PERMISSION_FAIL, payload: { permissionCode, @@ -302,6 +304,7 @@ describe('Permission Actions', () => { }); expect({ ...action }).toEqual({ + error, type: PermissionActions.LOAD_PERMISSION_TYPES_FAIL, payload: { permissionCode, diff --git a/feature-libs/organization/administration/core/store/actions/permission.action.ts b/feature-libs/organization/administration/core/store/actions/permission.action.ts index 578a25af9be..3d18979e334 100644 --- a/feature-libs/organization/administration/core/store/actions/permission.action.ts +++ b/feature-libs/organization/administration/core/store/actions/permission.action.ts @@ -5,6 +5,7 @@ */ import { + ErrorAction, ListModel, OrderApprovalPermissionType, SearchConfig, @@ -45,13 +46,18 @@ export const LOAD_PERMISSION_TYPES_SUCCESS = export class LoadPermission extends StateUtils.EntityLoadAction { readonly type = LOAD_PERMISSION; + constructor(public payload: { userId: string; permissionCode: string }) { super(PERMISSION_ENTITIES, payload.permissionCode); } } -export class LoadPermissionFail extends StateUtils.EntityFailAction { +export class LoadPermissionFail + extends StateUtils.EntityFailAction + implements ErrorAction +{ readonly type = LOAD_PERMISSION_FAIL; + constructor(public payload: { permissionCode: string; error: any }) { super(PERMISSION_ENTITIES, payload.permissionCode, payload.error); } @@ -59,6 +65,7 @@ export class LoadPermissionFail extends StateUtils.EntityFailAction { export class LoadPermissionSuccess extends StateUtils.EntitySuccessAction { readonly type = LOAD_PERMISSION_SUCCESS; + constructor(public payload: Permission | Permission[]) { super( PERMISSION_ENTITIES, @@ -71,6 +78,7 @@ export class LoadPermissionSuccess extends StateUtils.EntitySuccessAction { export class LoadPermissions extends StateUtils.EntityLoadAction { readonly type = LOAD_PERMISSIONS; + constructor( public payload: { userId: string; @@ -81,8 +89,12 @@ export class LoadPermissions extends StateUtils.EntityLoadAction { } } -export class LoadPermissionsFail extends StateUtils.EntityFailAction { +export class LoadPermissionsFail + extends StateUtils.EntityFailAction + implements ErrorAction +{ readonly type = LOAD_PERMISSIONS_FAIL; + constructor(public payload: { params: SearchConfig; error: any }) { super( PERMISSION_LIST, @@ -94,6 +106,7 @@ export class LoadPermissionsFail extends StateUtils.EntityFailAction { export class LoadPermissionsSuccess extends StateUtils.EntitySuccessAction { readonly type = LOAD_PERMISSIONS_SUCCESS; + constructor( public payload: { page: ListModel; @@ -106,13 +119,18 @@ export class LoadPermissionsSuccess extends StateUtils.EntitySuccessAction { export class CreatePermission extends StateUtils.EntityLoadAction { readonly type = CREATE_PERMISSION; + constructor(public payload: { userId: string; permission: Permission }) { super(PERMISSION_ENTITIES, payload.permission.code ?? null); } } -export class CreatePermissionFail extends StateUtils.EntityFailAction { +export class CreatePermissionFail + extends StateUtils.EntityFailAction + implements ErrorAction +{ readonly type = CREATE_PERMISSION_FAIL; + constructor(public payload: { permissionCode: string; error: any }) { super(PERMISSION_ENTITIES, payload.permissionCode, payload.error); } @@ -120,6 +138,7 @@ export class CreatePermissionFail extends StateUtils.EntityFailAction { export class CreatePermissionSuccess extends StateUtils.EntitySuccessAction { readonly type = CREATE_PERMISSION_SUCCESS; + constructor(public payload: Permission) { super(PERMISSION_ENTITIES, payload.code ?? null, payload); } @@ -127,6 +146,7 @@ export class CreatePermissionSuccess extends StateUtils.EntitySuccessAction { export class UpdatePermission extends StateUtils.EntityLoadAction { readonly type = UPDATE_PERMISSION; + constructor( public payload: { userId: string; @@ -138,8 +158,12 @@ export class UpdatePermission extends StateUtils.EntityLoadAction { } } -export class UpdatePermissionFail extends StateUtils.EntityFailAction { +export class UpdatePermissionFail + extends StateUtils.EntityFailAction + implements ErrorAction +{ readonly type = UPDATE_PERMISSION_FAIL; + constructor(public payload: { permissionCode: string; error: any }) { super(PERMISSION_ENTITIES, payload.permissionCode, payload.error); } @@ -147,6 +171,7 @@ export class UpdatePermissionFail extends StateUtils.EntityFailAction { export class UpdatePermissionSuccess extends StateUtils.EntitySuccessAction { readonly type = UPDATE_PERMISSION_SUCCESS; + constructor(public payload: Permission) { super(PERMISSION_ENTITIES, payload.code ?? '', payload); } @@ -154,13 +179,18 @@ export class UpdatePermissionSuccess extends StateUtils.EntitySuccessAction { export class LoadPermissionTypes extends StateUtils.EntityLoadAction { readonly type = LOAD_PERMISSION_TYPES; + constructor() { super(PERMISSION_TYPES_LIST, PERMISSION_TYPES); } } -export class LoadPermissionTypesFail extends StateUtils.EntityFailAction { +export class LoadPermissionTypesFail + extends StateUtils.EntityFailAction + implements ErrorAction +{ readonly type = LOAD_PERMISSION_TYPES_FAIL; + constructor(public payload: any) { super(PERMISSION_TYPES_LIST, PERMISSION_TYPES, payload.error); } @@ -168,6 +198,7 @@ export class LoadPermissionTypesFail extends StateUtils.EntityFailAction { export class LoadPermissionTypesSuccess extends StateUtils.EntitySuccessAction { readonly type = LOAD_PERMISSION_TYPES_SUCCESS; + constructor(public payload: OrderApprovalPermissionType[]) { super(PERMISSION_TYPES_LIST, PERMISSION_TYPES); } diff --git a/feature-libs/organization/administration/core/store/actions/user-group.action.spec.ts b/feature-libs/organization/administration/core/store/actions/user-group.action.spec.ts index 23ec79dbb55..ad79fbac77f 100644 --- a/feature-libs/organization/administration/core/store/actions/user-group.action.spec.ts +++ b/feature-libs/organization/administration/core/store/actions/user-group.action.spec.ts @@ -17,7 +17,7 @@ const userGroup: UserGroup = { uid: userGroupId, }; const userId = 'xxx@xxx.xxx'; -const error = 'anError'; +const error = { message: 'anError' }; const params = { currentPage: 2 }; const query = '?pageSize=¤tPage=2&sort='; @@ -50,6 +50,7 @@ describe('UserGroup Actions', () => { }); expect({ ...action }).toEqual({ + error, type: UserGroupActions.LOAD_USER_GROUP_FAIL, payload: { userGroupId, error }, meta: StateUtils.entityFailMeta( @@ -126,15 +127,14 @@ describe('UserGroup Actions', () => { it('should create the action', () => { const action = new UserGroupActions.LoadUserGroupsFail({ params, - error: { error }, + error, }); expect({ ...action }).toEqual({ + error, type: UserGroupActions.LOAD_USER_GROUPS_FAIL, - payload: { params, error: { error } }, - meta: StateUtils.entityFailMeta(USER_GROUP_LIST, query, { - error, - }), + payload: { params, error }, + meta: StateUtils.entityFailMeta(USER_GROUP_LIST, query, error), }); }); }); @@ -179,6 +179,7 @@ describe('UserGroup Actions', () => { }); expect({ ...action }).toEqual({ + error, type: UserGroupActions.CREATE_USER_GROUP_FAIL, payload: { userGroupId, @@ -231,6 +232,7 @@ describe('UserGroup Actions', () => { }); expect({ ...action }).toEqual({ + error, type: UserGroupActions.UPDATE_USER_GROUP_FAIL, payload: { userGroupId, @@ -282,6 +284,7 @@ describe('UserGroup Actions', () => { }); expect({ ...action }).toEqual({ + error, type: UserGroupActions.DELETE_USER_GROUP_FAIL, payload: { userGroupId, @@ -338,6 +341,7 @@ describe('UserGroup Actions', () => { }); expect({ ...action }).toEqual({ + error, type: UserGroupActions.LOAD_USER_GROUP_PERMISSIONS_FAIL, payload: { userGroupId, @@ -403,6 +407,7 @@ describe('UserGroup Actions', () => { }); expect({ ...action }).toEqual({ + error, type: UserGroupActions.USER_GROUP_ASSIGN_PERMISSION_FAIL, payload: { userGroupId, @@ -463,6 +468,7 @@ describe('UserGroup Actions', () => { }); expect({ ...action }).toEqual({ + error, type: UserGroupActions.USER_GROUP_UNASSIGN_PERMISSION_FAIL, payload: { userGroupId, @@ -526,6 +532,7 @@ describe('UserGroup Actions', () => { }); expect({ ...action }).toEqual({ + error, type: UserGroupActions.LOAD_USER_GROUP_AVAILABLE_CUSTOMERS_FAIL, payload: { userGroupId, @@ -591,6 +598,7 @@ describe('UserGroup Actions', () => { }); expect({ ...action }).toEqual({ + error, type: UserGroupActions.USER_GROUP_ASSIGN_MEMBER_FAIL, payload: { userGroupId, @@ -644,6 +652,7 @@ describe('UserGroup Actions', () => { }); expect({ ...action }).toEqual({ + error, type: UserGroupActions.USER_GROUP_UNASSIGN_MEMBER_FAIL, payload: { userGroupId, diff --git a/feature-libs/organization/administration/core/store/actions/user-group.action.ts b/feature-libs/organization/administration/core/store/actions/user-group.action.ts index e7575e891c0..5de3ac85520 100644 --- a/feature-libs/organization/administration/core/store/actions/user-group.action.ts +++ b/feature-libs/organization/administration/core/store/actions/user-group.action.ts @@ -4,7 +4,12 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { ListModel, SearchConfig, StateUtils } from '@spartacus/core'; +import { + ErrorAction, + ListModel, + SearchConfig, + StateUtils, +} from '@spartacus/core'; import { UserGroup } from '../../model/user-group.model'; import { B2B_USER_ENTITIES, @@ -80,13 +85,18 @@ export const USER_GROUP_UNASSIGN_PERMISSION_SUCCESS = export class LoadUserGroup extends StateUtils.EntityLoadAction { readonly type = LOAD_USER_GROUP; + constructor(public payload: { userId: string; userGroupId: string }) { super(USER_GROUP_ENTITIES, payload.userGroupId); } } -export class LoadUserGroupFail extends StateUtils.EntityFailAction { +export class LoadUserGroupFail + extends StateUtils.EntityFailAction + implements ErrorAction +{ readonly type = LOAD_USER_GROUP_FAIL; + constructor(public payload: { userGroupId: string; error: any }) { super(USER_GROUP_ENTITIES, payload.userGroupId, payload.error); } @@ -94,6 +104,7 @@ export class LoadUserGroupFail extends StateUtils.EntityFailAction { export class LoadUserGroupSuccess extends StateUtils.EntitySuccessAction { readonly type = LOAD_USER_GROUP_SUCCESS; + constructor(public payload: UserGroup | UserGroup[]) { super( USER_GROUP_ENTITIES, @@ -106,6 +117,7 @@ export class LoadUserGroupSuccess extends StateUtils.EntitySuccessAction { export class LoadUserGroups extends StateUtils.EntityLoadAction { readonly type = LOAD_USER_GROUPS; + constructor( public payload: { userId: string; @@ -116,8 +128,12 @@ export class LoadUserGroups extends StateUtils.EntityLoadAction { } } -export class LoadUserGroupsFail extends StateUtils.EntityFailAction { +export class LoadUserGroupsFail + extends StateUtils.EntityFailAction + implements ErrorAction +{ readonly type = LOAD_USER_GROUPS_FAIL; + constructor(public payload: { params: SearchConfig; error: any }) { super( USER_GROUP_LIST, @@ -129,6 +145,7 @@ export class LoadUserGroupsFail extends StateUtils.EntityFailAction { export class LoadUserGroupsSuccess extends StateUtils.EntitySuccessAction { readonly type = LOAD_USER_GROUPS_SUCCESS; + constructor( public payload: { page: ListModel; @@ -141,6 +158,7 @@ export class LoadUserGroupsSuccess extends StateUtils.EntitySuccessAction { export class LoadPermissions extends StateUtils.EntityLoadAction { readonly type = LOAD_USER_GROUP_PERMISSIONS; + constructor( public payload: { userId: string; @@ -155,8 +173,12 @@ export class LoadPermissions extends StateUtils.EntityLoadAction { } } -export class LoadPermissionsFail extends StateUtils.EntityFailAction { +export class LoadPermissionsFail + extends StateUtils.EntityFailAction + implements ErrorAction +{ readonly type = LOAD_USER_GROUP_PERMISSIONS_FAIL; + constructor( public payload: { userGroupId: string; @@ -174,6 +196,7 @@ export class LoadPermissionsFail extends StateUtils.EntityFailAction { export class LoadPermissionsSuccess extends StateUtils.EntitySuccessAction { readonly type = LOAD_USER_GROUP_PERMISSIONS_SUCCESS; + constructor( public payload: { userGroupId: string; @@ -190,6 +213,7 @@ export class LoadPermissionsSuccess extends StateUtils.EntitySuccessAction { export class LoadAvailableOrgCustomers extends StateUtils.EntityLoadAction { readonly type = LOAD_USER_GROUP_AVAILABLE_CUSTOMERS; + constructor( public payload: { userId: string; @@ -204,8 +228,12 @@ export class LoadAvailableOrgCustomers extends StateUtils.EntityLoadAction { } } -export class LoadAvailableOrgCustomersFail extends StateUtils.EntityFailAction { +export class LoadAvailableOrgCustomersFail + extends StateUtils.EntityFailAction + implements ErrorAction +{ readonly type = LOAD_USER_GROUP_AVAILABLE_CUSTOMERS_FAIL; + constructor( public payload: { userGroupId: string; @@ -223,6 +251,7 @@ export class LoadAvailableOrgCustomersFail extends StateUtils.EntityFailAction { export class LoadAvailableOrgCustomersSuccess extends StateUtils.EntitySuccessAction { readonly type = LOAD_USER_GROUP_AVAILABLE_CUSTOMERS_SUCCESS; + constructor( public payload: { userGroupId: string; @@ -239,13 +268,18 @@ export class LoadAvailableOrgCustomersSuccess extends StateUtils.EntitySuccessAc export class CreateUserGroup extends StateUtils.EntityLoadAction { readonly type = CREATE_USER_GROUP; + constructor(public payload: { userId: string; userGroup: UserGroup }) { super(USER_GROUP_ENTITIES, payload.userGroup.uid ?? null); } } -export class CreateUserGroupFail extends StateUtils.EntityFailAction { +export class CreateUserGroupFail + extends StateUtils.EntityFailAction + implements ErrorAction +{ readonly type = CREATE_USER_GROUP_FAIL; + constructor(public payload: { userGroupId: string; error: any }) { super(USER_GROUP_ENTITIES, payload.userGroupId, payload.error); } @@ -253,6 +287,7 @@ export class CreateUserGroupFail extends StateUtils.EntityFailAction { export class CreateUserGroupSuccess extends StateUtils.EntitySuccessAction { readonly type = CREATE_USER_GROUP_SUCCESS; + constructor(public payload: UserGroup) { super(USER_GROUP_ENTITIES, payload.uid ?? null, payload); } @@ -260,6 +295,7 @@ export class CreateUserGroupSuccess extends StateUtils.EntitySuccessAction { export class AssignMember extends StateUtils.EntityLoadAction { readonly type = USER_GROUP_ASSIGN_MEMBER; + constructor( public payload: { userId: string; @@ -271,8 +307,12 @@ export class AssignMember extends StateUtils.EntityLoadAction { } } -export class AssignMemberFail extends StateUtils.EntityFailAction { +export class AssignMemberFail + extends StateUtils.EntityFailAction + implements ErrorAction +{ readonly type = USER_GROUP_ASSIGN_MEMBER_FAIL; + constructor( public payload: { userGroupId: string; @@ -286,6 +326,7 @@ export class AssignMemberFail extends StateUtils.EntityFailAction { export class AssignMemberSuccess extends StateUtils.EntitySuccessAction { readonly type = USER_GROUP_ASSIGN_MEMBER_SUCCESS; + constructor(public payload: { customerId: string; selected: boolean }) { super(B2B_USER_ENTITIES, payload.customerId, payload); } @@ -293,6 +334,7 @@ export class AssignMemberSuccess extends StateUtils.EntitySuccessAction { export class AssignPermission extends StateUtils.EntityLoadAction { readonly type = USER_GROUP_ASSIGN_PERMISSION; + constructor( public payload: { userId: string; @@ -304,8 +346,12 @@ export class AssignPermission extends StateUtils.EntityLoadAction { } } -export class AssignPermissionFail extends StateUtils.EntityFailAction { +export class AssignPermissionFail + extends StateUtils.EntityFailAction + implements ErrorAction +{ readonly type = USER_GROUP_ASSIGN_PERMISSION_FAIL; + constructor( public payload: { userGroupId: string; @@ -319,6 +365,7 @@ export class AssignPermissionFail extends StateUtils.EntityFailAction { export class AssignPermissionSuccess extends StateUtils.EntitySuccessAction { readonly type = USER_GROUP_ASSIGN_PERMISSION_SUCCESS; + constructor(public payload: { permissionUid: string; selected: boolean }) { super(PERMISSION_ENTITIES, payload.permissionUid, payload); } @@ -326,6 +373,7 @@ export class AssignPermissionSuccess extends StateUtils.EntitySuccessAction { export class UpdateUserGroup extends StateUtils.EntityLoadAction { readonly type = UPDATE_USER_GROUP; + constructor( public payload: { userId: string; @@ -337,8 +385,12 @@ export class UpdateUserGroup extends StateUtils.EntityLoadAction { } } -export class UpdateUserGroupFail extends StateUtils.EntityFailAction { +export class UpdateUserGroupFail + extends StateUtils.EntityFailAction + implements ErrorAction +{ readonly type = UPDATE_USER_GROUP_FAIL; + constructor(public payload: { userGroupId: string; error: any }) { super(USER_GROUP_ENTITIES, payload.userGroupId, payload.error); } @@ -346,6 +398,7 @@ export class UpdateUserGroupFail extends StateUtils.EntityFailAction { export class UpdateUserGroupSuccess extends StateUtils.EntitySuccessAction { readonly type = UPDATE_USER_GROUP_SUCCESS; + constructor(public payload: UserGroup) { super(USER_GROUP_ENTITIES, payload.uid ?? '', payload); } @@ -353,6 +406,7 @@ export class UpdateUserGroupSuccess extends StateUtils.EntitySuccessAction { export class DeleteUserGroup extends StateUtils.EntityLoadAction { readonly type = DELETE_USER_GROUP; + constructor( public payload: { userId: string; @@ -363,8 +417,12 @@ export class DeleteUserGroup extends StateUtils.EntityLoadAction { } } -export class DeleteUserGroupFail extends StateUtils.EntityFailAction { +export class DeleteUserGroupFail + extends StateUtils.EntityFailAction + implements ErrorAction +{ readonly type = DELETE_USER_GROUP_FAIL; + constructor(public payload: { userGroupId: string; error: any }) { super(USER_GROUP_ENTITIES, payload.userGroupId, payload.error); } @@ -372,6 +430,7 @@ export class DeleteUserGroupFail extends StateUtils.EntityFailAction { export class DeleteUserGroupSuccess extends StateUtils.EntitySuccessAction { readonly type = DELETE_USER_GROUP_SUCCESS; + constructor(public payload: UserGroup) { super(USER_GROUP_ENTITIES, payload.uid ?? '', payload); } @@ -379,6 +438,7 @@ export class DeleteUserGroupSuccess extends StateUtils.EntitySuccessAction { export class UnassignMember extends StateUtils.EntityLoadAction { readonly type = USER_GROUP_UNASSIGN_MEMBER; + constructor( public payload: { userId: string; @@ -390,8 +450,12 @@ export class UnassignMember extends StateUtils.EntityLoadAction { } } -export class UnassignMemberFail extends StateUtils.EntityFailAction { +export class UnassignMemberFail + extends StateUtils.EntityFailAction + implements ErrorAction +{ readonly type = USER_GROUP_UNASSIGN_MEMBER_FAIL; + constructor( public payload: { userGroupId: string; @@ -405,6 +469,7 @@ export class UnassignMemberFail extends StateUtils.EntityFailAction { export class UnassignMemberSuccess extends StateUtils.EntitySuccessAction { readonly type = USER_GROUP_UNASSIGN_MEMBER_SUCCESS; + constructor(public payload: { customerId: string; selected: boolean }) { super(B2B_USER_ENTITIES, payload.customerId, payload); } @@ -412,6 +477,7 @@ export class UnassignMemberSuccess extends StateUtils.EntitySuccessAction { export class UnassignAllMembers extends StateUtils.EntityLoadAction { readonly type = USER_GROUP_UNASSIGN_ALL_MEMBERS; + constructor( public payload: { userId: string; @@ -422,8 +488,12 @@ export class UnassignAllMembers extends StateUtils.EntityLoadAction { } } -export class UnassignAllMembersFail extends StateUtils.EntityFailAction { +export class UnassignAllMembersFail + extends StateUtils.EntityFailAction + implements ErrorAction +{ readonly type = USER_GROUP_UNASSIGN_ALL_MEMBERS_FAIL; + constructor(public payload: { userGroupId: string; error: any }) { super(USER_GROUP_ENTITIES, payload.userGroupId, payload.error); } @@ -431,6 +501,7 @@ export class UnassignAllMembersFail extends StateUtils.EntityFailAction { export class UnassignAllMembersSuccess extends StateUtils.EntitySuccessAction { readonly type = USER_GROUP_UNASSIGN_ALL_MEMBERS_SUCCESS; + constructor(public payload: UserGroup) { super(USER_GROUP_ENTITIES, payload.uid ?? '', payload); } @@ -438,6 +509,7 @@ export class UnassignAllMembersSuccess extends StateUtils.EntitySuccessAction { export class UnassignPermission extends StateUtils.EntityLoadAction { readonly type = USER_GROUP_UNASSIGN_PERMISSION; + constructor( public payload: { userId: string; @@ -449,8 +521,12 @@ export class UnassignPermission extends StateUtils.EntityLoadAction { } } -export class UnassignPermissionFail extends StateUtils.EntityFailAction { +export class UnassignPermissionFail + extends StateUtils.EntityFailAction + implements ErrorAction +{ readonly type = USER_GROUP_UNASSIGN_PERMISSION_FAIL; + constructor( public payload: { userGroupId: string; @@ -464,6 +540,7 @@ export class UnassignPermissionFail extends StateUtils.EntityFailAction { export class UnassignPermissionSuccess extends StateUtils.EntitySuccessAction { readonly type = USER_GROUP_UNASSIGN_PERMISSION_SUCCESS; + constructor(public payload: { permissionUid: string; selected: boolean }) { super(PERMISSION_ENTITIES, payload.permissionUid, payload); } diff --git a/feature-libs/organization/administration/core/store/effects/b2b-user.effect.ts b/feature-libs/organization/administration/core/store/effects/b2b-user.effect.ts index 3838aa94467..eee2149aaec 100644 --- a/feature-libs/organization/administration/core/store/effects/b2b-user.effect.ts +++ b/feature-libs/organization/administration/core/store/effects/b2b-user.effect.ts @@ -19,7 +19,7 @@ import { StateUtils, User, UserIdService, - normalizeHttpError, + tryNormalizeHttpError, } from '@spartacus/core'; import { UserAccountFacade } from '@spartacus/user/account/root'; import { Observable, from, of } from 'rxjs'; @@ -68,7 +68,7 @@ export class B2BUserEffects { of( new B2BUserActions.LoadB2BUserFail({ orgCustomerId, - error: normalizeHttpError(error, this.logger), + error: tryNormalizeHttpError(error, this.logger), }) ) ) @@ -130,7 +130,7 @@ export class B2BUserEffects { from([ new B2BUserActions.CreateB2BUserFail({ orgCustomerId: orgCustomer.customerId ?? '', - error: normalizeHttpError(error, this.logger), + error: tryNormalizeHttpError(error, this.logger), }), new OrganizationActions.OrganizationClearData(), ]) @@ -175,7 +175,7 @@ export class B2BUserEffects { from([ new B2BUserActions.UpdateB2BUserFail({ orgCustomerId: orgCustomer.customerId ?? '', - error: normalizeHttpError(error, this.logger), + error: tryNormalizeHttpError(error, this.logger), }), new OrganizationActions.OrganizationClearData(), ]) @@ -241,7 +241,7 @@ export class B2BUserEffects { of( new B2BUserActions.LoadB2BUsersFail({ params: payload.params, - error: normalizeHttpError(error, this.logger), + error: tryNormalizeHttpError(error, this.logger), }) ) ) @@ -290,7 +290,7 @@ export class B2BUserEffects { new B2BUserActions.LoadB2BUserApproversFail({ orgCustomerId: payload.orgCustomerId, params: payload.params, - error: normalizeHttpError(error, this.logger), + error: tryNormalizeHttpError(error, this.logger), }) ) ) @@ -341,7 +341,7 @@ export class B2BUserEffects { new B2BUserActions.LoadB2BUserPermissionsFail({ orgCustomerId: payload.orgCustomerId, params: payload.params, - error: normalizeHttpError(error, this.logger), + error: tryNormalizeHttpError(error, this.logger), }) ) ) @@ -392,7 +392,7 @@ export class B2BUserEffects { new B2BUserActions.LoadB2BUserUserGroupsFail({ orgCustomerId: payload.orgCustomerId, params: payload.params, - error: normalizeHttpError(error, this.logger), + error: tryNormalizeHttpError(error, this.logger), }) ) ) @@ -433,7 +433,7 @@ export class B2BUserEffects { new B2BUserActions.AssignB2BUserApproverFail({ orgCustomerId: payload.orgCustomerId, approverId: payload.approverId, - error: normalizeHttpError(error, this.logger), + error: tryNormalizeHttpError(error, this.logger), }), new OrganizationActions.OrganizationClearData(), ]) @@ -473,7 +473,7 @@ export class B2BUserEffects { new B2BUserActions.UnassignB2BUserApproverFail({ orgCustomerId: payload.orgCustomerId, approverId: payload.approverId, - error: normalizeHttpError(error, this.logger), + error: tryNormalizeHttpError(error, this.logger), }), new OrganizationActions.OrganizationClearData(), ]) @@ -511,7 +511,7 @@ export class B2BUserEffects { new B2BUserActions.AssignB2BUserPermissionFail({ orgCustomerId: payload.orgCustomerId, permissionId: payload.permissionId, - error: normalizeHttpError(error, this.logger), + error: tryNormalizeHttpError(error, this.logger), }), new OrganizationActions.OrganizationClearData(), ]) @@ -549,7 +549,7 @@ export class B2BUserEffects { new B2BUserActions.UnassignB2BUserPermissionFail({ orgCustomerId: payload.orgCustomerId, permissionId: payload.permissionId, - error: normalizeHttpError(error, this.logger), + error: tryNormalizeHttpError(error, this.logger), }), new OrganizationActions.OrganizationClearData(), ]) @@ -587,7 +587,7 @@ export class B2BUserEffects { new B2BUserActions.AssignB2BUserUserGroupFail({ orgCustomerId: payload.orgCustomerId, userGroupId: payload.userGroupId, - error: normalizeHttpError(error, this.logger), + error: tryNormalizeHttpError(error, this.logger), }), new OrganizationActions.OrganizationClearData(), ]) @@ -633,7 +633,7 @@ export class B2BUserEffects { new B2BUserActions.UnassignB2BUserUserGroupFail({ orgCustomerId: payload.orgCustomerId, userGroupId: payload.userGroupId, - error: normalizeHttpError(error, this.logger), + error: tryNormalizeHttpError(error, this.logger), }), new OrganizationActions.OrganizationClearData(), ]) diff --git a/feature-libs/organization/administration/core/store/effects/budget.effect.ts b/feature-libs/organization/administration/core/store/effects/budget.effect.ts index 7fe42acea1b..3148e6fb872 100644 --- a/feature-libs/organization/administration/core/store/effects/budget.effect.ts +++ b/feature-libs/organization/administration/core/store/effects/budget.effect.ts @@ -11,7 +11,7 @@ import { EntitiesModel, LoggerService, StateUtils, - normalizeHttpError, + tryNormalizeHttpError, } from '@spartacus/core'; import { Observable, from, of } from 'rxjs'; import { catchError, map, switchMap } from 'rxjs/operators'; @@ -38,7 +38,7 @@ export class BudgetEffects { of( new BudgetActions.LoadBudgetFail({ budgetCode, - error: normalizeHttpError(error, this.logger), + error: tryNormalizeHttpError(error, this.logger), }) ) ) @@ -74,7 +74,7 @@ export class BudgetEffects { of( new BudgetActions.LoadBudgetsFail({ params: payload.params, - error: normalizeHttpError(error, this.logger), + error: tryNormalizeHttpError(error, this.logger), }) ) ) @@ -101,7 +101,7 @@ export class BudgetEffects { from([ new BudgetActions.CreateBudgetFail({ budgetCode: payload.budget.code ?? '', - error: normalizeHttpError(error, this.logger), + error: tryNormalizeHttpError(error, this.logger), }), new OrganizationActions.OrganizationClearData(), ]) @@ -131,7 +131,7 @@ export class BudgetEffects { from([ new BudgetActions.UpdateBudgetFail({ budgetCode: payload.budget.code ?? '', - error: normalizeHttpError(error, this.logger), + error: tryNormalizeHttpError(error, this.logger), }), new OrganizationActions.OrganizationClearData(), ]) diff --git a/feature-libs/organization/administration/core/store/effects/cost-center.effect.ts b/feature-libs/organization/administration/core/store/effects/cost-center.effect.ts index fc41ba9919b..5ca9c333e95 100644 --- a/feature-libs/organization/administration/core/store/effects/cost-center.effect.ts +++ b/feature-libs/organization/administration/core/store/effects/cost-center.effect.ts @@ -12,7 +12,7 @@ import { EntitiesModel, LoggerService, StateUtils, - normalizeHttpError, + tryNormalizeHttpError, } from '@spartacus/core'; import { Observable, from, of } from 'rxjs'; import { catchError, groupBy, map, mergeMap, switchMap } from 'rxjs/operators'; @@ -44,7 +44,7 @@ export class CostCenterEffects { of( new CostCenterActions.LoadCostCenterFail({ costCenterCode, - error: normalizeHttpError(error, this.logger), + error: tryNormalizeHttpError(error, this.logger), }) ) ) @@ -80,7 +80,7 @@ export class CostCenterEffects { of( new CostCenterActions.LoadCostCentersFail({ params: payload.params, - error: normalizeHttpError(error, this.logger), + error: tryNormalizeHttpError(error, this.logger), }) ) ) @@ -109,7 +109,7 @@ export class CostCenterEffects { from([ new CostCenterActions.CreateCostCenterFail({ costCenterCode: payload.costCenter.code ?? '', - error: normalizeHttpError(error, this.logger), + error: tryNormalizeHttpError(error, this.logger), }), new OrganizationActions.OrganizationClearData(), ]) @@ -139,7 +139,7 @@ export class CostCenterEffects { from([ new CostCenterActions.UpdateCostCenterFail({ costCenterCode: payload.costCenter.code ?? '', - error: normalizeHttpError(error, this.logger), + error: tryNormalizeHttpError(error, this.logger), }), new OrganizationActions.OrganizationClearData(), ]) @@ -185,7 +185,7 @@ export class CostCenterEffects { new CostCenterActions.LoadAssignedBudgetsFail({ costCenterCode, params, - error: normalizeHttpError(error, this.logger), + error: tryNormalizeHttpError(error, this.logger), }) ) ) @@ -219,7 +219,7 @@ export class CostCenterEffects { from([ new CostCenterActions.AssignBudgetFail({ budgetCode, - error: normalizeHttpError(error, this.logger), + error: tryNormalizeHttpError(error, this.logger), }), new OrganizationActions.OrganizationClearData(), ]) @@ -252,7 +252,7 @@ export class CostCenterEffects { from([ new CostCenterActions.UnassignBudgetFail({ budgetCode, - error: normalizeHttpError(error, this.logger), + error: tryNormalizeHttpError(error, this.logger), }), new OrganizationActions.OrganizationClearData(), ]) diff --git a/feature-libs/organization/administration/core/store/effects/org-unit.effect.ts b/feature-libs/organization/administration/core/store/effects/org-unit.effect.ts index f4b1b2865f8..654cfef2ac6 100644 --- a/feature-libs/organization/administration/core/store/effects/org-unit.effect.ts +++ b/feature-libs/organization/administration/core/store/effects/org-unit.effect.ts @@ -5,7 +5,7 @@ */ import { HttpErrorResponse } from '@angular/common/http'; -import { Injectable, inject } from '@angular/core'; +import { inject, Injectable } from '@angular/core'; import { Actions, createEffect, ofType } from '@ngrx/effects'; import { B2BApprovalProcess, @@ -15,16 +15,16 @@ import { FeatureConfigService, LoggerService, StateUtils, - normalizeHttpError, + tryNormalizeHttpError, } from '@spartacus/core'; -import { Observable, from, of } from 'rxjs'; +import { from, Observable, of } from 'rxjs'; import { catchError, groupBy, map, mergeMap, switchMap } from 'rxjs/operators'; import { OrgUnitConnector } from '../../connectors/org-unit/org-unit.connector'; import { B2BUnitNode } from '../../model/unit-node.model'; import { B2BUserActions, - OrgUnitActions, OrganizationActions, + OrgUnitActions, } from '../actions/index'; @Injectable() @@ -62,7 +62,7 @@ export class OrgUnitEffects { of( new OrgUnitActions.LoadOrgUnitFail({ orgUnitId, - error: normalizeHttpError(error, this.logger), + error: tryNormalizeHttpError(error, this.logger), }) ) ) @@ -86,7 +86,7 @@ export class OrgUnitEffects { catchError((error: HttpErrorResponse) => of( new OrgUnitActions.LoadOrgUnitNodesFail({ - error: normalizeHttpError(error, this.logger), + error: tryNormalizeHttpError(error, this.logger), }) ) ) @@ -113,7 +113,7 @@ export class OrgUnitEffects { from([ new OrgUnitActions.CreateUnitFail({ unitCode: payload.unit.uid ?? '', - error: normalizeHttpError(error, this.logger), + error: tryNormalizeHttpError(error, this.logger), }), new OrganizationActions.OrganizationClearData(), ]) @@ -144,7 +144,7 @@ export class OrgUnitEffects { from([ new OrgUnitActions.UpdateUnitFail({ unitCode: payload.unit.uid ?? '', - error: normalizeHttpError(error, this.logger), + error: tryNormalizeHttpError(error, this.logger), }), new OrganizationActions.OrganizationClearData(), ]) @@ -169,7 +169,7 @@ export class OrgUnitEffects { catchError((error: HttpErrorResponse) => of( new OrgUnitActions.LoadTreeFail({ - error: normalizeHttpError(error, this.logger), + error: tryNormalizeHttpError(error, this.logger), }) ) ) @@ -194,7 +194,7 @@ export class OrgUnitEffects { catchError((error: HttpErrorResponse) => of( new OrgUnitActions.LoadApprovalProcessesFail({ - error: normalizeHttpError(error, this.logger), + error: tryNormalizeHttpError(error, this.logger), }) ) ) @@ -241,7 +241,7 @@ export class OrgUnitEffects { orgUnitId, roleId, params, - error: normalizeHttpError(error, this.logger), + error: tryNormalizeHttpError(error, this.logger), }) ) ) @@ -272,7 +272,7 @@ export class OrgUnitEffects { of( new OrgUnitActions.AssignRoleFail({ orgCustomerId, - error: normalizeHttpError(error, this.logger), + error: tryNormalizeHttpError(error, this.logger), }) ) ) @@ -301,7 +301,7 @@ export class OrgUnitEffects { of( new OrgUnitActions.UnassignRoleFail({ orgCustomerId, - error: normalizeHttpError(error, this.logger), + error: tryNormalizeHttpError(error, this.logger), }) ) ) @@ -334,7 +334,7 @@ export class OrgUnitEffects { from([ new OrgUnitActions.AssignApproverFail({ orgCustomerId, - error: normalizeHttpError(error, this.logger), + error: tryNormalizeHttpError(error, this.logger), }), new OrganizationActions.OrganizationClearData(), ]) @@ -368,7 +368,7 @@ export class OrgUnitEffects { from([ new OrgUnitActions.UnassignApproverFail({ orgCustomerId, - error: normalizeHttpError(error, this.logger), + error: tryNormalizeHttpError(error, this.logger), }), new OrganizationActions.OrganizationClearData(), ]) @@ -406,7 +406,7 @@ export class OrgUnitEffects { from([ new OrgUnitActions.CreateAddressFail({ addressId: payload.address.id ?? '', - error: normalizeHttpError(error, this.logger), + error: tryNormalizeHttpError(error, this.logger), }), new OrganizationActions.OrganizationClearData(), ]) @@ -438,7 +438,7 @@ export class OrgUnitEffects { from([ new OrgUnitActions.UpdateAddressFail({ addressId: address.id ?? '', - error: normalizeHttpError(error, this.logger), + error: tryNormalizeHttpError(error, this.logger), }), new OrganizationActions.OrganizationClearData(), ]) @@ -470,7 +470,7 @@ export class OrgUnitEffects { from([ new OrgUnitActions.DeleteAddressFail({ addressId: payload.addressId, - error: normalizeHttpError(error, this.logger), + error: tryNormalizeHttpError(error, this.logger), }), new OrganizationActions.OrganizationClearData(), ]) diff --git a/feature-libs/organization/administration/core/store/effects/permission.effect.ts b/feature-libs/organization/administration/core/store/effects/permission.effect.ts index 1eddc2a6291..3afc85849a4 100644 --- a/feature-libs/organization/administration/core/store/effects/permission.effect.ts +++ b/feature-libs/organization/administration/core/store/effects/permission.effect.ts @@ -12,7 +12,7 @@ import { LoggerService, OrderApprovalPermissionType, StateUtils, - normalizeHttpError, + tryNormalizeHttpError, } from '@spartacus/core'; import { Observable, from, of } from 'rxjs'; import { catchError, map, switchMap } from 'rxjs/operators'; @@ -40,7 +40,7 @@ export class PermissionEffects { of( new PermissionActions.LoadPermissionFail({ permissionCode, - error: normalizeHttpError(error, this.logger), + error: tryNormalizeHttpError(error, this.logger), }) ) ) @@ -76,7 +76,7 @@ export class PermissionEffects { of( new PermissionActions.LoadPermissionsFail({ params: payload.params, - error: normalizeHttpError(error, this.logger), + error: tryNormalizeHttpError(error, this.logger), }) ) ) @@ -105,7 +105,7 @@ export class PermissionEffects { from([ new PermissionActions.CreatePermissionFail({ permissionCode: payload.permission.code ?? '', - error: normalizeHttpError(error, this.logger), + error: tryNormalizeHttpError(error, this.logger), }), new OrganizationActions.OrganizationClearData(), ]) @@ -135,7 +135,7 @@ export class PermissionEffects { from([ new PermissionActions.UpdatePermissionFail({ permissionCode: payload.permission.code ?? '', - error: normalizeHttpError(error, this.logger), + error: tryNormalizeHttpError(error, this.logger), }), new OrganizationActions.OrganizationClearData(), ]) @@ -162,7 +162,7 @@ export class PermissionEffects { catchError((error: HttpErrorResponse) => of( new PermissionActions.LoadPermissionTypesFail({ - error: normalizeHttpError(error, this.logger), + error: tryNormalizeHttpError(error, this.logger), }) ) ) diff --git a/feature-libs/organization/administration/core/store/effects/user-group.effect.ts b/feature-libs/organization/administration/core/store/effects/user-group.effect.ts index a5c2f0dd3f2..1c7f01ef09e 100644 --- a/feature-libs/organization/administration/core/store/effects/user-group.effect.ts +++ b/feature-libs/organization/administration/core/store/effects/user-group.effect.ts @@ -12,7 +12,7 @@ import { EntitiesModel, LoggerService, StateUtils, - normalizeHttpError, + tryNormalizeHttpError, } from '@spartacus/core'; import { Observable, from, of } from 'rxjs'; import { catchError, groupBy, map, mergeMap, switchMap } from 'rxjs/operators'; @@ -45,7 +45,7 @@ export class UserGroupEffects { of( new UserGroupActions.LoadUserGroupFail({ userGroupId, - error: normalizeHttpError(error, this.logger), + error: tryNormalizeHttpError(error, this.logger), }) ) ) @@ -81,7 +81,7 @@ export class UserGroupEffects { of( new UserGroupActions.LoadUserGroupsFail({ params: payload.params, - error: normalizeHttpError(error, this.logger), + error: tryNormalizeHttpError(error, this.logger), }) ) ) @@ -130,7 +130,7 @@ export class UserGroupEffects { new UserGroupActions.LoadPermissionsFail({ userGroupId: payload.userGroupId, params: payload.params, - error: normalizeHttpError(error, this.logger), + error: tryNormalizeHttpError(error, this.logger), }) ) ) @@ -183,7 +183,7 @@ export class UserGroupEffects { new UserGroupActions.LoadAvailableOrgCustomersFail({ userGroupId: payload.userGroupId, params: payload.params, - error: normalizeHttpError(error, this.logger), + error: tryNormalizeHttpError(error, this.logger), }) ) ) @@ -212,7 +212,7 @@ export class UserGroupEffects { from([ new UserGroupActions.CreateUserGroupFail({ userGroupId: payload.userGroup.uid ?? '', - error: normalizeHttpError(error, this.logger), + error: tryNormalizeHttpError(error, this.logger), }), new OrganizationActions.OrganizationClearData(), ]) @@ -243,7 +243,7 @@ export class UserGroupEffects { from([ new UserGroupActions.UpdateUserGroupFail({ userGroupId: payload.userGroup.uid ?? '', - error: normalizeHttpError(error, this.logger), + error: tryNormalizeHttpError(error, this.logger), }), new OrganizationActions.OrganizationClearData(), ]) @@ -273,7 +273,7 @@ export class UserGroupEffects { from([ new UserGroupActions.DeleteUserGroupFail({ userGroupId: payload.userGroupId, - error: normalizeHttpError(error, this.logger), + error: tryNormalizeHttpError(error, this.logger), }), new OrganizationActions.OrganizationClearData(), ]) @@ -311,7 +311,7 @@ export class UserGroupEffects { new UserGroupActions.AssignPermissionFail({ userGroupId: payload.userGroupId, permissionUid: payload.permissionUid, - error: normalizeHttpError(error, this.logger), + error: tryNormalizeHttpError(error, this.logger), }), new OrganizationActions.OrganizationClearData(), ]) @@ -345,7 +345,7 @@ export class UserGroupEffects { new UserGroupActions.AssignMemberFail({ userGroupId: payload.userGroupId, customerId: payload.customerId, - error: normalizeHttpError(error, this.logger), + error: tryNormalizeHttpError(error, this.logger), }), new OrganizationActions.OrganizationClearData(), ]) @@ -383,7 +383,7 @@ export class UserGroupEffects { new UserGroupActions.UnassignMemberFail({ userGroupId: payload.userGroupId, customerId: payload.customerId, - error: normalizeHttpError(error, this.logger), + error: tryNormalizeHttpError(error, this.logger), }), new OrganizationActions.OrganizationClearData(), ]) @@ -421,7 +421,7 @@ export class UserGroupEffects { new UserGroupActions.UnassignPermissionFail({ userGroupId: payload.userGroupId, permissionUid: payload.permissionUid, - error: normalizeHttpError(error, this.logger), + error: tryNormalizeHttpError(error, this.logger), }), new OrganizationActions.OrganizationClearData(), ]) @@ -453,7 +453,7 @@ export class UserGroupEffects { from([ new UserGroupActions.UnassignAllMembersFail({ userGroupId: payload.userGroupId, - error: normalizeHttpError(error, this.logger), + error: tryNormalizeHttpError(error, this.logger), }), new OrganizationActions.OrganizationClearData(), ]) diff --git a/feature-libs/organization/order-approval/core/services/order-approval.service.spec.ts b/feature-libs/organization/order-approval/core/services/order-approval.service.spec.ts index 4d98b67882a..25cf8e5d769 100644 --- a/feature-libs/organization/order-approval/core/services/order-approval.service.spec.ts +++ b/feature-libs/organization/order-approval/core/services/order-approval.service.spec.ts @@ -238,7 +238,7 @@ describe('OrderApprovalService', () => { store.dispatch( new OrderApprovalActions.MakeDecisionFail({ orderApprovalCode, - error: 'error', + error: new Error('error'), }) ); diff --git a/feature-libs/organization/order-approval/core/store/actions/order-approval.action.spec.ts b/feature-libs/organization/order-approval/core/store/actions/order-approval.action.spec.ts index e1282a2544a..bd49f560885 100644 --- a/feature-libs/organization/order-approval/core/store/actions/order-approval.action.spec.ts +++ b/feature-libs/organization/order-approval/core/store/actions/order-approval.action.spec.ts @@ -22,7 +22,7 @@ const orderApprovalDecision: OrderApprovalDecision = { }; const userId = 'xxx@xxx.xxx'; -const error = 'anError'; +const error = { message: 'anError' }; const params = { currentPage: 2 }; const query = '?pageSize=¤tPage=2&sort='; @@ -58,6 +58,7 @@ describe('OrderApproval Actions', () => { }); expect({ ...action }).toEqual({ + error, type: OrderApprovalActions.LOAD_ORDER_APPROVAL_FAIL, payload: { orderApprovalCode, @@ -109,15 +110,14 @@ describe('OrderApproval Actions', () => { it('should create the action', () => { const action = new OrderApprovalActions.LoadOrderApprovalsFail({ params, - error: { error }, + error, }); expect({ ...action }).toEqual({ + error, type: OrderApprovalActions.LOAD_ORDER_APPROVALS_FAIL, - payload: { params, error: { error } }, - meta: StateUtils.entityFailMeta(ORDER_APPROVAL_LIST, query, { - error, - }), + payload: { params, error }, + meta: StateUtils.entityFailMeta(ORDER_APPROVAL_LIST, query, error), }); }); }); @@ -166,6 +166,7 @@ describe('OrderApproval Actions', () => { }); expect({ ...action }).toEqual({ + error, type: OrderApprovalActions.MAKE_DECISION_FAIL, payload: { orderApprovalCode, @@ -174,10 +175,7 @@ describe('OrderApproval Actions', () => { meta: StateUtils.entityFailMeta( PROCESS_FEATURE, ORDER_APPROVAL_MAKE_DECISION_PROCESS_ID, - { - orderApprovalCode, - error, - } + error ), }); }); diff --git a/feature-libs/organization/order-approval/core/store/actions/order-approval.action.ts b/feature-libs/organization/order-approval/core/store/actions/order-approval.action.ts index 425f05707f1..1e114bc14ac 100644 --- a/feature-libs/organization/order-approval/core/store/actions/order-approval.action.ts +++ b/feature-libs/organization/order-approval/core/store/actions/order-approval.action.ts @@ -5,6 +5,7 @@ */ import { + ErrorAction, ListModel, PROCESS_FEATURE, SearchConfig, @@ -42,13 +43,18 @@ export const MAKE_DECISION_RESET = export class LoadOrderApproval extends StateUtils.EntityLoadAction { readonly type = LOAD_ORDER_APPROVAL; + constructor(public payload: { userId: string; orderApprovalCode: string }) { super(ORDER_APPROVAL_ENTITIES, payload.orderApprovalCode); } } -export class LoadOrderApprovalFail extends StateUtils.EntityFailAction { +export class LoadOrderApprovalFail + extends StateUtils.EntityFailAction + implements ErrorAction +{ readonly type = LOAD_ORDER_APPROVAL_FAIL; + constructor(public payload: { orderApprovalCode: string; error: any }) { super(ORDER_APPROVAL_ENTITIES, payload.orderApprovalCode, payload.error); } @@ -56,6 +62,7 @@ export class LoadOrderApprovalFail extends StateUtils.EntityFailAction { export class LoadOrderApprovalSuccess extends StateUtils.EntitySuccessAction { readonly type = LOAD_ORDER_APPROVAL_SUCCESS; + constructor(public payload: OrderApproval | OrderApproval[]) { super( ORDER_APPROVAL_ENTITIES, @@ -68,6 +75,7 @@ export class LoadOrderApprovalSuccess extends StateUtils.EntitySuccessAction { export class LoadOrderApprovals extends StateUtils.EntityLoadAction { readonly type = LOAD_ORDER_APPROVALS; + constructor( public payload: { userId: string; @@ -81,8 +89,12 @@ export class LoadOrderApprovals extends StateUtils.EntityLoadAction { } } -export class LoadOrderApprovalsFail extends StateUtils.EntityFailAction { +export class LoadOrderApprovalsFail + extends StateUtils.EntityFailAction + implements ErrorAction +{ readonly type = LOAD_ORDER_APPROVALS_FAIL; + constructor(public payload: { params: SearchConfig; error: any }) { super( ORDER_APPROVAL_LIST, @@ -94,6 +106,7 @@ export class LoadOrderApprovalsFail extends StateUtils.EntityFailAction { export class LoadOrderApprovalsSuccess extends StateUtils.EntitySuccessAction { readonly type = LOAD_ORDER_APPROVALS_SUCCESS; + constructor( public payload: { page: ListModel; @@ -109,6 +122,7 @@ export class LoadOrderApprovalsSuccess extends StateUtils.EntitySuccessAction { export class MakeDecision extends StateUtils.EntityLoadAction { readonly type = MAKE_DECISION; + constructor( public payload: { userId: string; @@ -120,15 +134,24 @@ export class MakeDecision extends StateUtils.EntityLoadAction { } } -export class MakeDecisionFail extends StateUtils.EntityFailAction { +export class MakeDecisionFail + extends StateUtils.EntityFailAction + implements ErrorAction +{ readonly type = MAKE_DECISION_FAIL; + constructor(public payload: { orderApprovalCode: string; error: any }) { - super(PROCESS_FEATURE, ORDER_APPROVAL_MAKE_DECISION_PROCESS_ID, payload); + super( + PROCESS_FEATURE, + ORDER_APPROVAL_MAKE_DECISION_PROCESS_ID, + payload.error + ); } } export class MakeDecisionSuccess extends StateUtils.EntitySuccessAction { readonly type = MAKE_DECISION_SUCCESS; + constructor( public payload: { orderApprovalCode: string; @@ -141,6 +164,7 @@ export class MakeDecisionSuccess extends StateUtils.EntitySuccessAction { export class MakeDecisionReset extends StateUtils.EntityLoaderResetAction { readonly type = MAKE_DECISION_RESET; + constructor() { super(PROCESS_FEATURE, ORDER_APPROVAL_MAKE_DECISION_PROCESS_ID); } diff --git a/feature-libs/organization/order-approval/core/store/effects/order-approval.effect.ts b/feature-libs/organization/order-approval/core/store/effects/order-approval.effect.ts index d24a246894c..ffbb140c317 100644 --- a/feature-libs/organization/order-approval/core/store/effects/order-approval.effect.ts +++ b/feature-libs/organization/order-approval/core/store/effects/order-approval.effect.ts @@ -12,7 +12,7 @@ import { LoggerService, OCC_USER_ID_ANONYMOUS, StateUtils, - normalizeHttpError, + tryNormalizeHttpError, } from '@spartacus/core'; import { Observable, of } from 'rxjs'; import { catchError, filter, map, switchMap } from 'rxjs/operators'; @@ -43,7 +43,7 @@ export class OrderApprovalEffects { of( new OrderApprovalActions.LoadOrderApprovalFail({ orderApprovalCode, - error: normalizeHttpError(error, this.logger), + error: tryNormalizeHttpError(error, this.logger), }) ) ) @@ -80,7 +80,7 @@ export class OrderApprovalEffects { of( new OrderApprovalActions.LoadOrderApprovalsFail({ params: params, - error: normalizeHttpError(error, this.logger), + error: tryNormalizeHttpError(error, this.logger), }) ) ) @@ -116,7 +116,7 @@ export class OrderApprovalEffects { of( new OrderApprovalActions.MakeDecisionFail({ orderApprovalCode: orderApprovalCode, - error: normalizeHttpError(error, this.logger), + error: tryNormalizeHttpError(error, this.logger), }) ) ) diff --git a/feature-libs/organization/unit-order/core/store/actions/unit-order.action.spec.ts b/feature-libs/organization/unit-order/core/store/actions/unit-order.action.spec.ts index b6789f1adbe..9b3b7a53afe 100644 --- a/feature-libs/organization/unit-order/core/store/actions/unit-order.action.spec.ts +++ b/feature-libs/organization/unit-order/core/store/actions/unit-order.action.spec.ts @@ -30,12 +30,13 @@ describe('OrdersActions', () => { describe('LoadUnitOrdersFail Action', () => { it('should create the action', () => { - const error = 'mockError'; + const error = { message: 'mockError' }; const action = new UnitOrderActions.LoadUnitOrdersFail(error); expect({ ...action }).toEqual({ type: UnitOrderActions.LOAD_UNIT_ORDERS_FAIL, payload: error, + error, meta: StateUtils.failMeta(UNIT_ORDERS, error), }); }); @@ -97,12 +98,13 @@ describe('Order Details Actions', () => { describe('LoadOrderDetailsFail Action', () => { it('should create the action', () => { - const error = 'mockError'; + const error = { message: 'mockError' }; const action = new UnitOrderActions.LoadOrderDetailsFail(error); expect({ ...action }).toEqual({ type: UnitOrderActions.LOAD_ORDER_DETAILS_FAIL, payload: error, + error, meta: StateUtils.failMeta(UNIT_ORDER_DETAILS, error), }); }); diff --git a/feature-libs/organization/unit-order/core/store/actions/unit-order.action.ts b/feature-libs/organization/unit-order/core/store/actions/unit-order.action.ts index 276ba1bb4e8..b66988f6f04 100644 --- a/feature-libs/organization/unit-order/core/store/actions/unit-order.action.ts +++ b/feature-libs/organization/unit-order/core/store/actions/unit-order.action.ts @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { StateUtils } from '@spartacus/core'; +import { ErrorAction, StateUtils } from '@spartacus/core'; import { Order, OrderHistoryList } from '@spartacus/order/root'; import { UNIT_ORDERS, UNIT_ORDER_DETAILS } from '../unit-order-state'; @@ -22,6 +22,7 @@ export const CLEAR_ORDER_DETAILS = '[Unit Order] Clear Unit Order Details'; export class LoadUnitOrders extends StateUtils.LoaderLoadAction { readonly type = LOAD_UNIT_ORDERS; + constructor( public payload: { userId: string; @@ -35,8 +36,12 @@ export class LoadUnitOrders extends StateUtils.LoaderLoadAction { } } -export class LoadUnitOrdersFail extends StateUtils.LoaderFailAction { +export class LoadUnitOrdersFail + extends StateUtils.LoaderFailAction + implements ErrorAction +{ readonly type = LOAD_UNIT_ORDERS_FAIL; + constructor(public payload: any) { super(UNIT_ORDERS, payload); } @@ -44,6 +49,7 @@ export class LoadUnitOrdersFail extends StateUtils.LoaderFailAction { export class LoadUnitOrdersSuccess extends StateUtils.LoaderSuccessAction { readonly type = LOAD_UNIT_ORDERS_SUCCESS; + constructor(public payload?: OrderHistoryList) { super(UNIT_ORDERS); } @@ -51,6 +57,7 @@ export class LoadUnitOrdersSuccess extends StateUtils.LoaderSuccessAction { export class ClearUnitOrders extends StateUtils.LoaderResetAction { readonly type = CLEAR_UNIT_ORDERS; + constructor() { super(UNIT_ORDERS); } @@ -58,6 +65,7 @@ export class ClearUnitOrders extends StateUtils.LoaderResetAction { export class LoadOrderDetails extends StateUtils.LoaderLoadAction { readonly type = LOAD_ORDER_DETAILS; + constructor( public payload: { userId: string; @@ -68,8 +76,12 @@ export class LoadOrderDetails extends StateUtils.LoaderLoadAction { } } -export class LoadOrderDetailsFail extends StateUtils.LoaderFailAction { +export class LoadOrderDetailsFail + extends StateUtils.LoaderFailAction + implements ErrorAction +{ readonly type = LOAD_ORDER_DETAILS_FAIL; + constructor(public payload: any) { super(UNIT_ORDER_DETAILS, payload); } @@ -77,6 +89,7 @@ export class LoadOrderDetailsFail extends StateUtils.LoaderFailAction { export class LoadOrderDetailsSuccess extends StateUtils.LoaderSuccessAction { readonly type = LOAD_ORDER_DETAILS_SUCCESS; + constructor(public payload: Order) { super(UNIT_ORDER_DETAILS); } @@ -84,6 +97,7 @@ export class LoadOrderDetailsSuccess extends StateUtils.LoaderSuccessAction { export class ClearOrderDetails extends StateUtils.LoaderResetAction { readonly type = CLEAR_ORDER_DETAILS; + constructor() { super(UNIT_ORDER_DETAILS); } diff --git a/feature-libs/organization/unit-order/core/store/effects/unit-order.effect.spec.ts b/feature-libs/organization/unit-order/core/store/effects/unit-order.effect.spec.ts index 8c9ae636375..f38f80c58a2 100644 --- a/feature-libs/organization/unit-order/core/store/effects/unit-order.effect.spec.ts +++ b/feature-libs/organization/unit-order/core/store/effects/unit-order.effect.spec.ts @@ -4,7 +4,7 @@ import { TestBed } from '@angular/core/testing'; import { Actions } from '@ngrx/effects'; import { provideMockActions } from '@ngrx/effects/testing'; import { Action } from '@ngrx/store'; -import { normalizeHttpError, SiteContextActions } from '@spartacus/core'; +import { SiteContextActions, tryNormalizeHttpError } from '@spartacus/core'; import { Order, OrderHistoryList } from '@spartacus/order/root'; import { cold, hot } from 'jasmine-marbles'; import { Observable, of, throwError } from 'rxjs'; @@ -89,7 +89,7 @@ describe('Orders effect', () => { }); const completion = new UnitOrderActions.LoadUnitOrdersFail( - normalizeHttpError(mockError, new MockLoggerService()) + tryNormalizeHttpError(mockError, new MockLoggerService()) ); actions$ = hot('-a', { a: action }); @@ -135,7 +135,7 @@ describe('Orders effect', () => { }); it('should handle failures for load order details', () => { - const mockNormalizedError = normalizeHttpError( + const mockNormalizedError = tryNormalizeHttpError( mockError, new MockLoggerService() ); diff --git a/feature-libs/organization/unit-order/core/store/effects/unit-order.effect.ts b/feature-libs/organization/unit-order/core/store/effects/unit-order.effect.ts index 970c5402628..498d8e3edd8 100644 --- a/feature-libs/organization/unit-order/core/store/effects/unit-order.effect.ts +++ b/feature-libs/organization/unit-order/core/store/effects/unit-order.effect.ts @@ -9,7 +9,7 @@ import { Actions, createEffect, ofType } from '@ngrx/effects'; import { LoggerService, SiteContextActions, - normalizeHttpError, + tryNormalizeHttpError, } from '@spartacus/core'; import { Order, OrderHistoryList } from '@spartacus/order/root'; import { Observable, of } from 'rxjs'; @@ -47,7 +47,7 @@ export class UnitOrderEffect { catchError((error) => of( new UnitOrderActions.LoadUnitOrdersFail( - normalizeHttpError(error, this.logger) + tryNormalizeHttpError(error, this.logger) ) ) ) @@ -81,7 +81,7 @@ export class UnitOrderEffect { catchError((error) => of( new UnitOrderActions.LoadOrderDetailsFail( - normalizeHttpError(error, this.logger) + tryNormalizeHttpError(error, this.logger) ) ) ) diff --git a/feature-libs/organization/unit-order/core/store/reducers/unit-order.reducer.spec.ts b/feature-libs/organization/unit-order/core/store/reducers/unit-order.reducer.spec.ts index 24d404e0811..1cfc7f99d8d 100644 --- a/feature-libs/organization/unit-order/core/store/reducers/unit-order.reducer.spec.ts +++ b/feature-libs/organization/unit-order/core/store/reducers/unit-order.reducer.spec.ts @@ -45,7 +45,9 @@ describe('Unit Order Reducer', () => { describe('LOAD_UNIT_ORDERS_FAIL action', () => { it('should return the initial state', () => { - const action = new UnitOrderActions.LoadUnitOrdersFail('error'); + const action = new UnitOrderActions.LoadUnitOrdersFail( + new Error('error') + ); const state = unitOrdersReducer(initialState, action); expect(state).toEqual(initialState); }); diff --git a/feature-libs/pickup-in-store/core/store/actions/pickup-location.action.ts b/feature-libs/pickup-in-store/core/store/actions/pickup-location.action.ts index 2c1d48056b3..b31c7fc0e55 100644 --- a/feature-libs/pickup-in-store/core/store/actions/pickup-location.action.ts +++ b/feature-libs/pickup-in-store/core/store/actions/pickup-location.action.ts @@ -5,6 +5,7 @@ */ import { createAction, props } from '@ngrx/store'; +import { TypedAction } from '@ngrx/store/src/models'; import { PointOfService } from '@spartacus/core'; import { AugmentedPointOfService, @@ -80,8 +81,23 @@ export const SetStoreDetailsSuccess = createAction( STORE_DETAILS_SUCCESS, props<{ payload: PointOfService }>() ); - -export const SetStoreDetailsFailure = createAction( +// The `error` property was added later to the return type, +// to be recognizable by `CxErrorHandlerEffect` (which expects +// `error` property in every fail action). +// However, we keep the original property `payload` as the only +// one required _input props_ parameter, to avoid introducing a breaking change. +const _SetStoreDetailsFailure = createAction( STORE_DETAILS_FAIL, props<{ payload: any }>() ); +export const SetStoreDetailsFailure = ($props: { + payload: any; +}): { + payload: any; + error: any; +} & TypedAction => { + return { + ..._SetStoreDetailsFailure($props), + error: $props.payload, + }; +}; diff --git a/feature-libs/pickup-in-store/core/store/actions/stock.action.spec.ts b/feature-libs/pickup-in-store/core/store/actions/stock.action.spec.ts index 8f5f473a705..1dceb0a06a1 100644 --- a/feature-libs/pickup-in-store/core/store/actions/stock.action.spec.ts +++ b/feature-libs/pickup-in-store/core/store/actions/stock.action.spec.ts @@ -1,15 +1,15 @@ import { STOCK_DATA } from '../stock-state'; import { - ClearStockData, CLEAR_STOCK_DATA, - StockLevel, - StockLevelFail, - StockLevelOnHold, - StockLevelSuccess, + ClearStockData, STOCK_LEVEL, STOCK_LEVEL_FAIL, STOCK_LEVEL_ON_HOLD, STOCK_LEVEL_SUCCESS, + StockLevel, + StockLevelFail, + StockLevelOnHold, + StockLevelSuccess, } from './stock.action'; describe('[Stock] Actions', () => { @@ -60,11 +60,13 @@ describe('[Stock] Actions', () => { }, }, payload: {}, + error: {}, }; expect(RESULT.type).toEqual(EXPECTED.type); expect(RESULT.meta).toEqual(EXPECTED.meta); expect(RESULT.payload).toEqual(EXPECTED.payload); + expect(RESULT.error).toEqual(EXPECTED.error); }); it('StockLevelSuccess', () => { diff --git a/feature-libs/pickup-in-store/core/store/actions/stock.action.ts b/feature-libs/pickup-in-store/core/store/actions/stock.action.ts index 7c40ed7d7fd..09f62b1dcb1 100644 --- a/feature-libs/pickup-in-store/core/store/actions/stock.action.ts +++ b/feature-libs/pickup-in-store/core/store/actions/stock.action.ts @@ -5,7 +5,12 @@ */ import { createAction, props } from '@ngrx/store'; -import { StateUtils, Stock, StoreFinderStockSearchPage } from '@spartacus/core'; +import { + ErrorAction, + StateUtils, + Stock, + StoreFinderStockSearchPage, +} from '@spartacus/core'; import { StockLocationSearchParams } from '@spartacus/pickup-in-store/root'; import { STOCK_DATA } from '../stock-state'; @@ -21,6 +26,7 @@ export const STOCK_LEVEL_AT_STORE_SUCCESS = export class StockLevel extends StateUtils.LoaderLoadAction { readonly type = STOCK_LEVEL; + constructor(public payload: StockLocationSearchParams) { super(STOCK_DATA); } @@ -28,13 +34,18 @@ export class StockLevel extends StateUtils.LoaderLoadAction { export class StockLevelOnHold extends StateUtils.LoaderLoadAction { readonly type = STOCK_LEVEL_ON_HOLD; + constructor() { super(STOCK_DATA); } } -export class StockLevelFail extends StateUtils.LoaderFailAction { +export class StockLevelFail + extends StateUtils.LoaderFailAction + implements ErrorAction +{ readonly type = STOCK_LEVEL_FAIL; + constructor(public payload: any) { super(STOCK_DATA, payload); } @@ -47,6 +58,7 @@ export type StockLevelSuccessPayload = { export class StockLevelSuccess extends StateUtils.LoaderSuccessAction { readonly type = STOCK_LEVEL_SUCCESS; + constructor(public payload: StockLevelSuccessPayload) { super(STOCK_DATA); } @@ -54,6 +66,7 @@ export class StockLevelSuccess extends StateUtils.LoaderSuccessAction { export class ClearStockData extends StateUtils.LoaderResetAction { readonly type = CLEAR_STOCK_DATA; + constructor() { super(STOCK_DATA); } diff --git a/feature-libs/pickup-in-store/core/store/effects/pickup-location.effect.spec.ts b/feature-libs/pickup-in-store/core/store/effects/pickup-location.effect.spec.ts index 1f5668dacc9..b57082c9a7d 100644 --- a/feature-libs/pickup-in-store/core/store/effects/pickup-location.effect.spec.ts +++ b/feature-libs/pickup-in-store/core/store/effects/pickup-location.effect.spec.ts @@ -3,7 +3,7 @@ import { HttpClientTestingModule } from '@angular/common/http/testing'; import { TestBed } from '@angular/core/testing'; import { provideMockActions } from '@ngrx/effects/testing'; import { StoreModule } from '@ngrx/store'; -import { normalizeHttpError } from '@spartacus/core'; +import { tryNormalizeHttpError } from '@spartacus/core'; import { cold, hot } from 'jasmine-marbles'; import { Observable } from 'rxjs'; @@ -91,7 +91,7 @@ describe('PickupLocationEffect with Error', () => { const error = new HttpErrorResponse({ error: 'error' }); const actionFailure = SetStoreDetailsFailure({ - payload: normalizeHttpError(error, new MockLoggerService()), + payload: tryNormalizeHttpError(error, new MockLoggerService()), }); actions$ = hot('-a', { a: action }); const expected = cold('-(b)', { b: actionFailure }); diff --git a/feature-libs/pickup-in-store/core/store/effects/pickup-location.effect.ts b/feature-libs/pickup-in-store/core/store/effects/pickup-location.effect.ts index c6662e42589..b252fc9876d 100644 --- a/feature-libs/pickup-in-store/core/store/effects/pickup-location.effect.ts +++ b/feature-libs/pickup-in-store/core/store/effects/pickup-location.effect.ts @@ -4,9 +4,9 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { Injectable, inject } from '@angular/core'; +import { inject, Injectable } from '@angular/core'; import { Actions, createEffect, ofType } from '@ngrx/effects'; -import { LoggerService, normalizeHttpError } from '@spartacus/core'; +import { LoggerService, tryNormalizeHttpError } from '@spartacus/core'; import { of } from 'rxjs'; import { catchError, map, mergeMap } from 'rxjs/operators'; import { PickupLocationConnector } from '../../connectors'; @@ -41,7 +41,7 @@ export class PickupLocationEffect { catchError((error) => of( PickupLocationActions.SetStoreDetailsFailure({ - payload: normalizeHttpError(error, this.logger), + payload: tryNormalizeHttpError(error, this.logger), }) ) ) diff --git a/feature-libs/pickup-in-store/core/store/effects/stock.effect.ts b/feature-libs/pickup-in-store/core/store/effects/stock.effect.ts index f84c2eed5a4..ab807e64cda 100644 --- a/feature-libs/pickup-in-store/core/store/effects/stock.effect.ts +++ b/feature-libs/pickup-in-store/core/store/effects/stock.effect.ts @@ -6,7 +6,7 @@ import { Injectable, inject } from '@angular/core'; import { Actions, createEffect, ofType } from '@ngrx/effects'; -import { LoggerService, normalizeHttpError } from '@spartacus/core'; +import { LoggerService, tryNormalizeHttpError } from '@spartacus/core'; import { of } from 'rxjs'; import { catchError, concatMap, map, switchMap } from 'rxjs/operators'; import { StockConnector } from '../../connectors/index'; @@ -39,7 +39,7 @@ export class StockEffect { catchError((error) => of( new StockLevelActions.StockLevelFail( - normalizeHttpError(error, this.logger) + tryNormalizeHttpError(error, this.logger) ) ) ) diff --git a/feature-libs/product-configurator/rulebased/core/state/actions/configurator-cart.action.ts b/feature-libs/product-configurator/rulebased/core/state/actions/configurator-cart.action.ts index 400ad7d0f5e..47239dd1906 100644 --- a/feature-libs/product-configurator/rulebased/core/state/actions/configurator-cart.action.ts +++ b/feature-libs/product-configurator/rulebased/core/state/actions/configurator-cart.action.ts @@ -6,7 +6,7 @@ import { Action } from '@ngrx/store'; import { MULTI_CART_DATA } from '@spartacus/cart/base/core'; -import { StateUtils } from '@spartacus/core'; +import { ErrorAction, StateUtils } from '@spartacus/core'; import { CommonConfigurator } from '@spartacus/product-configurator/common'; import { Configurator } from '../../model/configurator.model'; import { CONFIGURATOR_DATA } from '../configurator-state'; @@ -52,7 +52,10 @@ export class ReadCartEntryConfigurationSuccess extends StateUtils.EntitySuccessA } } -export class ReadCartEntryConfigurationFail extends StateUtils.EntityFailAction { +export class ReadCartEntryConfigurationFail + extends StateUtils.EntityFailAction + implements ErrorAction +{ readonly type = READ_CART_ENTRY_CONFIGURATION_FAIL; constructor(public payload: { ownerKey: string; error: any }) { super(CONFIGURATOR_DATA, payload.ownerKey, payload.error); @@ -75,7 +78,10 @@ export class ReadOrderEntryConfigurationSuccess extends StateUtils.EntitySuccess } } -export class ReadOrderEntryConfigurationFail extends StateUtils.EntityFailAction { +export class ReadOrderEntryConfigurationFail + extends StateUtils.EntityFailAction + implements ErrorAction +{ readonly type = READ_ORDER_ENTRY_CONFIGURATION_FAIL; constructor(public payload: { ownerKey: string; error: any }) { super(CONFIGURATOR_DATA, payload.ownerKey, payload.error); diff --git a/feature-libs/product-configurator/rulebased/core/state/actions/configurator-variant.action.spec.ts b/feature-libs/product-configurator/rulebased/core/state/actions/configurator-variant.action.spec.ts index 0069ce16b76..6ea20263c6f 100644 --- a/feature-libs/product-configurator/rulebased/core/state/actions/configurator-variant.action.spec.ts +++ b/feature-libs/product-configurator/rulebased/core/state/actions/configurator-variant.action.spec.ts @@ -43,7 +43,7 @@ describe('ConfiguratorVariantActions', () => { it('should provide variant search fail action with proper type', () => { const action = new ConfiguratorVariantActions.SearchVariantsFail({ ownerKey: CONFIGURATION.owner.key, - error: 'Error', + error: new Error('Error'), }); expect(action.type).toBe(ConfiguratorVariantActions.SEARCH_VARIANTS_FAIL); }); diff --git a/feature-libs/product-configurator/rulebased/core/state/actions/configurator-variant.action.ts b/feature-libs/product-configurator/rulebased/core/state/actions/configurator-variant.action.ts index 43cc958c133..8693978d3b4 100644 --- a/feature-libs/product-configurator/rulebased/core/state/actions/configurator-variant.action.ts +++ b/feature-libs/product-configurator/rulebased/core/state/actions/configurator-variant.action.ts @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { StateUtils } from '@spartacus/core'; +import { ErrorAction, StateUtils } from '@spartacus/core'; import { Configurator } from '../../model/configurator.model'; import { CONFIGURATOR_DATA } from '../configurator-state'; @@ -19,7 +19,10 @@ export class SearchVariants extends StateUtils.EntityLoadAction { } } -export class SearchVariantsFail extends StateUtils.EntityFailAction { +export class SearchVariantsFail + extends StateUtils.EntityFailAction + implements ErrorAction +{ readonly type = SEARCH_VARIANTS_FAIL; constructor(public payload: { ownerKey: string; error: any }) { super(CONFIGURATOR_DATA, payload.ownerKey, payload.error); diff --git a/feature-libs/product-configurator/rulebased/core/state/actions/configurator.action.spec.ts b/feature-libs/product-configurator/rulebased/core/state/actions/configurator.action.spec.ts index d3ef1ed13a6..794a0de9764 100644 --- a/feature-libs/product-configurator/rulebased/core/state/actions/configurator.action.spec.ts +++ b/feature-libs/product-configurator/rulebased/core/state/actions/configurator.action.spec.ts @@ -79,12 +79,13 @@ describe('ConfiguratorActions', () => { describe('ReadConfigurationFail', () => { it('Should create the action', () => { - const error = 'anError'; + const error = { message: 'anError' }; const action = new ConfiguratorActions.ReadConfigurationFail({ ownerKey: PRODUCT_CODE, error: error, }); expect({ ...action }).toEqual({ + error, type: ConfiguratorActions.READ_CONFIGURATION_FAIL, payload: { ownerKey: PRODUCT_CODE, @@ -135,13 +136,14 @@ describe('ConfiguratorActions', () => { describe('UpdateConfigurationFail', () => { it('Should create the action', () => { - const error = 'anError'; + const error = { message: 'anError' }; const action = new ConfiguratorActions.UpdateConfigurationFail({ configuration: CONFIGURATION, error: error, }); expect({ ...action }).toEqual({ + error, type: ConfiguratorActions.UPDATE_CONFIGURATION_FAIL, payload: { configuration: CONFIGURATION, @@ -201,13 +203,14 @@ describe('ConfiguratorActions', () => { }); it('should allow to create the fail action', () => { - const error = 'anError'; + const error = { message: 'anError' }; const action = new ConfiguratorActions.UpdateConfigurationOverviewFail({ ownerKey: CONFIGURATION.owner.key, error: error, }); expect({ ...action }).toEqual({ + error, type: ConfiguratorActions.UPDATE_CONFIGURATION_OVERVIEW_FAIL, payload: { ownerKey: CONFIGURATION.owner.key, diff --git a/feature-libs/product-configurator/rulebased/core/state/actions/configurator.action.ts b/feature-libs/product-configurator/rulebased/core/state/actions/configurator.action.ts index 40e4d117165..579a9ed9e56 100644 --- a/feature-libs/product-configurator/rulebased/core/state/actions/configurator.action.ts +++ b/feature-libs/product-configurator/rulebased/core/state/actions/configurator.action.ts @@ -5,7 +5,7 @@ */ import { Action } from '@ngrx/store'; -import { StateUtils } from '@spartacus/core'; +import { ErrorAction, StateUtils } from '@spartacus/core'; import { CommonConfigurator } from '@spartacus/product-configurator/common'; import { Configurator } from '../../model/configurator.model'; import { CONFIGURATOR_DATA } from '../configurator-state'; @@ -72,6 +72,7 @@ export const CHECK_CONFLICT_DIALOG = '[Configurator] Check conflict dialog'; export class CreateConfiguration extends StateUtils.EntityLoadAction { readonly type = CREATE_CONFIGURATION; + constructor( public payload: { owner: CommonConfigurator.Owner; @@ -83,8 +84,12 @@ export class CreateConfiguration extends StateUtils.EntityLoadAction { } } -export class CreateConfigurationFail extends StateUtils.EntityFailAction { +export class CreateConfigurationFail + extends StateUtils.EntityFailAction + implements ErrorAction +{ readonly type = CREATE_CONFIGURATION_FAIL; + constructor( public payload: { ownerKey: string; @@ -97,6 +102,7 @@ export class CreateConfigurationFail extends StateUtils.EntityFailAction { export class CreateConfigurationSuccess extends StateUtils.EntitySuccessAction { readonly type = CREATE_CONFIGURATION_SUCCESS; + constructor(public payload: Configurator.Configuration) { super(CONFIGURATOR_DATA, payload.owner.key); } @@ -104,6 +110,7 @@ export class CreateConfigurationSuccess extends StateUtils.EntitySuccessAction { export class ReadConfiguration extends StateUtils.EntityLoadAction { readonly type = READ_CONFIGURATION; + constructor( public payload: { configuration: Configurator.Configuration; @@ -114,8 +121,12 @@ export class ReadConfiguration extends StateUtils.EntityLoadAction { } } -export class ReadConfigurationFail extends StateUtils.EntityFailAction { +export class ReadConfigurationFail + extends StateUtils.EntityFailAction + implements ErrorAction +{ readonly type = READ_CONFIGURATION_FAIL; + constructor(public payload: { ownerKey: string; error: any }) { super(CONFIGURATOR_DATA, payload.ownerKey, payload.error); } @@ -123,6 +134,7 @@ export class ReadConfigurationFail extends StateUtils.EntityFailAction { export class ReadConfigurationSuccess extends StateUtils.EntitySuccessAction { readonly type = READ_CONFIGURATION_SUCCESS; + constructor(public payload: Configurator.Configuration) { super(CONFIGURATOR_DATA, payload.owner.key); } @@ -130,6 +142,7 @@ export class ReadConfigurationSuccess extends StateUtils.EntitySuccessAction { export class UpdateConfiguration extends StateUtils.EntityProcessesIncrementAction { readonly type = UPDATE_CONFIGURATION; + constructor(public payload: Configurator.Configuration) { super(CONFIGURATOR_DATA, payload.owner.key); this.meta.loader = { @@ -138,8 +151,13 @@ export class UpdateConfiguration extends StateUtils.EntityProcessesIncrementActi } } -export class UpdateConfigurationFail extends StateUtils.EntityProcessesDecrementAction { +export class UpdateConfigurationFail + extends StateUtils.EntityProcessesDecrementAction + implements ErrorAction +{ + public error: any; readonly type = UPDATE_CONFIGURATION_FAIL; + constructor( public payload: { configuration: Configurator.Configuration; error: any } ) { @@ -147,11 +165,13 @@ export class UpdateConfigurationFail extends StateUtils.EntityProcessesDecrement this.meta.loader = { error: payload.error, }; + this.error = payload.error; } } export class UpdateConfigurationSuccess extends StateUtils.EntityProcessesDecrementAction { readonly type = UPDATE_CONFIGURATION_SUCCESS; + constructor(public payload: Configurator.Configuration) { super(CONFIGURATOR_DATA, payload.owner.key); } @@ -159,13 +179,18 @@ export class UpdateConfigurationSuccess extends StateUtils.EntityProcessesDecrem export class UpdateConfigurationFinalizeSuccess extends StateUtils.EntitySuccessAction { readonly type = UPDATE_CONFIGURATION_FINALIZE_SUCCESS; + constructor(public payload: Configurator.Configuration) { super(CONFIGURATOR_DATA, payload.owner.key); } } -export class UpdateConfigurationFinalizeFail extends StateUtils.EntityFailAction { +export class UpdateConfigurationFinalizeFail + extends StateUtils.EntityFailAction + implements ErrorAction +{ readonly type = UPDATE_CONFIGURATION_FINALIZE_FAIL; + constructor(public payload: Configurator.Configuration) { super(CONFIGURATOR_DATA, payload.owner.key); } @@ -173,12 +198,18 @@ export class UpdateConfigurationFinalizeFail extends StateUtils.EntityFailAction export class UpdatePriceSummary extends StateUtils.EntityLoadAction { readonly type = UPDATE_PRICE_SUMMARY; + constructor(public payload: Configurator.Configuration) { super(CONFIGURATOR_DATA, payload.owner.key); } } -export class UpdatePriceSummaryFail extends StateUtils.EntityFailAction { + +export class UpdatePriceSummaryFail + extends StateUtils.EntityFailAction + implements ErrorAction +{ readonly type = UPDATE_PRICE_SUMMARY_FAIL; + constructor(public payload: { ownerKey: string; error: any }) { super(CONFIGURATOR_DATA, payload.ownerKey, payload.error); } @@ -211,6 +242,7 @@ export class UpdatePriceSummarySuccess extends StateUtils.EntitySuccessAction { export class ChangeGroup extends StateUtils.EntityLoadAction { readonly type = CHANGE_GROUP; + constructor( public payload: { configuration: Configurator.Configuration; @@ -228,6 +260,7 @@ export class ChangeGroup extends StateUtils.EntityLoadAction { export class ChangeGroupFinalize extends StateUtils.EntityLoadAction { readonly type = CHANGE_GROUP_FINALIZE; + constructor(public payload: Configurator.Configuration) { super(CONFIGURATOR_DATA, payload.owner.key); } @@ -235,6 +268,7 @@ export class ChangeGroupFinalize extends StateUtils.EntityLoadAction { export class RemoveConfiguration extends StateUtils.EntityLoaderResetAction { readonly type = REMOVE_CONFIGURATION; + constructor(public payload: { ownerKey: string | string[] }) { super(CONFIGURATOR_DATA, payload.ownerKey); } @@ -242,13 +276,18 @@ export class RemoveConfiguration extends StateUtils.EntityLoaderResetAction { export class GetConfigurationOverview extends StateUtils.EntityLoadAction { readonly type = GET_CONFIGURATION_OVERVIEW; + constructor(public payload: Configurator.Configuration) { super(CONFIGURATOR_DATA, payload.owner.key); } } -export class GetConfigurationOverviewFail extends StateUtils.EntityFailAction { +export class GetConfigurationOverviewFail + extends StateUtils.EntityFailAction + implements ErrorAction +{ readonly type = GET_CONFIGURATION_OVERVIEW_FAIL; + constructor(public payload: { ownerKey: string; error: any }) { super(CONFIGURATOR_DATA, payload.ownerKey, payload.error); } @@ -256,6 +295,7 @@ export class GetConfigurationOverviewFail extends StateUtils.EntityFailAction { export class GetConfigurationOverviewSuccess extends StateUtils.EntitySuccessAction { readonly type = GET_CONFIGURATION_OVERVIEW_SUCCESS; + constructor( public payload: { ownerKey: string; overview: Configurator.Overview } ) { @@ -265,13 +305,18 @@ export class GetConfigurationOverviewSuccess extends StateUtils.EntitySuccessAct export class UpdateConfigurationOverview extends StateUtils.EntityLoadAction { readonly type = UPDATE_CONFIGURATION_OVERVIEW; + constructor(public payload: Configurator.Configuration) { super(CONFIGURATOR_DATA, payload.owner.key); } } -export class UpdateConfigurationOverviewFail extends StateUtils.EntityFailAction { +export class UpdateConfigurationOverviewFail + extends StateUtils.EntityFailAction + implements ErrorAction +{ readonly type = UPDATE_CONFIGURATION_OVERVIEW_FAIL; + constructor(public payload: { ownerKey: string; error: any }) { super(CONFIGURATOR_DATA, payload.ownerKey, payload.error); } @@ -279,6 +324,7 @@ export class UpdateConfigurationOverviewFail extends StateUtils.EntityFailAction export class UpdateConfigurationOverviewSuccess extends StateUtils.EntitySuccessAction { readonly type = UPDATE_CONFIGURATION_OVERVIEW_SUCCESS; + constructor( public payload: { ownerKey: string; overview: Configurator.Overview } ) { @@ -327,6 +373,7 @@ export class SetMenuParentGroup extends StateUtils.EntitySuccessAction { export class SetGroupsVisited extends StateUtils.EntitySuccessAction { readonly type = SET_GROUPS_VISITED; + constructor(public payload: { entityKey: string; visitedGroups: string[] }) { super(CONFIGURATOR_DATA, payload.entityKey, payload.visitedGroups); } @@ -334,6 +381,7 @@ export class SetGroupsVisited extends StateUtils.EntitySuccessAction { export class RemoveProductBoundConfigurations implements Action { readonly type = REMOVE_PRODUCT_BOUND_CONFIGURATIONS; + constructor() { // Intentional Empty Constructor } @@ -341,6 +389,7 @@ export class RemoveProductBoundConfigurations implements Action { export class DissmissConflictDialoge extends StateUtils.EntitySuccessAction { readonly type = DISMISS_CONFLICT_DIALOG; + constructor(public ownerKey: string) { super(CONFIGURATOR_DATA, ownerKey); } @@ -348,6 +397,7 @@ export class DissmissConflictDialoge extends StateUtils.EntitySuccessAction { export class CheckConflictDialoge extends StateUtils.EntitySuccessAction { readonly type = CHECK_CONFLICT_DIALOG; + constructor(public ownerKey: string) { super(CONFIGURATOR_DATA, ownerKey); } diff --git a/feature-libs/product-configurator/rulebased/core/state/effects/configurator-basic.effect.ts b/feature-libs/product-configurator/rulebased/core/state/effects/configurator-basic.effect.ts index 6bc92f3a291..49d660d16a4 100644 --- a/feature-libs/product-configurator/rulebased/core/state/effects/configurator-basic.effect.ts +++ b/feature-libs/product-configurator/rulebased/core/state/effects/configurator-basic.effect.ts @@ -10,7 +10,7 @@ import { Store, select } from '@ngrx/store'; import { FeatureConfigService, LoggerService, - normalizeHttpError, + tryNormalizeHttpError, } from '@spartacus/core'; import { CommonConfigurator, @@ -86,7 +86,7 @@ export class ConfiguratorBasicEffects { catchError((error) => [ new ConfiguratorActions.CreateConfigurationFail({ ownerKey: action.payload.owner.key, - error: normalizeHttpError(error, this.logger), + error: tryNormalizeHttpError(error, this.logger), }), ]) ); @@ -115,7 +115,7 @@ export class ConfiguratorBasicEffects { catchError((error) => [ new ConfiguratorActions.ReadConfigurationFail({ ownerKey: action.payload.configuration.owner.key, - error: normalizeHttpError(error, this.logger), + error: tryNormalizeHttpError(error, this.logger), }), ]) ); @@ -147,7 +147,7 @@ export class ConfiguratorBasicEffects { }); }), catchError((error) => { - const errorPayload = normalizeHttpError(error, this.logger); + const errorPayload = tryNormalizeHttpError(error, this.logger); return [ new ConfiguratorActions.UpdateConfigurationFail({ configuration: payload, @@ -184,7 +184,7 @@ export class ConfiguratorBasicEffects { ); }), catchError((error) => { - const errorPayload = normalizeHttpError(error, this.logger); + const errorPayload = tryNormalizeHttpError(error, this.logger); return [ new ConfiguratorActions.UpdatePriceSummaryFail({ ownerKey: payload.owner.key, @@ -217,7 +217,7 @@ export class ConfiguratorBasicEffects { }); }), catchError((error) => { - const errorPayload = normalizeHttpError(error, this.logger); + const errorPayload = tryNormalizeHttpError(error, this.logger); return [ new ConfiguratorActions.GetConfigurationOverviewFail({ ownerKey: payload.owner.key, @@ -253,7 +253,7 @@ export class ConfiguratorBasicEffects { ); }), catchError((error) => { - const errorPayload = normalizeHttpError(error, this.logger); + const errorPayload = tryNormalizeHttpError(error, this.logger); return [ new ConfiguratorActions.UpdateConfigurationOverviewFail({ ownerKey: payload.owner.key, @@ -447,7 +447,7 @@ export class ConfiguratorBasicEffects { catchError((error) => [ new ConfiguratorActions.ReadConfigurationFail({ ownerKey: action.payload.configuration.owner.key, - error: normalizeHttpError(error, this.logger), + error: tryNormalizeHttpError(error, this.logger), }), ]) ); diff --git a/feature-libs/product-configurator/rulebased/core/state/effects/configurator-cart.effect.ts b/feature-libs/product-configurator/rulebased/core/state/effects/configurator-cart.effect.ts index 9e04eb09d5d..3993e512f10 100644 --- a/feature-libs/product-configurator/rulebased/core/state/effects/configurator-cart.effect.ts +++ b/feature-libs/product-configurator/rulebased/core/state/effects/configurator-cart.effect.ts @@ -4,13 +4,12 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { HttpErrorResponse } from '@angular/common/http'; -import { Injectable, inject } from '@angular/core'; +import { inject, Injectable } from '@angular/core'; import { Actions, createEffect, ofType } from '@ngrx/effects'; -import { Store, select } from '@ngrx/store'; +import { select, Store } from '@ngrx/store'; import { CartActions } from '@spartacus/cart/base/core'; import { CartModification } from '@spartacus/cart/base/root'; -import { LoggerService, normalizeHttpError } from '@spartacus/core'; +import { LoggerService, tryNormalizeHttpError } from '@spartacus/core'; import { CommonConfigurator, CommonConfiguratorUtilsService, @@ -34,6 +33,7 @@ type readConfigurationForCartEntryResultType = export const ERROR_MESSAGE_NO_ENTRY_NUMBER_FOUND = 'Entry number is required in addToCart response'; + @Injectable() /** * Common configurator effects related to cart handling @@ -84,10 +84,7 @@ export class ConfiguratorCartEffects { cartId: payload.cartId, productCode: payload.productCode, quantity: payload.quantity, - error: - error instanceof HttpErrorResponse - ? normalizeHttpError(error, this.logger) - : error, + error: tryNormalizeHttpError(error, this.logger), }) ) ) @@ -123,7 +120,7 @@ export class ConfiguratorCartEffects { userId: payload.userId, cartId: payload.cartId, entryNumber: payload.cartEntryNumber, - error: normalizeHttpError(error, this.logger), + error: tryNormalizeHttpError(error, this.logger), }) ) ) @@ -166,7 +163,7 @@ export class ConfiguratorCartEffects { catchError((error) => [ new ConfiguratorActions.ReadCartEntryConfigurationFail({ ownerKey: action.payload.owner.key, - error: normalizeHttpError(error, this.logger), + error: tryNormalizeHttpError(error, this.logger), }), ]) ); @@ -194,7 +191,7 @@ export class ConfiguratorCartEffects { catchError((error) => [ new ConfiguratorActions.ReadOrderEntryConfigurationFail({ ownerKey: action.payload.owner.key, - error: normalizeHttpError(error, this.logger), + error: tryNormalizeHttpError(error, this.logger), }), ]) ); diff --git a/feature-libs/product-configurator/rulebased/core/state/effects/configurator-variant.effect.ts b/feature-libs/product-configurator/rulebased/core/state/effects/configurator-variant.effect.ts index 224fea6a25a..a2e8f61aa7a 100644 --- a/feature-libs/product-configurator/rulebased/core/state/effects/configurator-variant.effect.ts +++ b/feature-libs/product-configurator/rulebased/core/state/effects/configurator-variant.effect.ts @@ -6,7 +6,7 @@ import { Injectable, inject } from '@angular/core'; import { Actions, createEffect, ofType } from '@ngrx/effects'; -import { LoggerService, normalizeHttpError } from '@spartacus/core'; +import { LoggerService, tryNormalizeHttpError } from '@spartacus/core'; import { ConfiguratorType } from '@spartacus/product-configurator/common'; import { Observable } from 'rxjs'; import { catchError, filter, map, switchMap } from 'rxjs/operators'; @@ -50,7 +50,7 @@ export class ConfiguratorVariantEffects { catchError((error) => [ new ConfiguratorActions.SearchVariantsFail({ ownerKey: action.payload.owner.key, - error: normalizeHttpError(error, this.logger), + error: tryNormalizeHttpError(error, this.logger), }), ]) ); diff --git a/feature-libs/product-configurator/textfield/core/state/actions/configurator-textfield.action.ts b/feature-libs/product-configurator/textfield/core/state/actions/configurator-textfield.action.ts index 9289a983dd0..aa24e9c4009 100644 --- a/feature-libs/product-configurator/textfield/core/state/actions/configurator-textfield.action.ts +++ b/feature-libs/product-configurator/textfield/core/state/actions/configurator-textfield.action.ts @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { StateUtils } from '@spartacus/core'; +import { ErrorAction, StateUtils } from '@spartacus/core'; import { CommonConfigurator } from '@spartacus/product-configurator/common'; import { ConfiguratorTextfield } from '../../model/configurator-textfield.model'; import { CONFIGURATION_TEXTFIELD_DATA } from '../configuration-textfield-state'; @@ -41,6 +41,7 @@ export const REMOVE_CONFIGURATION = export class CreateConfiguration extends StateUtils.LoaderLoadAction { readonly type = CREATE_CONFIGURATION; + constructor( public payload: { productCode: string; owner: CommonConfigurator.Owner } ) { @@ -48,8 +49,12 @@ export class CreateConfiguration extends StateUtils.LoaderLoadAction { } } -export class CreateConfigurationFail extends StateUtils.LoaderFailAction { +export class CreateConfigurationFail + extends StateUtils.LoaderFailAction + implements ErrorAction +{ readonly type = CREATE_CONFIGURATION_FAIL; + constructor(public payload: any) { super(CONFIGURATION_TEXTFIELD_DATA, payload); } @@ -57,6 +62,7 @@ export class CreateConfigurationFail extends StateUtils.LoaderFailAction { export class CreateConfigurationSuccess extends StateUtils.LoaderSuccessAction { readonly type = CREATE_CONFIGURATION_SUCCESS; + constructor(public payload: ConfiguratorTextfield.Configuration) { super(CONFIGURATION_TEXTFIELD_DATA); } @@ -64,6 +70,7 @@ export class CreateConfigurationSuccess extends StateUtils.LoaderSuccessAction { export class UpdateConfiguration extends StateUtils.LoaderLoadAction { readonly type = UPDATE_CONFIGURATION; + constructor(public payload: ConfiguratorTextfield.Configuration) { super(CONFIGURATION_TEXTFIELD_DATA); } @@ -71,13 +78,18 @@ export class UpdateConfiguration extends StateUtils.LoaderLoadAction { export class AddToCart extends StateUtils.LoaderLoadAction { readonly type = ADD_TO_CART; + constructor(public payload: ConfiguratorTextfield.AddToCartParameters) { super(CONFIGURATION_TEXTFIELD_DATA); } } -export class AddToCartFail extends StateUtils.LoaderFailAction { +export class AddToCartFail + extends StateUtils.LoaderFailAction + implements ErrorAction +{ readonly type = ADD_TO_CART_FAIL; + constructor(public payload: any) { super(CONFIGURATION_TEXTFIELD_DATA, payload); } @@ -85,13 +97,18 @@ export class AddToCartFail extends StateUtils.LoaderFailAction { export class UpdateCartEntryConfiguration extends StateUtils.LoaderLoadAction { readonly type = UPDATE_CART_ENTRY_CONFIGURATION; + constructor(public payload: ConfiguratorTextfield.UpdateCartEntryParameters) { super(CONFIGURATION_TEXTFIELD_DATA); } } -export class UpdateCartEntryConfigurationFail extends StateUtils.LoaderFailAction { +export class UpdateCartEntryConfigurationFail + extends StateUtils.LoaderFailAction + implements ErrorAction +{ readonly type = UPDATE_CART_ENTRY_CONFIGURATION_FAIL; + constructor(public payload: any) { super(CONFIGURATION_TEXTFIELD_DATA, payload); } @@ -99,6 +116,7 @@ export class UpdateCartEntryConfigurationFail extends StateUtils.LoaderFailActio export class ReadCartEntryConfiguration extends StateUtils.LoaderLoadAction { readonly type = READ_CART_ENTRY_CONFIGURATION; + constructor( public payload: CommonConfigurator.ReadConfigurationFromCartEntryParameters ) { @@ -108,13 +126,18 @@ export class ReadCartEntryConfiguration extends StateUtils.LoaderLoadAction { export class ReadCartEntryConfigurationSuccess extends StateUtils.LoaderSuccessAction { readonly type = READ_CART_ENTRY_CONFIGURATION_SUCCESS; + constructor(public payload: ConfiguratorTextfield.Configuration) { super(CONFIGURATION_TEXTFIELD_DATA); } } -export class ReadCartEntryConfigurationFail extends StateUtils.LoaderFailAction { +export class ReadCartEntryConfigurationFail + extends StateUtils.LoaderFailAction + implements ErrorAction +{ readonly type = READ_CART_ENTRY_CONFIGURATION_FAIL; + constructor(public payload: any) { super(CONFIGURATION_TEXTFIELD_DATA, payload); } @@ -122,6 +145,7 @@ export class ReadCartEntryConfigurationFail extends StateUtils.LoaderFailAction export class ReadOrderEntryConfiguration extends StateUtils.LoaderLoadAction { readonly type = READ_ORDER_ENTRY_CONFIGURATION; + constructor( public payload: CommonConfigurator.ReadConfigurationFromOrderEntryParameters ) { @@ -131,13 +155,18 @@ export class ReadOrderEntryConfiguration extends StateUtils.LoaderLoadAction { export class ReadOrderEntryConfigurationSuccess extends StateUtils.LoaderSuccessAction { readonly type = READ_ORDER_ENTRY_CONFIGURATION_SUCCESS; + constructor(public payload: ConfiguratorTextfield.Configuration) { super(CONFIGURATION_TEXTFIELD_DATA); } } -export class ReadOrderEntryConfigurationFail extends StateUtils.LoaderFailAction { +export class ReadOrderEntryConfigurationFail + extends StateUtils.LoaderFailAction + implements ErrorAction +{ readonly type = READ_ORDER_ENTRY_CONFIGURATION_FAIL; + constructor(public payload: any) { super(CONFIGURATION_TEXTFIELD_DATA, payload); } @@ -145,6 +174,7 @@ export class ReadOrderEntryConfigurationFail extends StateUtils.LoaderFailAction export class RemoveConfiguration extends StateUtils.LoaderResetAction { readonly type = REMOVE_CONFIGURATION; + constructor() { super(CONFIGURATION_TEXTFIELD_DATA); } diff --git a/feature-libs/product-configurator/textfield/core/state/effects/configurator-textfield.effect.ts b/feature-libs/product-configurator/textfield/core/state/effects/configurator-textfield.effect.ts index 69642535624..cdd9a19dfb8 100644 --- a/feature-libs/product-configurator/textfield/core/state/effects/configurator-textfield.effect.ts +++ b/feature-libs/product-configurator/textfield/core/state/effects/configurator-textfield.effect.ts @@ -7,7 +7,7 @@ import { Injectable, inject } from '@angular/core'; import { Actions, createEffect, ofType } from '@ngrx/effects'; import { CartActions } from '@spartacus/cart/base/core'; -import { LoggerService, normalizeHttpError } from '@spartacus/core'; +import { LoggerService, tryNormalizeHttpError } from '@spartacus/core'; import { CommonConfigurator } from '@spartacus/product-configurator/common'; import { Observable, of } from 'rxjs'; import { catchError, map, switchMap } from 'rxjs/operators'; @@ -43,7 +43,7 @@ export class ConfiguratorTextfieldEffects { catchError((error) => of( new ConfiguratorTextfieldActions.CreateConfigurationFail( - normalizeHttpError(error, this.logger) + tryNormalizeHttpError(error, this.logger) ) ) ) @@ -74,7 +74,7 @@ export class ConfiguratorTextfieldEffects { catchError((error) => of( new ConfiguratorTextfieldActions.AddToCartFail( - normalizeHttpError(error, this.logger) + tryNormalizeHttpError(error, this.logger) ) ) ) @@ -110,7 +110,7 @@ export class ConfiguratorTextfieldEffects { catchError((error) => of( new ConfiguratorTextfieldActions.UpdateCartEntryConfigurationFail( - normalizeHttpError(error, this.logger) + tryNormalizeHttpError(error, this.logger) ) ) ) @@ -140,7 +140,7 @@ export class ConfiguratorTextfieldEffects { ]), catchError((error) => [ new ConfiguratorTextfieldActions.ReadCartEntryConfigurationFail( - normalizeHttpError(error, this.logger) + tryNormalizeHttpError(error, this.logger) ), ]) ); @@ -169,7 +169,7 @@ export class ConfiguratorTextfieldEffects { ]), catchError((error) => [ new ConfiguratorTextfieldActions.ReadOrderEntryConfigurationFail( - normalizeHttpError(error, this.logger) + tryNormalizeHttpError(error, this.logger) ), ]) ); diff --git a/feature-libs/storefinder/core/store/actions/find-stores.action.spec.ts b/feature-libs/storefinder/core/store/actions/find-stores.action.spec.ts index 5344676a1ce..72b696059f1 100644 --- a/feature-libs/storefinder/core/store/actions/find-stores.action.spec.ts +++ b/feature-libs/storefinder/core/store/actions/find-stores.action.spec.ts @@ -1,6 +1,6 @@ +import { GeoPoint, SearchConfig, StateUtils } from '@spartacus/core'; import { STORE_FINDER_DATA } from '../store-finder-state'; import { StoreFinderActions } from './index'; -import { GeoPoint, SearchConfig, StateUtils } from '@spartacus/core'; describe('Find Stores Actions', () => { describe('OnHold', () => { @@ -50,13 +50,14 @@ describe('Find Stores Actions', () => { describe('FindStoresFail', () => { it('should create FindStoresFail action', () => { - const payload = { errorMessage: 'Error' }; - const action = new StoreFinderActions.FindStoresFail(payload); + const error = { message: 'Error' }; + const action = new StoreFinderActions.FindStoresFail(error); expect({ ...action }).toEqual({ type: StoreFinderActions.FIND_STORES_FAIL, - payload, - meta: StateUtils.failMeta(STORE_FINDER_DATA, payload), + payload: error, + error, + meta: StateUtils.failMeta(STORE_FINDER_DATA, error), }); }); }); @@ -92,15 +93,16 @@ describe('Find Stores Actions', () => { describe('FindStoreByIdFail', () => { it('should create FindStoreByIdFail action', () => { - const payload = { errorMessage: 'Error' }; - const action = new StoreFinderActions.FindStoreByIdFail(payload); + const error = { message: 'Error' }; + const action = new StoreFinderActions.FindStoreByIdFail(error); expect({ ...action, }).toEqual({ type: StoreFinderActions.FIND_STORE_BY_ID_FAIL, - payload, - meta: StateUtils.failMeta(STORE_FINDER_DATA, payload), + payload: error, + error, + meta: StateUtils.failMeta(STORE_FINDER_DATA, error), }); }); }); diff --git a/feature-libs/storefinder/core/store/actions/find-stores.action.ts b/feature-libs/storefinder/core/store/actions/find-stores.action.ts index 2b79993dd1d..e618ed5a632 100644 --- a/feature-libs/storefinder/core/store/actions/find-stores.action.ts +++ b/feature-libs/storefinder/core/store/actions/find-stores.action.ts @@ -4,8 +4,13 @@ * SPDX-License-Identifier: Apache-2.0 */ +import { + ErrorAction, + GeoPoint, + SearchConfig, + StateUtils, +} from '@spartacus/core'; import { STORE_FINDER_DATA } from '../store-finder-state'; -import { GeoPoint, SearchConfig, StateUtils } from '@spartacus/core'; export const FIND_STORES_ON_HOLD = '[StoreFinder] On Hold'; export const FIND_STORES = '[StoreFinder] Find Stores'; @@ -19,6 +24,7 @@ export const FIND_STORE_BY_ID_SUCCESS = export class FindStoresOnHold extends StateUtils.LoaderLoadAction { readonly type = FIND_STORES_ON_HOLD; + constructor() { super(STORE_FINDER_DATA); } @@ -26,6 +32,7 @@ export class FindStoresOnHold extends StateUtils.LoaderLoadAction { export class FindStores extends StateUtils.LoaderLoadAction { readonly type = FIND_STORES; + constructor( public payload: { queryText: string; @@ -40,8 +47,12 @@ export class FindStores extends StateUtils.LoaderLoadAction { } } -export class FindStoresFail extends StateUtils.LoaderFailAction { +export class FindStoresFail + extends StateUtils.LoaderFailAction + implements ErrorAction +{ readonly type = FIND_STORES_FAIL; + constructor(public payload: any) { super(STORE_FINDER_DATA, payload); } @@ -49,6 +60,7 @@ export class FindStoresFail extends StateUtils.LoaderFailAction { export class FindStoresSuccess extends StateUtils.LoaderSuccessAction { readonly type = FIND_STORES_SUCCESS; + constructor(public payload: any) { super(STORE_FINDER_DATA); } @@ -56,13 +68,18 @@ export class FindStoresSuccess extends StateUtils.LoaderSuccessAction { export class FindStoreById extends StateUtils.LoaderLoadAction { readonly type = FIND_STORE_BY_ID; + constructor(public payload: { storeId: string }) { super(STORE_FINDER_DATA); } } -export class FindStoreByIdFail extends StateUtils.LoaderFailAction { +export class FindStoreByIdFail + extends StateUtils.LoaderFailAction + implements ErrorAction +{ readonly type = FIND_STORE_BY_ID_FAIL; + constructor(public payload: any) { super(STORE_FINDER_DATA, payload); } @@ -70,6 +87,7 @@ export class FindStoreByIdFail extends StateUtils.LoaderFailAction { export class FindStoreByIdSuccess extends StateUtils.LoaderSuccessAction { readonly type = FIND_STORE_BY_ID_SUCCESS; + constructor(public payload: any) { super(STORE_FINDER_DATA); } diff --git a/feature-libs/storefinder/core/store/actions/view-all-stores.action.spec.ts b/feature-libs/storefinder/core/store/actions/view-all-stores.action.spec.ts index b5b01e749d5..04c641776c4 100644 --- a/feature-libs/storefinder/core/store/actions/view-all-stores.action.spec.ts +++ b/feature-libs/storefinder/core/store/actions/view-all-stores.action.spec.ts @@ -1,6 +1,6 @@ +import { StateUtils } from '@spartacus/core'; import { STORE_FINDER_DATA } from '../store-finder-state'; import { StoreFinderActions } from './index'; -import { StateUtils } from '@spartacus/core'; describe('View All Stores Actions', () => { describe('ViewAllStores', () => { @@ -16,13 +16,14 @@ describe('View All Stores Actions', () => { describe('ViewAllStoresFail', () => { it('should create ViewAllStoresFail action', () => { - const payload = { errorMessage: 'Error' }; - const action = new StoreFinderActions.ViewAllStoresFail(payload); + const error = { message: 'Error' }; + const action = new StoreFinderActions.ViewAllStoresFail(error); expect({ ...action }).toEqual({ type: StoreFinderActions.VIEW_ALL_STORES_FAIL, - payload, - meta: StateUtils.failMeta(STORE_FINDER_DATA, payload), + payload: error, + error, + meta: StateUtils.failMeta(STORE_FINDER_DATA, error), }); }); }); diff --git a/feature-libs/storefinder/core/store/actions/view-all-stores.action.ts b/feature-libs/storefinder/core/store/actions/view-all-stores.action.ts index 980bdcb421b..78dbf7a924e 100644 --- a/feature-libs/storefinder/core/store/actions/view-all-stores.action.ts +++ b/feature-libs/storefinder/core/store/actions/view-all-stores.action.ts @@ -4,9 +4,9 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { STORE_FINDER_DATA } from '../store-finder-state'; -import { StateUtils } from '@spartacus/core'; import { Action } from '@ngrx/store'; +import { ErrorAction, StateUtils } from '@spartacus/core'; +import { STORE_FINDER_DATA } from '../store-finder-state'; export const VIEW_ALL_STORES = '[StoreFinder] View All Stores'; export const VIEW_ALL_STORES_FAIL = '[StoreFinder] View All Stores Fail'; @@ -15,13 +15,18 @@ export const CLEAR_STORE_FINDER_DATA = '[StoreFinder] Clear Data'; export class ViewAllStores extends StateUtils.LoaderLoadAction { readonly type = VIEW_ALL_STORES; + constructor() { super(STORE_FINDER_DATA); } } -export class ViewAllStoresFail extends StateUtils.LoaderFailAction { +export class ViewAllStoresFail + extends StateUtils.LoaderFailAction + implements ErrorAction +{ readonly type = VIEW_ALL_STORES_FAIL; + constructor(public payload: any) { super(STORE_FINDER_DATA, payload); } @@ -29,6 +34,7 @@ export class ViewAllStoresFail extends StateUtils.LoaderFailAction { export class ViewAllStoresSuccess extends StateUtils.LoaderSuccessAction { readonly type = VIEW_ALL_STORES_SUCCESS; + constructor(public payload: any) { super(STORE_FINDER_DATA); } diff --git a/feature-libs/storefinder/core/store/effects/find-stores.effect.ts b/feature-libs/storefinder/core/store/effects/find-stores.effect.ts index 65311e915e1..495f378d2ae 100644 --- a/feature-libs/storefinder/core/store/effects/find-stores.effect.ts +++ b/feature-libs/storefinder/core/store/effects/find-stores.effect.ts @@ -6,7 +6,7 @@ import { Injectable, inject } from '@angular/core'; import { Actions, createEffect, ofType } from '@ngrx/effects'; -import { LoggerService, normalizeHttpError } from '@spartacus/core'; +import { LoggerService, tryNormalizeHttpError } from '@spartacus/core'; import { Observable, of } from 'rxjs'; import { catchError, map, mergeMap, switchMap } from 'rxjs/operators'; import { StoreFinderConnector } from '../../connectors/store-finder.connector'; @@ -52,7 +52,7 @@ export class FindStoresEffect { catchError((error) => of( new StoreFinderActions.FindStoresFail( - normalizeHttpError(error, this.logger) + tryNormalizeHttpError(error, this.logger) ) ) ) @@ -74,7 +74,7 @@ export class FindStoresEffect { catchError((error) => of( new StoreFinderActions.FindStoreByIdFail( - normalizeHttpError(error, this.logger) + tryNormalizeHttpError(error, this.logger) ) ) ) diff --git a/feature-libs/storefinder/core/store/effects/view-all-stores.effect.ts b/feature-libs/storefinder/core/store/effects/view-all-stores.effect.ts index 4cf5ef9afc1..6ce3faf31c6 100644 --- a/feature-libs/storefinder/core/store/effects/view-all-stores.effect.ts +++ b/feature-libs/storefinder/core/store/effects/view-all-stores.effect.ts @@ -9,7 +9,7 @@ import { Actions, createEffect, ofType } from '@ngrx/effects'; import { LoggerService, SiteContextActions, - normalizeHttpError, + tryNormalizeHttpError, } from '@spartacus/core'; import { Observable, of } from 'rxjs'; import { catchError, map, switchMap } from 'rxjs/operators'; @@ -45,7 +45,7 @@ export class ViewAllStoresEffect { catchError((error) => of( new StoreFinderActions.ViewAllStoresFail( - normalizeHttpError(error, this.logger) + tryNormalizeHttpError(error, this.logger) ) ) ) diff --git a/feature-libs/user/profile/assets/translations/en/my-account-v2-email.18n.ts b/feature-libs/user/profile/assets/translations/en/my-account-v2-email.18n.ts index 87734806f5d..48d765bbf85 100644 --- a/feature-libs/user/profile/assets/translations/en/my-account-v2-email.18n.ts +++ b/feature-libs/user/profile/assets/translations/en/my-account-v2-email.18n.ts @@ -1,5 +1,4 @@ /* - * SPDX-FileCopyrightText: 2023 SAP Spartacus team * SPDX-FileCopyrightText: 2024 SAP Spartacus team * * SPDX-License-Identifier: Apache-2.0 diff --git a/feature-libs/user/profile/assets/translations/en/my-account-v2-password.i18n.ts b/feature-libs/user/profile/assets/translations/en/my-account-v2-password.i18n.ts index 0e892780d0e..8926650cc7e 100644 --- a/feature-libs/user/profile/assets/translations/en/my-account-v2-password.i18n.ts +++ b/feature-libs/user/profile/assets/translations/en/my-account-v2-password.i18n.ts @@ -1,5 +1,4 @@ /* - * SPDX-FileCopyrightText: 2023 SAP Spartacus team * SPDX-FileCopyrightText: 2024 SAP Spartacus team * * SPDX-License-Identifier: Apache-2.0 diff --git a/feature-libs/user/profile/assets/translations/en/my-account-v2-user-profile.18n.ts b/feature-libs/user/profile/assets/translations/en/my-account-v2-user-profile.18n.ts index 253222cb43c..4a4878e5139 100644 --- a/feature-libs/user/profile/assets/translations/en/my-account-v2-user-profile.18n.ts +++ b/feature-libs/user/profile/assets/translations/en/my-account-v2-user-profile.18n.ts @@ -1,5 +1,4 @@ /* - * SPDX-FileCopyrightText: 2023 SAP Spartacus team * SPDX-FileCopyrightText: 2024 SAP Spartacus team * * SPDX-License-Identifier: Apache-2.0 diff --git a/feature-libs/user/profile/components/update-email/my-account-v2-email.component.ts b/feature-libs/user/profile/components/update-email/my-account-v2-email.component.ts index a25f9525980..ded1e6ac8a3 100644 --- a/feature-libs/user/profile/components/update-email/my-account-v2-email.component.ts +++ b/feature-libs/user/profile/components/update-email/my-account-v2-email.component.ts @@ -1,5 +1,4 @@ /* - * SPDX-FileCopyrightText: 2023 SAP Spartacus team * SPDX-FileCopyrightText: 2024 SAP Spartacus team * * SPDX-License-Identifier: Apache-2.0 @@ -12,8 +11,8 @@ import { inject, } from '@angular/core'; import { UntypedFormGroup } from '@angular/forms'; -import { Observable } from 'rxjs'; import { GlobalMessageType, User } from '@spartacus/core'; +import { Observable } from 'rxjs'; import { UserProfileFacade } from '@spartacus/user/profile/root'; import { filter } from 'rxjs/operators'; diff --git a/feature-libs/user/profile/components/update-email/update-email-component.service.ts b/feature-libs/user/profile/components/update-email/update-email-component.service.ts index f737ecaa0f3..b1bcc78f139 100644 --- a/feature-libs/user/profile/components/update-email/update-email-component.service.ts +++ b/feature-libs/user/profile/components/update-email/update-email-component.service.ts @@ -1,5 +1,4 @@ /* - * SPDX-FileCopyrightText: 2023 SAP Spartacus team * SPDX-FileCopyrightText: 2024 SAP Spartacus team * * SPDX-License-Identifier: Apache-2.0 diff --git a/feature-libs/user/profile/components/update-email/use-my-account-v2-email.ts.ts b/feature-libs/user/profile/components/update-email/use-my-account-v2-email.ts.ts index 0750ed5998e..501506ffedd 100644 --- a/feature-libs/user/profile/components/update-email/use-my-account-v2-email.ts.ts +++ b/feature-libs/user/profile/components/update-email/use-my-account-v2-email.ts.ts @@ -1,5 +1,4 @@ /* - * SPDX-FileCopyrightText: 2023 SAP Spartacus team * SPDX-FileCopyrightText: 2024 SAP Spartacus team * * SPDX-License-Identifier: Apache-2.0 diff --git a/feature-libs/user/profile/components/update-password/my-account-v2-password.component.ts b/feature-libs/user/profile/components/update-password/my-account-v2-password.component.ts index 60e5acbbbf2..c63b91a0d53 100644 --- a/feature-libs/user/profile/components/update-password/my-account-v2-password.component.ts +++ b/feature-libs/user/profile/components/update-password/my-account-v2-password.component.ts @@ -1,5 +1,4 @@ /* - * SPDX-FileCopyrightText: 2023 SAP Spartacus team * SPDX-FileCopyrightText: 2024 SAP Spartacus team * * SPDX-License-Identifier: Apache-2.0 diff --git a/feature-libs/user/profile/components/update-password/use-my-account-v2-password.ts b/feature-libs/user/profile/components/update-password/use-my-account-v2-password.ts index cbf0ed0c84b..f091ec91291 100644 --- a/feature-libs/user/profile/components/update-password/use-my-account-v2-password.ts +++ b/feature-libs/user/profile/components/update-password/use-my-account-v2-password.ts @@ -1,5 +1,4 @@ /* - * SPDX-FileCopyrightText: 2023 SAP Spartacus team * SPDX-FileCopyrightText: 2024 SAP Spartacus team * * SPDX-License-Identifier: Apache-2.0 diff --git a/feature-libs/user/profile/components/update-profile/my-account-v2-profile.component.ts b/feature-libs/user/profile/components/update-profile/my-account-v2-profile.component.ts index a2e3005e794..f41855b2679 100644 --- a/feature-libs/user/profile/components/update-profile/my-account-v2-profile.component.ts +++ b/feature-libs/user/profile/components/update-profile/my-account-v2-profile.component.ts @@ -1,5 +1,4 @@ /* - * SPDX-FileCopyrightText: 2023 SAP Spartacus team * SPDX-FileCopyrightText: 2024 SAP Spartacus team * * SPDX-License-Identifier: Apache-2.0 @@ -12,8 +11,8 @@ import { inject, } from '@angular/core'; import { UntypedFormGroup } from '@angular/forms'; -import { Title } from '@spartacus/user/profile/root'; import { User } from '@spartacus/user/account/root'; +import { Title } from '@spartacus/user/profile/root'; import { Observable } from 'rxjs'; import { UpdateProfileComponentService } from './update-profile-component.service'; diff --git a/feature-libs/user/profile/components/update-profile/update-profile-component.service.ts b/feature-libs/user/profile/components/update-profile/update-profile-component.service.ts index 39cea0418df..0aed02495cf 100644 --- a/feature-libs/user/profile/components/update-profile/update-profile-component.service.ts +++ b/feature-libs/user/profile/components/update-profile/update-profile-component.service.ts @@ -1,5 +1,4 @@ /* - * SPDX-FileCopyrightText: 2023 SAP Spartacus team * SPDX-FileCopyrightText: 2024 SAP Spartacus team * * SPDX-License-Identifier: Apache-2.0 diff --git a/feature-libs/user/profile/components/update-profile/use-my-account-v2-profile.ts b/feature-libs/user/profile/components/update-profile/use-my-account-v2-profile.ts index 69a7a9c6c72..12f93241a23 100644 --- a/feature-libs/user/profile/components/update-profile/use-my-account-v2-profile.ts +++ b/feature-libs/user/profile/components/update-profile/use-my-account-v2-profile.ts @@ -1,5 +1,4 @@ /* - * SPDX-FileCopyrightText: 2023 SAP Spartacus team * SPDX-FileCopyrightText: 2024 SAP Spartacus team * * SPDX-License-Identifier: Apache-2.0 diff --git a/integration-libs/cdc/core/store/actions/cdc-user-token.action.spec.ts b/integration-libs/cdc/core/store/actions/cdc-user-token.action.spec.ts index 63b5cce0ac2..cb092138139 100644 --- a/integration-libs/cdc/core/store/actions/cdc-user-token.action.spec.ts +++ b/integration-libs/cdc/core/store/actions/cdc-user-token.action.spec.ts @@ -22,13 +22,14 @@ describe('CDC User Token Actions', () => { describe('LoadUserTokenFail Action', () => { it('should create the action', () => { const data = { - error: 'anError', + error: new Error('anError'), initActionPayload: 'payload', } as any; const action = new CdcAuthActions.LoadCdcUserTokenFail(data); expect({ ...action }).toEqual({ + error: data.error, type: CdcAuthActions.LOAD_CDC_USER_TOKEN_FAIL, payload: data, }); diff --git a/integration-libs/cdc/core/store/actions/cdc-user-token.action.ts b/integration-libs/cdc/core/store/actions/cdc-user-token.action.ts index 1f700344528..873741e65e4 100644 --- a/integration-libs/cdc/core/store/actions/cdc-user-token.action.ts +++ b/integration-libs/cdc/core/store/actions/cdc-user-token.action.ts @@ -5,7 +5,7 @@ */ import { Action } from '@ngrx/store'; -import { ErrorModel, HttpErrorModel } from '@spartacus/core'; +import { ErrorAction } from '@spartacus/core'; export const LOAD_CDC_USER_TOKEN = '[Auth] Load CDC User Token'; export const LOAD_CDC_USER_TOKEN_FAIL = '[Auth] Load CDC User Token Fail'; @@ -19,17 +19,22 @@ interface LoadUserTokenPayload { } interface LoadUserTokenFailurePayload { - error: ErrorModel | HttpErrorModel | any; + error: any; initialActionPayload: LoadUserTokenPayload; } -export class LoadCdcUserTokenFail implements Action { +export class LoadCdcUserTokenFail implements ErrorAction { + public error: any; readonly type = LOAD_CDC_USER_TOKEN_FAIL; - constructor(public payload: LoadUserTokenFailurePayload) {} + + constructor(public payload: LoadUserTokenFailurePayload) { + this.error = payload.error; + } } export class LoadCdcUserToken implements Action { readonly type = LOAD_CDC_USER_TOKEN; + constructor(public payload: LoadUserTokenPayload) {} } diff --git a/integration-libs/cdc/core/store/effects/cdc-user-token.effect.ts b/integration-libs/cdc/core/store/effects/cdc-user-token.effect.ts index cae59a7b807..8441e21ee4d 100644 --- a/integration-libs/cdc/core/store/effects/cdc-user-token.effect.ts +++ b/integration-libs/cdc/core/store/effects/cdc-user-token.effect.ts @@ -10,7 +10,7 @@ import { GlobalMessageService, GlobalMessageType, LoggerService, - normalizeHttpError, + tryNormalizeHttpError, } from '@spartacus/core'; import { EMPTY, Observable, of } from 'rxjs'; import { catchError, map, mergeMap, switchMap } from 'rxjs/operators'; @@ -48,7 +48,7 @@ export class CdcUserTokenEffects { ); return of( new CdcAuthActions.LoadCdcUserTokenFail({ - error: normalizeHttpError(error, this.logger), + error: tryNormalizeHttpError(error, this.logger), initialActionPayload: payload, }) ); diff --git a/package-lock.json b/package-lock.json index f07112b6cb1..efcf008fea3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -68,6 +68,7 @@ "@swc/core": "^1.3.85", "@types/express": "^4.17.17", "@types/fs-extra": "^11.0.1", + "@types/http-proxy": "^1.17.15", "@types/i18next": "^13.0.0", "@types/jasmine": "~5.1.0", "@types/jest": "^29.4.0", @@ -8377,9 +8378,9 @@ "dev": true }, "node_modules/@types/http-proxy": { - "version": "1.17.14", - "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.14.tgz", - "integrity": "sha512-SSrD0c1OQzlFX7pGu1eXxSEjemej64aaNPRhhVYUGqXh0BtldAAx37MG8btcumvpgKyZp1F5Gn3JkktdxiFv6w==", + "version": "1.17.15", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.15.tgz", + "integrity": "sha512-25g5atgiVNTIv0LBDTg1H74Hvayx0ajtJPLLcYE3whFv75J0pWNtOBzaXJQgDTmrX1bx5U9YC2w/n65BN1HwRQ==", "dev": true, "dependencies": { "@types/node": "*" @@ -11436,6 +11437,25 @@ "node-fetch": "^2.6.12" } }, + "node_modules/cross-fetch/node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -17986,44 +18006,6 @@ "dev": true, "optional": true }, - "node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/node-fetch/node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" - }, - "node_modules/node-fetch/node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" - }, - "node_modules/node-fetch/node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, "node_modules/node-forge": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", @@ -22645,6 +22627,11 @@ "node": ">= 4.0.0" } }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, "node_modules/tree-kill": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", @@ -23853,6 +23840,20 @@ "node": ">=12" } }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/whatwg-url/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -29581,9 +29582,9 @@ "dev": true }, "@types/http-proxy": { - "version": "1.17.14", - "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.14.tgz", - "integrity": "sha512-SSrD0c1OQzlFX7pGu1eXxSEjemej64aaNPRhhVYUGqXh0BtldAAx37MG8btcumvpgKyZp1F5Gn3JkktdxiFv6w==", + "version": "1.17.15", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.15.tgz", + "integrity": "sha512-25g5atgiVNTIv0LBDTg1H74Hvayx0ajtJPLLcYE3whFv75J0pWNtOBzaXJQgDTmrX1bx5U9YC2w/n65BN1HwRQ==", "dev": true, "requires": { "@types/node": "*" @@ -31861,6 +31862,16 @@ "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", "requires": { "node-fetch": "^2.6.12" + }, + "dependencies": { + "node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "requires": { + "whatwg-url": "^5.0.0" + } + } } }, "cross-spawn": { @@ -36738,35 +36749,6 @@ "dev": true, "optional": true }, - "node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "requires": { - "whatwg-url": "^5.0.0" - }, - "dependencies": { - "tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" - }, - "webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" - }, - "whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "requires": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - } - } - }, "node-forge": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", @@ -40074,6 +40056,11 @@ } } }, + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, "tree-kill": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", @@ -40870,6 +40857,22 @@ "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", "dev": true }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + }, + "dependencies": { + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + } + } + }, "which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index 570b04fd573..6983bb145ab 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,7 @@ "build:setup": "nx build setup --configuration production", "build:ssr": "env-cmd --no-override -e dev,b2c,$SPA_ENV nx run storefrontapp:server:production", "build:ssr:ci": "env-cmd -e ci,b2c,$SPA_ENV nx run storefrontapp:server:production", - "build:ssr:local-http": "env-cmd -e local-http,b2c,$SPA_ENV nx run storefrontapp:server:production", + "build:ssr:local-http-backend": "env-cmd -e local-http,b2c,$SPA_ENV nx run storefrontapp:server:production", "build:storefinder": "npm --prefix feature-libs/storefinder run build:schematics && nx build storefinder --configuration production", "build:smartedit": "npm --prefix feature-libs/smartedit run build:schematics && nx build smartedit --configuration production", "build:tracking": "npm --prefix feature-libs/tracking run build:schematics && nx build tracking --configuration production", @@ -179,6 +179,7 @@ "@swc/core": "^1.3.85", "@types/express": "^4.17.17", "@types/fs-extra": "^11.0.1", + "@types/http-proxy": "^1.17.15", "@types/i18next": "^13.0.0", "@types/jasmine": "~5.1.0", "@types/jest": "^29.4.0", diff --git a/projects/core/src/anonymous-consents/facade/anonymous-consents.service.spec.ts b/projects/core/src/anonymous-consents/facade/anonymous-consents.service.spec.ts index 16b8f740676..1a2a02a54f6 100644 --- a/projects/core/src/anonymous-consents/facade/anonymous-consents.service.spec.ts +++ b/projects/core/src/anonymous-consents/facade/anonymous-consents.service.spec.ts @@ -176,7 +176,9 @@ describe('AnonymousConsentsService', () => { it('getLoadTemplatesError should call getAnonymousConsentTemplatesError selector', () => { store.dispatch( - new AnonymousConsentsActions.LoadAnonymousConsentTemplatesFail('an error') + new AnonymousConsentsActions.LoadAnonymousConsentTemplatesFail( + new Error('an error') + ) ); let result = false; diff --git a/projects/core/src/anonymous-consents/store/actions/anonymous-consents.action.spec.ts b/projects/core/src/anonymous-consents/store/actions/anonymous-consents.action.spec.ts index 4281bd1b784..eeb2969b4b6 100644 --- a/projects/core/src/anonymous-consents/store/actions/anonymous-consents.action.spec.ts +++ b/projects/core/src/anonymous-consents/store/actions/anonymous-consents.action.spec.ts @@ -45,7 +45,7 @@ describe('anonymous consent actions', () => { }); describe('LoadAnonymousConsentTemplatesFail', () => { it('should create the action', () => { - const mockError = 'anError'; + const mockError = new Error('anError'); const action = new AnonymousConsentsActions.LoadAnonymousConsentTemplatesFail( mockError @@ -53,6 +53,7 @@ describe('anonymous consent actions', () => { expect({ ...action }).toEqual({ type: AnonymousConsentsActions.LOAD_ANONYMOUS_CONSENT_TEMPLATES_FAIL, meta: StateUtils.failMeta(ANONYMOUS_CONSENTS, mockError), + error: mockError, }); }); }); diff --git a/projects/core/src/anonymous-consents/store/actions/anonymous-consents.action.ts b/projects/core/src/anonymous-consents/store/actions/anonymous-consents.action.ts index ba4c8011cb1..5d264c95ad6 100644 --- a/projects/core/src/anonymous-consents/store/actions/anonymous-consents.action.ts +++ b/projects/core/src/anonymous-consents/store/actions/anonymous-consents.action.ts @@ -4,6 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ +import { ErrorAction } from '../../../error-handling'; import { AnonymousConsent, ConsentTemplate, @@ -40,6 +41,7 @@ export const ANONYMOUS_CONSENT_CHECK_UPDATED_VERSIONS = export class LoadAnonymousConsentTemplates extends StateUtils.LoaderLoadAction { readonly type = LOAD_ANONYMOUS_CONSENT_TEMPLATES; + constructor() { super(ANONYMOUS_CONSENTS); } @@ -47,12 +49,18 @@ export class LoadAnonymousConsentTemplates extends StateUtils.LoaderLoadAction { export class LoadAnonymousConsentTemplatesSuccess extends StateUtils.LoaderSuccessAction { readonly type = LOAD_ANONYMOUS_CONSENT_TEMPLATES_SUCCESS; + constructor(public payload: ConsentTemplate[]) { super(ANONYMOUS_CONSENTS); } } -export class LoadAnonymousConsentTemplatesFail extends StateUtils.LoaderFailAction { + +export class LoadAnonymousConsentTemplatesFail + extends StateUtils.LoaderFailAction + implements ErrorAction +{ readonly type = LOAD_ANONYMOUS_CONSENT_TEMPLATES_FAIL; + constructor(payload: any) { super(ANONYMOUS_CONSENTS, payload); } @@ -60,6 +68,7 @@ export class LoadAnonymousConsentTemplatesFail extends StateUtils.LoaderFailActi export class ResetLoadAnonymousConsentTemplates extends StateUtils.LoaderResetAction { readonly type = RESET_LOAD_ANONYMOUS_CONSENT_TEMPLATES; + constructor() { super(ANONYMOUS_CONSENTS); } @@ -67,6 +76,7 @@ export class ResetLoadAnonymousConsentTemplates extends StateUtils.LoaderResetAc export class GetAllAnonymousConsents { readonly type = GET_ALL_ANONYMOUS_CONSENTS; + constructor() { // Intentional empty constructor } @@ -74,36 +84,43 @@ export class GetAllAnonymousConsents { export class GetAnonymousConsent { readonly type = GET_ANONYMOUS_CONSENT; + constructor(public templateCode: string) {} } export class SetAnonymousConsents { readonly type = SET_ANONYMOUS_CONSENTS; + constructor(public payload: AnonymousConsent[]) {} } export class GiveAnonymousConsent { readonly type = GIVE_ANONYMOUS_CONSENT; + constructor(public templateCode: string) {} } export class WithdrawAnonymousConsent { readonly type = WITHDRAW_ANONYMOUS_CONSENT; + constructor(public templateCode: string) {} } export class ToggleAnonymousConsentsBannerDissmissed { readonly type = TOGGLE_ANONYMOUS_CONSENTS_BANNER_DISMISSED; + constructor(public dismissed: boolean) {} } export class ToggleAnonymousConsentTemplatesUpdated { readonly type = TOGGLE_ANONYMOUS_CONSENT_TEMPLATES_UPDATED; + constructor(public updated: boolean) {} } export class AnonymousConsentCheckUpdatedVersions { readonly type = ANONYMOUS_CONSENT_CHECK_UPDATED_VERSIONS; + constructor() { // Intentional empty constructor } diff --git a/projects/core/src/anonymous-consents/store/effects/anonymous-consents.effect.ts b/projects/core/src/anonymous-consents/store/effects/anonymous-consents.effect.ts index 59df1c71f78..465994ee766 100644 --- a/projects/core/src/anonymous-consents/store/effects/anonymous-consents.effect.ts +++ b/projects/core/src/anonymous-consents/store/effects/anonymous-consents.effect.ts @@ -23,7 +23,7 @@ import { AuthActions, AuthService, UserIdService } from '../../../auth/index'; import { LoggerService } from '../../../logger'; import { UserConsentService } from '../../../user/facade/user-consent.service'; import { UserActions } from '../../../user/store/actions/index'; -import { normalizeHttpError } from '../../../util/normalize-http-error'; +import { tryNormalizeHttpError } from '../../../util/try-normalize-http-error'; import { AnonymousConsentsConfig } from '../../config/anonymous-consents-config'; import { AnonymousConsentTemplatesConnector } from '../../connectors/anonymous-consent-templates.connector'; import { AnonymousConsentsService } from '../../facade/index'; @@ -77,7 +77,7 @@ export class AnonymousConsentsEffects { catchError((error) => of( new AnonymousConsentsActions.LoadAnonymousConsentTemplatesFail( - normalizeHttpError(error, this.logger) + tryNormalizeHttpError(error, this.logger) ) ) ) @@ -119,7 +119,7 @@ export class AnonymousConsentsEffects { catchError((error) => of( new AnonymousConsentsActions.LoadAnonymousConsentTemplatesFail( - normalizeHttpError(error, this.logger) + tryNormalizeHttpError(error, this.logger) ) ) ) diff --git a/projects/core/src/anonymous-consents/store/selectors/anonymous-consent-templates.selectors.spec.ts b/projects/core/src/anonymous-consents/store/selectors/anonymous-consent-templates.selectors.spec.ts index 0c21c2ab185..0891511d621 100644 --- a/projects/core/src/anonymous-consents/store/selectors/anonymous-consent-templates.selectors.spec.ts +++ b/projects/core/src/anonymous-consents/store/selectors/anonymous-consent-templates.selectors.spec.ts @@ -120,7 +120,7 @@ describe('anonymous consent templates selectors', () => { it('should return the error flag', () => { store.dispatch( new AnonymousConsentsActions.LoadAnonymousConsentTemplatesFail( - 'anError' + new Error('anError') ) ); diff --git a/projects/core/src/auth/client-auth/store/actions/client-token.action.spec.ts b/projects/core/src/auth/client-auth/store/actions/client-token.action.spec.ts index 79956451eea..fc08d38eae5 100644 --- a/projects/core/src/auth/client-auth/store/actions/client-token.action.spec.ts +++ b/projects/core/src/auth/client-auth/store/actions/client-token.action.spec.ts @@ -23,11 +23,12 @@ describe('Client Token Actions', () => { describe('LoadClientTokenFail', () => { it('should create the action', () => { - const error = 'anError'; + const error = new Error('anError'); const action = new ClientAuthActions.LoadClientTokenFail(error); expect({ ...action }).toEqual({ type: ClientAuthActions.LOAD_CLIENT_TOKEN_FAIL, payload: error, + error, meta: StateUtils.failMeta(CLIENT_TOKEN_DATA, error), }); }); diff --git a/projects/core/src/auth/client-auth/store/actions/client-token.action.ts b/projects/core/src/auth/client-auth/store/actions/client-token.action.ts index 531462ece1c..15030b8575f 100644 --- a/projects/core/src/auth/client-auth/store/actions/client-token.action.ts +++ b/projects/core/src/auth/client-auth/store/actions/client-token.action.ts @@ -4,6 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ +import { ErrorAction } from '../../../../error-handling'; import { StateUtils } from '../../../../state/utils/index'; import { ClientToken } from '../../models/client-token.model'; import { CLIENT_TOKEN_DATA } from '../client-auth-state'; @@ -14,13 +15,18 @@ export const LOAD_CLIENT_TOKEN_SUCCESS = '[Token] Load Client Token Success'; export class LoadClientToken extends StateUtils.LoaderLoadAction { readonly type = LOAD_CLIENT_TOKEN; + constructor() { super(CLIENT_TOKEN_DATA); } } -export class LoadClientTokenFail extends StateUtils.LoaderFailAction { +export class LoadClientTokenFail + extends StateUtils.LoaderFailAction + implements ErrorAction +{ readonly type = LOAD_CLIENT_TOKEN_FAIL; + constructor(public payload: any) { super(CLIENT_TOKEN_DATA, payload); } @@ -28,6 +34,7 @@ export class LoadClientTokenFail extends StateUtils.LoaderFailAction { export class LoadClientTokenSuccess extends StateUtils.LoaderSuccessAction { readonly type = LOAD_CLIENT_TOKEN_SUCCESS; + constructor(public payload: ClientToken) { super(CLIENT_TOKEN_DATA); } diff --git a/projects/core/src/auth/client-auth/store/effects/client-token.effect.ts b/projects/core/src/auth/client-auth/store/effects/client-token.effect.ts index 37b7e58c446..b2b8098f6bd 100644 --- a/projects/core/src/auth/client-auth/store/effects/client-token.effect.ts +++ b/projects/core/src/auth/client-auth/store/effects/client-token.effect.ts @@ -9,7 +9,7 @@ import { Actions, createEffect, ofType } from '@ngrx/effects'; import { Observable, of } from 'rxjs'; import { catchError, exhaustMap, map } from 'rxjs/operators'; import { LoggerService } from '../../../../logger'; -import { normalizeHttpError } from '../../../../util/normalize-http-error'; +import { tryNormalizeHttpError } from '../../../../util/try-normalize-http-error'; import { ClientToken } from '../../../client-auth/models/client-token.model'; import { ClientAuthenticationTokenService } from '../../services/client-authentication-token.service'; import { ClientAuthActions } from '../actions/index'; @@ -32,7 +32,7 @@ export class ClientTokenEffect { catchError((error) => of( new ClientAuthActions.LoadClientTokenFail( - normalizeHttpError(error, this.logger) + tryNormalizeHttpError(error, this.logger) ) ) ) diff --git a/projects/core/src/base-core.module.ts b/projects/core/src/base-core.module.ts index e8ac9825661..714d93f4f76 100644 --- a/projects/core/src/base-core.module.ts +++ b/projects/core/src/base-core.module.ts @@ -23,6 +23,7 @@ import { StateModule } from './state/state.module'; @NgModule({ imports: [ + ErrorHandlingModule.forRoot(), // Import this module before any other interceptor to handle HTTP errors efficiently StateModule.forRoot(), ConfigModule.forRoot(), ConfigInitializerModule.forRoot(), @@ -37,7 +38,6 @@ import { StateModule } from './state/state.module'; BaseOccModule.forRoot(), LazyLoadingModule.forRoot(), HttpModule.forRoot(), - ErrorHandlingModule.forRoot(), ], }) export class BaseCoreModule { diff --git a/projects/core/src/cms/store/actions/components.action.spec.ts b/projects/core/src/cms/store/actions/components.action.spec.ts index a314dc02275..1d94c9cd6fb 100755 --- a/projects/core/src/cms/store/actions/components.action.spec.ts +++ b/projects/core/src/cms/store/actions/components.action.spec.ts @@ -34,6 +34,7 @@ describe('Cms Component Actions', () => { }); expect({ ...action }).toEqual({ + error, payload: { uid: test_uid, error, diff --git a/projects/core/src/cms/store/actions/components.action.ts b/projects/core/src/cms/store/actions/components.action.ts index 1cb4dde2256..9ea1f8b6bd0 100755 --- a/projects/core/src/cms/store/actions/components.action.ts +++ b/projects/core/src/cms/store/actions/components.action.ts @@ -4,6 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ +import { ErrorAction } from '../../../error-handling'; import { CmsComponent } from '../../../model/cms.model'; import { PageContext } from '../../../routing/index'; import { StateUtils } from '../../../state/utils/index'; @@ -16,6 +17,7 @@ export const CMS_GET_COMPONENT_FROM_PAGE = '[Cms] Get Component from Page'; export class LoadCmsComponent extends StateUtils.EntityLoadAction { readonly type = LOAD_CMS_COMPONENT; + constructor( public payload: { uid: string; @@ -26,10 +28,31 @@ export class LoadCmsComponent extends StateUtils.EntityLoadAction { } } -export class LoadCmsComponentFail extends StateUtils.EntityFailAction { +export class LoadCmsComponentFail + extends StateUtils.EntityFailAction + implements ErrorAction +{ readonly type = LOAD_CMS_COMPONENT_FAIL; + + constructor(payload: { uid: string; error: any; pageContext: PageContext }); + /** + * @deprecated Please pass the argument `error`. + * It will become mandatory along with removing + * the feature toggle `ssrStrictErrorHandlingForHttpAndNgrx`. + */ + constructor( + // eslint-disable-next-line @typescript-eslint/unified-signatures + payload: { + uid: string; + pageContext: PageContext; + } + ); constructor( - public payload: { uid: string; error?: any; pageContext: PageContext } + public payload: { + uid: string; + error?: any; + pageContext: PageContext; + } ) { super(COMPONENT_ENTITY, payload.uid, payload.error); } @@ -39,6 +62,7 @@ export class LoadCmsComponentSuccess< T extends CmsComponent, > extends StateUtils.EntitySuccessAction { readonly type = LOAD_CMS_COMPONENT_SUCCESS; + constructor( public payload: { component: T; @@ -54,6 +78,7 @@ export class CmsGetComponentFromPage< T extends CmsComponent, > extends StateUtils.EntitySuccessAction { readonly type = CMS_GET_COMPONENT_FROM_PAGE; + constructor( public payload: | { component: T; pageContext: PageContext } diff --git a/projects/core/src/cms/store/actions/navigation-entry-item.action.spec.ts b/projects/core/src/cms/store/actions/navigation-entry-item.action.spec.ts index a1683014728..64aa8bfbf82 100755 --- a/projects/core/src/cms/store/actions/navigation-entry-item.action.spec.ts +++ b/projects/core/src/cms/store/actions/navigation-entry-item.action.spec.ts @@ -26,20 +26,18 @@ describe('Navigation Entry Item Actions', () => { describe('LoadCmsNavigationItemsFail', () => { it('should create an action', () => { - const payload = { message: 'Load Error' }; + const error = { message: 'Load Error' }; const nodeId = 'test_uid'; - const action = new CmsActions.LoadCmsNavigationItemsFail( - nodeId, - payload - ); + const action = new CmsActions.LoadCmsNavigationItemsFail(nodeId, error); expect({ ...action }).toEqual({ type: CmsActions.LOAD_CMS_NAVIGATION_ITEMS_FAIL, - payload, + payload: error, + error, meta: StateUtils.entityFailMeta( NAVIGATION_DETAIL_ENTITY, nodeId, - payload + error ), }); }); diff --git a/projects/core/src/cms/store/actions/navigation-entry-item.action.ts b/projects/core/src/cms/store/actions/navigation-entry-item.action.ts index 83d21653fd7..6490359502c 100755 --- a/projects/core/src/cms/store/actions/navigation-entry-item.action.ts +++ b/projects/core/src/cms/store/actions/navigation-entry-item.action.ts @@ -4,6 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ +import { ErrorAction } from '../../../error-handling'; import { StateUtils } from '../../../state/utils/index'; import { NAVIGATION_DETAIL_ENTITY } from '../cms-state'; @@ -15,13 +16,18 @@ export const LOAD_CMS_NAVIGATION_ITEMS_SUCCESS = export class LoadCmsNavigationItems extends StateUtils.EntityLoadAction { readonly type = LOAD_CMS_NAVIGATION_ITEMS; + constructor(public payload: { nodeId: string; items: any[] }) { super(NAVIGATION_DETAIL_ENTITY, payload.nodeId); } } -export class LoadCmsNavigationItemsFail extends StateUtils.EntityFailAction { +export class LoadCmsNavigationItemsFail + extends StateUtils.EntityFailAction + implements ErrorAction +{ readonly type = LOAD_CMS_NAVIGATION_ITEMS_FAIL; + constructor( nodeId: string, public payload: any @@ -32,6 +38,7 @@ export class LoadCmsNavigationItemsFail extends StateUtils.EntityFailAction { export class LoadCmsNavigationItemsSuccess extends StateUtils.EntitySuccessAction { readonly type = LOAD_CMS_NAVIGATION_ITEMS_SUCCESS; + constructor(public payload: { nodeId: string; components: any[] }) { super(NAVIGATION_DETAIL_ENTITY, payload.nodeId); } diff --git a/projects/core/src/cms/store/actions/page.action.spec.ts b/projects/core/src/cms/store/actions/page.action.spec.ts index a7a0478dfb4..7b3043539ec 100755 --- a/projects/core/src/cms/store/actions/page.action.spec.ts +++ b/projects/core/src/cms/store/actions/page.action.spec.ts @@ -25,15 +25,16 @@ describe('Cms Page Actions', () => { describe('LoadCmsPageDataFail', () => { it('should create the action', () => { - const payload = 'error'; - const action = new CmsActions.LoadCmsPageDataFail(pageContext, payload); + const error = new Error('error'); + const action = new CmsActions.LoadCmsPageDataFail(pageContext, error); expect({ ...action }).toEqual({ + error, type: CmsActions.LOAD_CMS_PAGE_DATA_FAIL, meta: StateUtils.entityFailMeta( pageContext.type, pageContext.id, - payload + error ), }); }); @@ -41,6 +42,7 @@ describe('Cms Page Actions', () => { describe('CmsSetPageFailIndex', () => { it('should create the action', () => { + const error = new Error('Failed to set cms page index'); const newIndex = 'index'; const action = new CmsActions.CmsSetPageFailIndex( pageContext, @@ -50,7 +52,12 @@ describe('Cms Page Actions', () => { expect({ ...action }).toEqual({ payload: newIndex, type: CmsActions.CMS_SET_PAGE_FAIL_INDEX, - meta: StateUtils.entityFailMeta(pageContext.type, pageContext.id), + meta: StateUtils.entityFailMeta( + pageContext.type, + pageContext.id, + error + ), + error, }); }); }); diff --git a/projects/core/src/cms/store/actions/page.action.ts b/projects/core/src/cms/store/actions/page.action.ts index 63be897248f..1eea647ba07 100755 --- a/projects/core/src/cms/store/actions/page.action.ts +++ b/projects/core/src/cms/store/actions/page.action.ts @@ -4,6 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ +import { ErrorAction } from '../../../error-handling'; import { PageContext } from '../../../routing/index'; import { StateUtils } from '../../../state/utils/index'; import { Page } from '../../model/page.model'; @@ -16,13 +17,18 @@ export const CMS_SET_PAGE_FAIL_INDEX = '[Cms] Set Page Fail Index'; export class LoadCmsPageData extends StateUtils.EntityLoadAction { readonly type = LOAD_CMS_PAGE_DATA; + constructor(public payload: PageContext) { super(payload.type ?? '', payload.id); } } -export class LoadCmsPageDataFail extends StateUtils.EntityFailAction { +export class LoadCmsPageDataFail + extends StateUtils.EntityFailAction + implements ErrorAction +{ readonly type = LOAD_CMS_PAGE_DATA_FAIL; + constructor(pageContext: PageContext, error: any) { super(pageContext.type ?? '', pageContext.id, error); } @@ -30,6 +36,7 @@ export class LoadCmsPageDataFail extends StateUtils.EntityFailAction { export class LoadCmsPageDataSuccess extends StateUtils.EntitySuccessAction { readonly type = LOAD_CMS_PAGE_DATA_SUCCESS; + constructor(pageContext: PageContext, payload: Page) { super(pageContext.type ?? '', pageContext.id, payload); } @@ -37,18 +44,27 @@ export class LoadCmsPageDataSuccess extends StateUtils.EntitySuccessAction { export class CmsSetPageSuccessIndex extends StateUtils.EntitySuccessAction { readonly type = CMS_SET_PAGE_SUCCESS_INDEX; + constructor(pageContext: PageContext, payload: Page) { super(pageContext.type ?? '', pageContext.id, payload); } } -export class CmsSetPageFailIndex extends StateUtils.EntityFailAction { +export class CmsSetPageFailIndex + extends StateUtils.EntityFailAction + implements ErrorAction +{ readonly type = CMS_SET_PAGE_FAIL_INDEX; + constructor( pageContext: PageContext, public payload: string ) { - super(pageContext.type ?? '', pageContext.id); + super( + pageContext.type ?? '', + pageContext.id, + new Error('Failed to set cms page index') + ); } } diff --git a/projects/core/src/cms/store/effects/components.effect.spec.ts b/projects/core/src/cms/store/effects/components.effect.spec.ts index 8ddf0696e9c..c2fe2c23bd5 100755 --- a/projects/core/src/cms/store/effects/components.effect.spec.ts +++ b/projects/core/src/cms/store/effects/components.effect.spec.ts @@ -98,6 +98,9 @@ describe('Component Effects', () => { pageContext, }); const completion = new CmsActions.LoadCmsComponentFail({ + error: { + message: `Failed to load CmsComponent ${pageContext.type} uid: comp1`, + }, uid: action.payload.uid, pageContext, }); diff --git a/projects/core/src/cms/store/effects/components.effect.ts b/projects/core/src/cms/store/effects/components.effect.ts index 9b69fb1dcd4..b4635f50fcc 100755 --- a/projects/core/src/cms/store/effects/components.effect.ts +++ b/projects/core/src/cms/store/effects/components.effect.ts @@ -4,17 +4,17 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { Injectable, inject } from '@angular/core'; +import { inject, Injectable } from '@angular/core'; import { Actions, createEffect, ofType } from '@ngrx/effects'; import { Action } from '@ngrx/store'; -import { Observable, from } from 'rxjs'; +import { from, Observable } from 'rxjs'; import { catchError, groupBy, mergeMap, switchMap } from 'rxjs/operators'; import { AuthActions } from '../../../auth/user-auth/store/actions/index'; import { LoggerService } from '../../../logger'; import { CmsComponent } from '../../../model/cms.model'; import { PageContext } from '../../../routing/index'; import { SiteContextActions } from '../../../site-context/store/actions/index'; -import { normalizeHttpError } from '../../../util/normalize-http-error'; +import { tryNormalizeHttpError } from '../../../util/try-normalize-http-error'; import { bufferDebounceTime } from '../../../util/rxjs/buffer-debounce-time'; import { withdrawOn } from '../../../util/rxjs/withdraw-on'; import { CmsComponentConnector } from '../../connectors/component/cms-component.connector'; @@ -94,6 +94,9 @@ export class ComponentsEffects { actions.push( new CmsActions.LoadCmsComponentFail({ uid, + error: { + message: `Failed to load CmsComponent ${pageContext.type} uid: ${uid}`, + }, pageContext, }) ); @@ -106,7 +109,7 @@ export class ComponentsEffects { (uid) => new CmsActions.LoadCmsComponentFail({ uid, - error: normalizeHttpError(error, this.logger), + error: tryNormalizeHttpError(error, this.logger), pageContext, }) ) diff --git a/projects/core/src/cms/store/effects/navigation-entry-item.effect.ts b/projects/core/src/cms/store/effects/navigation-entry-item.effect.ts index 38086f889f7..b03bd38edf1 100755 --- a/projects/core/src/cms/store/effects/navigation-entry-item.effect.ts +++ b/projects/core/src/cms/store/effects/navigation-entry-item.effect.ts @@ -4,13 +4,13 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { Injectable, inject } from '@angular/core'; +import { inject, Injectable } from '@angular/core'; import { Actions, createEffect, ofType } from '@ngrx/effects'; import { Observable, of } from 'rxjs'; import { catchError, filter, map, mergeMap, take } from 'rxjs/operators'; import { LoggerService } from '../../../logger'; import { RoutingService } from '../../../routing/index'; -import { normalizeHttpError } from '../../../util/normalize-http-error'; +import { tryNormalizeHttpError } from '../../../util/try-normalize-http-error'; import { isNotUndefined } from '../../../util/type-guards'; import { CmsComponentConnector } from '../../connectors/component/cms-component.connector'; import { CmsActions } from '../actions/index'; @@ -54,7 +54,7 @@ export class NavigationEntryItemEffects { of( new CmsActions.LoadCmsNavigationItemsFail( data.nodeId, - normalizeHttpError(error, this.logger) + tryNormalizeHttpError(error, this.logger) ) ) ) @@ -71,7 +71,7 @@ export class NavigationEntryItemEffects { return of( new CmsActions.LoadCmsNavigationItemsFail( data.nodeId, - 'navigation nodes are empty' + new Error('navigation nodes are empty') ) ); } diff --git a/projects/core/src/cms/store/effects/page.effect.spec.ts b/projects/core/src/cms/store/effects/page.effect.spec.ts index 20a7272aa45..2297c5b021e 100644 --- a/projects/core/src/cms/store/effects/page.effect.spec.ts +++ b/projects/core/src/cms/store/effects/page.effect.spec.ts @@ -3,7 +3,7 @@ import { HttpClientTestingModule } from '@angular/common/http/testing'; import { TestBed } from '@angular/core/testing'; import { provideMockActions } from '@ngrx/effects/testing'; import { Action, StoreModule } from '@ngrx/store'; -import { LoggerService, normalizeHttpError } from '@spartacus/core'; +import { LoggerService, tryNormalizeHttpError } from '@spartacus/core'; import { cold, hot } from 'jasmine-marbles'; import { Observable, of, throwError } from 'rxjs'; import { AuthActions } from '../../../auth/user-auth/store/actions/index'; @@ -153,7 +153,7 @@ describe('Page Effects', () => { const completion = new CmsActions.LoadCmsPageDataFail( pageContext, - normalizeHttpError(error, new MockLoggerService()) + tryNormalizeHttpError(error, new MockLoggerService()) ); actions$ = hot('-a', { a: action }); diff --git a/projects/core/src/cms/store/effects/page.effect.ts b/projects/core/src/cms/store/effects/page.effect.ts index ad1f8983bb1..4ae83e1078d 100755 --- a/projects/core/src/cms/store/effects/page.effect.ts +++ b/projects/core/src/cms/store/effects/page.effect.ts @@ -21,7 +21,7 @@ import { AuthActions } from '../../../auth/user-auth/store/actions/index'; import { LoggerService } from '../../../logger'; import { RoutingService } from '../../../routing/index'; import { SiteContextActions } from '../../../site-context/store/actions/index'; -import { normalizeHttpError } from '../../../util/normalize-http-error'; +import { tryNormalizeHttpError } from '../../../util/try-normalize-http-error'; import { CmsPageConnector } from '../../connectors/page/cms-page.connector'; import { CmsStructureModel } from '../../model/page.model'; import { serializePageContext } from '../../utils/cms-utils'; @@ -96,7 +96,7 @@ export class PageEffects { of( new CmsActions.LoadCmsPageDataFail( pageContext, - normalizeHttpError(error, this.logger) + tryNormalizeHttpError(error, this.logger) ) ) ) diff --git a/projects/core/src/cms/store/reducers/page-index.reducer.spec.ts b/projects/core/src/cms/store/reducers/page-index.reducer.spec.ts index 8d03217345b..5a3250d8411 100644 --- a/projects/core/src/cms/store/reducers/page-index.reducer.spec.ts +++ b/projects/core/src/cms/store/reducers/page-index.reducer.spec.ts @@ -41,7 +41,7 @@ describe('Cms Page Index Reducer', () => { describe('LOAD_PAGE_DATA_FAIL action', () => { it('should return the initial state', () => { - const error = 'error'; + const error = new Error('error'); const { initialState } = fromPage; const action = new CmsActions.LoadCmsPageDataFail(pageContext, error); const state = fromPage.reducer(PageType.CONTENT_PAGE)( diff --git a/projects/core/src/error-handling/cx-error-handler.spec.ts b/projects/core/src/error-handling/cx-error-handler.spec.ts new file mode 100644 index 00000000000..c7461b48c76 --- /dev/null +++ b/projects/core/src/error-handling/cx-error-handler.spec.ts @@ -0,0 +1,46 @@ +import { TestBed } from '@angular/core/testing'; +import { CxErrorHandler } from './cx-error-handler'; +import { MULTI_ERROR_HANDLER, MultiErrorHandler } from './multi-error-handler'; + +class MockErrorHandler implements MultiErrorHandler { + handleError = jasmine.createSpy('handleError'); +} + +class MockErrorHandler2 implements MultiErrorHandler { + handleError = jasmine.createSpy('handleError'); +} + +describe('CxErrorHandler', () => { + let cxErrorHandler: CxErrorHandler; + let errorHandlers: MultiErrorHandler[]; + + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [ + CxErrorHandler, + { + provide: MULTI_ERROR_HANDLER, + useClass: MockErrorHandler, + multi: true, + }, + { + provide: MULTI_ERROR_HANDLER, + useClass: MockErrorHandler2, + multi: true, + }, + ], + }); + + cxErrorHandler = TestBed.inject(CxErrorHandler); + errorHandlers = TestBed.inject(MULTI_ERROR_HANDLER); + }); + + it('should call all error handlers', () => { + const error = new Error('test error'); + + cxErrorHandler.handleError(error); + errorHandlers.forEach((handler) => { + expect(handler.handleError).toHaveBeenCalledWith(error); + }); + }); +}); diff --git a/projects/core/src/error-handling/cx-error-handler.ts b/projects/core/src/error-handling/cx-error-handler.ts index b74bbac9bdd..e9f5cbca0b7 100644 --- a/projects/core/src/error-handling/cx-error-handler.ts +++ b/projects/core/src/error-handling/cx-error-handler.ts @@ -5,13 +5,35 @@ */ import { ErrorHandler, Injectable, inject } from '@angular/core'; -import { LoggerService } from '../logger/logger.service'; +import { LoggerService } from '../logger'; +import { MULTI_ERROR_HANDLER } from './multi-error-handler'; +/** + * The CxErrorHandler is the default ErrorHandler for Spartacus. + * It is responsible for handling errors and passing them to the registered multi error handlers. + * + * The prefix `Cx` was used to distinguish it from Angular's `ErrorHandler` class. + * For more, see Angular docs: https://angular.dev/api/core/ErrorHandler + * + */ @Injectable() export class CxErrorHandler implements ErrorHandler { - logger = inject(LoggerService); + /** + * @deprecated Since 2211.29 - `LoggerService` is not used anymore in this class. + * Instead it's now used in `LoggingErrorHandler`. + * This property will be removed in the future together with removing + * the feature toggle `propagateErrorsToServer`. + */ + protected logger = inject(LoggerService); + protected errorHandlers = inject(MULTI_ERROR_HANDLER); - handleError(error: any): void { - this.logger.error(error); + /** + * Error handler method. Handles the error by passing it to the registered multi error handlers. + * @param error - The error to be handled. + */ + handleError(error: unknown): void { + this.errorHandlers.forEach((handler) => { + handler.handleError(error); + }); } } diff --git a/projects/core/src/error-handling/effects-error-handler/cx-error-handler.effect.spec.ts b/projects/core/src/error-handling/effects-error-handler/cx-error-handler.effect.spec.ts new file mode 100644 index 00000000000..b51770da07e --- /dev/null +++ b/projects/core/src/error-handling/effects-error-handler/cx-error-handler.effect.spec.ts @@ -0,0 +1,96 @@ +import { TestBed } from '@angular/core/testing'; +import { Actions } from '@ngrx/effects'; +import { provideMockActions } from '@ngrx/effects/testing'; +import { Action } from '@ngrx/store'; +import { ErrorAction, FeatureConfigService } from '@spartacus/core'; +import { Observable, of } from 'rxjs'; +import { CxErrorHandlerEffect } from './cx-error-handler.effect'; +import { ErrorActionService } from './error-action.service'; + +describe('CxErrorHandlerEffect', () => { + let effect: CxErrorHandlerEffect; + let actions$: Observable; + let errorActionService: jasmine.SpyObj; + let featureConfigService: FeatureConfigService; + + beforeEach(() => { + const errorActionServiceSpy = jasmine.createSpyObj('ErrorActionService', [ + 'handle', + 'isErrorAction', + ]); + TestBed.configureTestingModule({ + providers: [ + CxErrorHandlerEffect, + FeatureConfigService, + provideMockActions(() => actions$), + { + provide: ErrorActionService, + useValue: errorActionServiceSpy, + }, + ], + }); + + effect = TestBed.inject(CxErrorHandlerEffect); + actions$ = TestBed.inject(Actions); + errorActionService = TestBed.inject( + ErrorActionService + ) as jasmine.SpyObj; + featureConfigService = TestBed.inject(FeatureConfigService); + }); + + it('should be created', () => { + expect(effect).toBeTruthy(); + }); + + describe('error$ ', () => { + describe('when ssrStrictErrorHandlingForHttpAndNgrx is enabled', () => { + beforeEach(() => { + spyOn(featureConfigService, 'isEnabled').and.returnValue(true); + }); + + it('should handle error action', () => { + const mockErrorAction: ErrorAction = { + type: 'ERROR_ACTION_TYPE', + error: new Error(), + }; + + errorActionService.isErrorAction.and.returnValue(true); + + actions$ = of(mockErrorAction); + + effect.error$.subscribe(); + + expect(errorActionService.handle).toHaveBeenCalledWith(mockErrorAction); + }); + + it('should not handle non-error action', () => { + const mockNonErrorAction = { + type: 'SOME_ACTION', + }; + + errorActionService.isErrorAction.and.returnValue(false); + + actions$ = of(mockNonErrorAction); + + effect.error$.subscribe(); + + expect(errorActionService.handle).not.toHaveBeenCalled(); + }); + }); + }); + describe('when ssrStrictErrorHandlingForHttpAndNgrx is disabled', () => { + beforeEach(() => { + spyOn(featureConfigService, 'isEnabled').and.returnValue(false); + }); + it('should not handle error action', () => { + const mockErrorAction: ErrorAction = { + type: 'ERROR_ACTION_TYPE', + error: new Error(), + }; + errorActionService.isErrorAction.and.returnValue(true); + actions$ = of(mockErrorAction); + effect.error$.subscribe(); + expect(errorActionService.handle).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/projects/core/src/error-handling/effects-error-handler/cx-error-handler.effect.ts b/projects/core/src/error-handling/effects-error-handler/cx-error-handler.effect.ts new file mode 100644 index 00000000000..301d7630bb5 --- /dev/null +++ b/projects/core/src/error-handling/effects-error-handler/cx-error-handler.effect.ts @@ -0,0 +1,41 @@ +/* + * SPDX-FileCopyrightText: 2024 SAP Spartacus team + * + * SPDX-License-Identifier: Apache-2.0 + */ + +import { Injectable, inject } from '@angular/core'; +import { Actions, createEffect } from '@ngrx/effects'; +import { Observable } from 'rxjs'; +import { filter, tap } from 'rxjs/operators'; +import { FeatureConfigService } from '../../features-config'; +import { ErrorAction } from './error-action'; +import { ErrorActionService } from './error-action.service'; + +/** + * Effect that captures in a centralized manner errors occurred in NgRx flow. + * When such an action is detected, it delegates the error handling to the `ErrorActionService`. + */ +@Injectable() +export class CxErrorHandlerEffect { + protected actions$ = inject(Actions); + protected errorActionService = inject(ErrorActionService); + private featureConfigService = inject(FeatureConfigService); + + error$: Observable = createEffect( + () => + this.actions$.pipe( + filter(this.errorActionService.isErrorAction), + tap((errorAction: ErrorAction) => { + if ( + this.featureConfigService.isEnabled( + 'ssrStrictErrorHandlingForHttpAndNgrx' + ) + ) { + this.errorActionService.handle(errorAction); + } + }) + ), + { dispatch: false } + ); +} diff --git a/projects/core/src/error-handling/effects-error-handler/effects-error-handler.module.ts b/projects/core/src/error-handling/effects-error-handler/effects-error-handler.module.ts new file mode 100644 index 00000000000..7a271bc04d8 --- /dev/null +++ b/projects/core/src/error-handling/effects-error-handler/effects-error-handler.module.ts @@ -0,0 +1,22 @@ +/* + * SPDX-FileCopyrightText: 2024 SAP Spartacus team + * + * SPDX-License-Identifier: Apache-2.0 + */ + +import { ModuleWithProviders, NgModule } from '@angular/core'; +import { EffectsModule } from '@ngrx/effects'; +import { CxErrorHandlerEffect } from './cx-error-handler.effect'; +import { ErrorActionService } from './error-action.service'; + +@NgModule({ + imports: [EffectsModule.forFeature([CxErrorHandlerEffect])], +}) +export class EffectsErrorHandlerModule { + static forRoot(): ModuleWithProviders { + return { + ngModule: EffectsErrorHandlerModule, + providers: [ErrorActionService], + }; + } +} diff --git a/projects/core/src/error-handling/effects-error-handler/error-action.service.spec.ts b/projects/core/src/error-handling/effects-error-handler/error-action.service.spec.ts new file mode 100644 index 00000000000..468a58c9613 --- /dev/null +++ b/projects/core/src/error-handling/effects-error-handler/error-action.service.spec.ts @@ -0,0 +1,109 @@ +import { HttpErrorResponse } from '@angular/common/http'; +import { ErrorHandler } from '@angular/core'; +import { TestBed } from '@angular/core/testing'; +import { Action } from '@ngrx/store'; +import { ErrorAction, HttpErrorModel, WindowRef } from '@spartacus/core'; +import { ErrorActionService } from './error-action.service'; + +describe('ErrorActionService', () => { + let errorActionService: ErrorActionService; + let windowRef: WindowRef; + let errorHandlerSpy: jasmine.SpyObj; + + beforeEach(() => { + const errorHandlerSpyObj = jasmine.createSpyObj('ErrorHandler', [ + 'handleError', + ]); + + TestBed.configureTestingModule({ + providers: [ + ErrorActionService, + { provide: WindowRef, useValue: { isBrowser: () => false } }, + { provide: ErrorHandler, useValue: errorHandlerSpyObj }, + ], + }); + + errorActionService = TestBed.inject(ErrorActionService); + windowRef = TestBed.inject(WindowRef); + errorHandlerSpy = TestBed.inject( + ErrorHandler + ) as jasmine.SpyObj; + }); + + it('should be created', () => { + expect(errorActionService).toBeTruthy(); + }); + + describe('handleError', () => { + it('should call ErrorHandler.handleError if error is not HttpErrorModel or HttpErrorResponse', () => { + const mockErrorAction: ErrorAction = { + type: 'ERROR_ACTION_TYPE', + error: new Error('Test error'), + }; + + errorActionService.handle(mockErrorAction); + + expect(errorHandlerSpy.handleError).toHaveBeenCalledWith( + mockErrorAction.error + ); + }); + + it('should not call ErrorHandler.handleError if error is HttpErrorModel', () => { + const mockErrorAction: ErrorAction = { + type: 'ERROR_ACTION_TYPE', + error: new HttpErrorModel(), + }; + + errorActionService.handle(mockErrorAction); + + expect(errorHandlerSpy.handleError).not.toHaveBeenCalled(); + }); + + it('should not call ErrorHandler.handleError if error is HttpErrorResponse', () => { + const mockErrorAction: ErrorAction = { + type: 'ERROR_ACTION_TYPE', + error: new HttpErrorResponse({}), + }; + + errorActionService.handle(mockErrorAction); + + expect(errorHandlerSpy.handleError).not.toHaveBeenCalled(); + }); + + it('should not call ErrorHandler.handleError in browser runtime environment', () => { + spyOn(windowRef, 'isBrowser').and.returnValue(true); + + const mockErrorAction: ErrorAction = { + type: 'ERROR_ACTION_TYPE', + error: new Error('Test error'), + }; + + errorActionService.handle(mockErrorAction); + + expect(errorHandlerSpy.handleError).not.toHaveBeenCalled(); + }); + }); + + describe('filterActions', () => { + it('should return true for action implementing ErrorAction interface', () => { + const mockErrorAction: ErrorAction = { + type: 'ERROR_ACTION_TYPE', + error: new Error(), + }; + + const result = errorActionService.isErrorAction(mockErrorAction); + + expect(result).toBeTruthy(); + }); + + it('should return false for action not implementing ErrorAction interface', () => { + const mockNonErrorAction = { type: 'SOME_ACTION' }; + + const result = errorActionService.isErrorAction( + mockNonErrorAction as Action + ); + + expect(result).toBeFalsy(); + }); + }); +}); diff --git a/projects/core/src/error-handling/effects-error-handler/error-action.service.ts b/projects/core/src/error-handling/effects-error-handler/error-action.service.ts new file mode 100644 index 00000000000..6a295ccd898 --- /dev/null +++ b/projects/core/src/error-handling/effects-error-handler/error-action.service.ts @@ -0,0 +1,57 @@ +/* + * SPDX-FileCopyrightText: 2024 SAP Spartacus team + * + * SPDX-License-Identifier: Apache-2.0 + */ + +import { HttpErrorResponse } from '@angular/common/http'; +import { ErrorHandler, Injectable, inject } from '@angular/core'; +import { Action } from '@ngrx/store'; +import { HttpErrorModel } from '../../model/index'; +import { WindowRef } from '../../window'; +import { ErrorAction } from './error-action'; + +/** + * Service responsible for capturing and handling NgRx error actions that implements `ErrorAction`. + * It ensures that HTTP errors, which are already handled by `HttpErrorHandlerInterceptor`, are not processed + * again to avoid duplicate error handling. + */ +@Injectable() +export class ErrorActionService { + protected errorHandler: ErrorHandler = inject(ErrorHandler); + protected windowRef = inject(WindowRef); + + handle(action: ErrorAction): void { + const error: unknown = action.error; + + // Http errors are already handled in HttpErrorHandlerInterceptor. + // To avoid duplicate errors we want to check if the error is not of type + // HttpErrorModel or HttpErrorResponse. + const isNotHttpError = + !(error instanceof HttpErrorModel) && + !(error instanceof HttpErrorResponse); + + if (isNotHttpError && this.shouldHandleError(error)) { + this.errorHandler.handleError(error); + } + } + + /** Here we want to filter which error actions should be handled. + * By default, we check if action implements interface ErrorAction */ + isErrorAction(action: Action): action is ErrorAction { + return 'error' in action; + } + + /** + * Determine if the error should be handled by the `ErrorHandler`. + * + * Be default, we avoid sending unpredictable errors to the browser's console, to prevent + * possibly exposing there potentially confidential user's data. + * This isn't an issue in SSR, where pages are rendered anonymously. + * Moreover, in SSR we want to capture all app's errors, so we can potentially send + * a HTTP error response (e.g. 500 error page) from SSR to a client. + */ + protected shouldHandleError(_error: unknown): boolean { + return !this.windowRef.isBrowser(); + } +} diff --git a/projects/core/src/error-handling/effects-error-handler/error-action.ts b/projects/core/src/error-handling/effects-error-handler/error-action.ts new file mode 100644 index 00000000000..2e04db6cc34 --- /dev/null +++ b/projects/core/src/error-handling/effects-error-handler/error-action.ts @@ -0,0 +1,11 @@ +/* + * SPDX-FileCopyrightText: 2024 SAP Spartacus team + * + * SPDX-License-Identifier: Apache-2.0 + */ + +import { Action } from '@ngrx/store'; + +export interface ErrorAction extends Action { + error: Object; +} diff --git a/projects/core/src/error-handling/effects-error-handler/index.ts b/projects/core/src/error-handling/effects-error-handler/index.ts new file mode 100644 index 00000000000..a3ba7d73555 --- /dev/null +++ b/projects/core/src/error-handling/effects-error-handler/index.ts @@ -0,0 +1,9 @@ +/* + * SPDX-FileCopyrightText: 2024 SAP Spartacus team + * + * SPDX-License-Identifier: Apache-2.0 + */ + +export * from './effects-error-handler.module'; +export * from './error-action'; +export * from './error-action.service'; diff --git a/projects/core/src/error-handling/error-handling.module.ts b/projects/core/src/error-handling/error-handling.module.ts index 9a85e9d5f66..7216f69410a 100644 --- a/projects/core/src/error-handling/error-handling.module.ts +++ b/projects/core/src/error-handling/error-handling.module.ts @@ -6,13 +6,22 @@ import { ErrorHandler, ModuleWithProviders, NgModule } from '@angular/core'; import { CxErrorHandler } from './cx-error-handler'; +import { EffectsErrorHandlerModule } from './effects-error-handler'; +import { HttpErrorHandlerModule } from './http-error-handler'; +import { provideMultiErrorHandler } from './multi-error-handler/provide-multi-error-handler'; -@NgModule() +@NgModule({ + imports: [ + EffectsErrorHandlerModule.forRoot(), + HttpErrorHandlerModule.forRoot(), + ], +}) export class ErrorHandlingModule { static forRoot(): ModuleWithProviders { return { ngModule: ErrorHandlingModule, providers: [ + provideMultiErrorHandler(), { provide: ErrorHandler, useClass: CxErrorHandler, diff --git a/projects/core/src/error-handling/http-error-handler/http-error-handler.interceptor.spec.ts b/projects/core/src/error-handling/http-error-handler/http-error-handler.interceptor.spec.ts new file mode 100644 index 00000000000..1d9e23332f1 --- /dev/null +++ b/projects/core/src/error-handling/http-error-handler/http-error-handler.interceptor.spec.ts @@ -0,0 +1,159 @@ +import { + HttpErrorResponse, + HttpEvent, + HttpHandler, + HttpRequest, +} from '@angular/common/http'; +import { ErrorHandler, Injectable } from '@angular/core'; +import { TestBed } from '@angular/core/testing'; +import { Observable, throwError } from 'rxjs'; +import { FeatureConfigService } from '../../features-config'; +import { OccEndpointsService } from '../../occ'; +import { WindowRef } from '../../window'; +import { HttpErrorHandlerInterceptor } from './http-error-handler.interceptor'; +import { + CmsPageNotFoundOutboundHttpError, + OutboundHttpError, +} from './outbound-http-error'; + +@Injectable() +class MockErrorHandler { + handleError(_error: any): void {} +} + +@Injectable() +class MockOccEndpointsService { + buildUrl = (val: string) => val; +} + +describe('HttpErrorHandlerInterceptor', () => { + let interceptor: HttpErrorHandlerInterceptor; + let errorHandler: ErrorHandler; + let request: HttpRequest; + let next: HttpHandler; + let featureConfigService: FeatureConfigService; + let windowRef: WindowRef; + + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [ + HttpErrorHandlerInterceptor, + FeatureConfigService, + { provide: OccEndpointsService, useClass: MockOccEndpointsService }, + { provide: WindowRef, useValue: { isBrowser: () => false } }, + { provide: ErrorHandler, useClass: MockErrorHandler }, + ], + }); + + interceptor = TestBed.inject(HttpErrorHandlerInterceptor); + errorHandler = TestBed.inject(ErrorHandler); + featureConfigService = TestBed.inject(FeatureConfigService); + windowRef = TestBed.inject(WindowRef); + + request = new HttpRequest('GET', 'test-url'); + next = { + handle: () => new Observable>(), + } as HttpHandler; + }); + + it('should create the interceptor', () => { + expect(interceptor).toBeTruthy(); + }); + + describe('when ssrStrictErrorHandlingForHttpAndNgrx is enabled', () => { + beforeEach(() => { + spyOn(featureConfigService, 'isEnabled').and.returnValue(true); + }); + it('should call handleError with OutboundHttpError for any HTTP error except 404 cms page not found', (done) => { + const error: HttpErrorResponse = new HttpErrorResponse({ + status: 500, + statusText: 'error', + }); + spyOn(errorHandler, 'handleError'); + + next.handle = () => throwError(() => error); + + interceptor.intercept(request, next).subscribe({ + error: (err) => { + expect(err).toEqual(error); + expect(errorHandler.handleError).toHaveBeenCalledWith( + jasmine.any(OutboundHttpError) + ); + done(); + }, + }); + }); + + it('should call handleError with CmsPageNotFoundOutboundHttpError when CMS page not found', (done) => { + const error: HttpErrorResponse = new HttpErrorResponse({ + url: 'pages', + status: 404, + }); + spyOn(errorHandler, 'handleError'); + + next.handle = () => throwError(() => error); + + interceptor.intercept(request, next).subscribe({ + error: (err) => { + expect(err).toEqual(error); + expect(errorHandler.handleError).toHaveBeenCalledWith( + jasmine.any(CmsPageNotFoundOutboundHttpError) + ); + done(); + }, + }); + }); + + it('should not call handleError when it is not SSR', (done) => { + spyOn(errorHandler, 'handleError'); + spyOn(windowRef, 'isBrowser').and.returnValue(true); + + next.handle = () => throwError(() => new HttpErrorResponse({})); + + interceptor.intercept(request, next).subscribe({ + error: () => { + expect(errorHandler.handleError).not.toHaveBeenCalled(); + done(); + }, + }); + }); + + it('should pass through the request when there is no error', (done) => { + const response: HttpEvent = { + status: 200, + statusText: 'ok', + } as HttpEvent; + next.handle = () => + new Observable>((observer) => observer.next(response)); + + interceptor.intercept(request, next).subscribe((result) => { + expect(result).toBe(response); + done(); + }); + }); + }); + + describe('when ssrStrictErrorHandlingForHttpAndNgrx is disabled', () => { + beforeEach(() => { + spyOn(featureConfigService, 'isEnabled').and.returnValue(false); + }); + + it('should pass through the request when there is an error', (done) => { + const error: HttpErrorResponse = new HttpErrorResponse({ + status: 400, + statusText: 'error', + }); + spyOn(errorHandler, 'handleError'); + + next.handle = () => throwError(() => error); + + interceptor.intercept(request, next).subscribe({ + error: (err) => { + expect(err).toEqual(error); + expect(errorHandler.handleError).not.toHaveBeenCalled(); + done(); + }, + }); + }); + }); +}); diff --git a/projects/core/src/error-handling/http-error-handler/http-error-handler.interceptor.ts b/projects/core/src/error-handling/http-error-handler/http-error-handler.interceptor.ts new file mode 100644 index 00000000000..0b8fa954899 --- /dev/null +++ b/projects/core/src/error-handling/http-error-handler/http-error-handler.interceptor.ts @@ -0,0 +1,100 @@ +/* + * SPDX-FileCopyrightText: 2024 SAP Spartacus team + * + * SPDX-License-Identifier: Apache-2.0 + */ + +import { + HttpErrorResponse, + HttpEvent, + HttpHandler, + HttpInterceptor, + HttpRequest, +} from '@angular/common/http'; +import { ErrorHandler, Injectable, inject } from '@angular/core'; +import { Observable } from 'rxjs'; +import { tap } from 'rxjs/operators'; +import { FeatureConfigService } from '../../features-config'; +import { HttpResponseStatus } from '../../global-message'; +import { OccEndpointsService } from '../../occ'; +import { WindowRef } from '../../window'; +import { + CmsPageNotFoundOutboundHttpError, + OutboundHttpError, +} from './outbound-http-error'; + +/** + * This interceptor forwards all HTTP errors (e.g. 5xx or 4xx status response from backend) + * to Angular `ErrorHandler`. + * + * Thanks to this, in SSR, any HTTP error from backend can potentially mark the Server-Side Rendering + * as an error and therefore allow for sending an appropriate error response to a final client of SSR. + * + * NOTE: It handles not only HTTP errors, but also any RxJs errors + * (e.g., `TimeoutError` from `timeout()` operator) thrown in any subsequent interceptor. + * + * CAUTION: It MUST be provided as the first one in the application to be able to + * catch errors from all subsequent interceptors. + */ +@Injectable() +export class HttpErrorHandlerInterceptor implements HttpInterceptor { + protected errorHandler = inject(ErrorHandler); + protected occEndpointsService = inject(OccEndpointsService); + protected windowRef = inject(WindowRef); + private featureService = inject(FeatureConfigService); + + intercept( + request: HttpRequest, + next: HttpHandler + ): Observable> { + return next.handle(request).pipe( + tap({ + error: (error: unknown) => { + if ( + this.featureService.isEnabled( + 'ssrStrictErrorHandlingForHttpAndNgrx' + ) && + this.shouldHandleError(error) + ) { + this.handleError(error); + } + }, + }) + ); + } + + /** + * Determine if the error should be handled by the `ErrorHandler`. + * + * Be default, we avoid sending unpredictable errors to the browser's console, to prevent + * possibly exposing there potentially confidential user's data. + * This isn't an issue in SSR, where pages are rendered anonymously. + * Moreover, in SSR we want to capture all app's errors, so we can potentially send + * a HTTP error response (e.g. 500 error page) from SSR to a client. + */ + protected shouldHandleError(_error: unknown): boolean { + return !this.windowRef.isBrowser(); + } + + protected handleError(error: unknown): void { + error = this.isCmsPageNotFoundHttpError(error) + ? new CmsPageNotFoundOutboundHttpError(error) + : new OutboundHttpError(error); + this.errorHandler.handleError(error); + } + + /** + * Checks if the error corresponds to a CMS page not found HTTP error. + * + * @param error - The error object to check. + * @returns `true` if the error corresponds to a CMS page not found HTTP error, `false` otherwise. + */ + protected isCmsPageNotFoundHttpError(error: unknown): boolean { + const expectedUrl = this.occEndpointsService.buildUrl('pages'); + return ( + error instanceof HttpErrorResponse && + error.status === HttpResponseStatus.NOT_FOUND && + (error.url ?? '').startsWith(expectedUrl) + ); + } +} diff --git a/projects/core/src/error-handling/http-error-handler/http-error-handler.module.ts b/projects/core/src/error-handling/http-error-handler/http-error-handler.module.ts new file mode 100644 index 00000000000..0f4b65dd9cd --- /dev/null +++ b/projects/core/src/error-handling/http-error-handler/http-error-handler.module.ts @@ -0,0 +1,25 @@ +/* + * SPDX-FileCopyrightText: 2024 SAP Spartacus team + * + * SPDX-License-Identifier: Apache-2.0 + */ + +import { HTTP_INTERCEPTORS } from '@angular/common/http'; +import { ModuleWithProviders, NgModule } from '@angular/core'; +import { HttpErrorHandlerInterceptor } from './http-error-handler.interceptor'; + +@NgModule() +export class HttpErrorHandlerModule { + static forRoot(): ModuleWithProviders { + return { + ngModule: HttpErrorHandlerModule, + providers: [ + { + provide: HTTP_INTERCEPTORS, + useClass: HttpErrorHandlerInterceptor, + multi: true, + }, + ], + }; + } +} diff --git a/projects/core/src/error-handling/http-error-handler/index.ts b/projects/core/src/error-handling/http-error-handler/index.ts new file mode 100644 index 00000000000..e7f24185fff --- /dev/null +++ b/projects/core/src/error-handling/http-error-handler/index.ts @@ -0,0 +1,9 @@ +/* + * SPDX-FileCopyrightText: 2024 SAP Spartacus team + * + * SPDX-License-Identifier: Apache-2.0 + */ + +export * from './http-error-handler.interceptor'; +export * from './http-error-handler.module'; +export * from './outbound-http-error'; diff --git a/projects/core/src/error-handling/http-error-handler/outbound-http-error.ts b/projects/core/src/error-handling/http-error-handler/outbound-http-error.ts new file mode 100644 index 00000000000..4dea48f0cdc --- /dev/null +++ b/projects/core/src/error-handling/http-error-handler/outbound-http-error.ts @@ -0,0 +1,25 @@ +/* + * SPDX-FileCopyrightText: 2024 SAP Spartacus team + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * Represents an outbound HTTP error that occurs when communicating with the backend. + */ +export class OutboundHttpError extends Error { + constructor(cause: unknown) { + super('Outbound HTTP Error', { cause }); + } +} + +/** + * Represents an outbound HTTP error specific to a CMS page not found. + * Extends the base OutboundHttpError class. + */ +export class CmsPageNotFoundOutboundHttpError extends OutboundHttpError { + constructor(cause: unknown) { + super(cause); + this.message = 'CMS Page Not Found'; + } +} diff --git a/projects/core/src/error-handling/index.ts b/projects/core/src/error-handling/index.ts index caaba103780..25cce8474b3 100644 --- a/projects/core/src/error-handling/index.ts +++ b/projects/core/src/error-handling/index.ts @@ -5,4 +5,7 @@ */ export * from './cx-error-handler'; +export * from './effects-error-handler'; export * from './error-handling.module'; +export * from './http-error-handler'; +export * from './multi-error-handler'; diff --git a/projects/core/src/error-handling/multi-error-handler/index.ts b/projects/core/src/error-handling/multi-error-handler/index.ts new file mode 100644 index 00000000000..cb3e4b55856 --- /dev/null +++ b/projects/core/src/error-handling/multi-error-handler/index.ts @@ -0,0 +1,8 @@ +/* + * SPDX-FileCopyrightText: 2024 SAP Spartacus team + * + * SPDX-License-Identifier: Apache-2.0 + */ + +export * from './logging-error-handler'; +export * from './multi-error-handler'; diff --git a/projects/core/src/error-handling/multi-error-handler/logging-error-handler.spec.ts b/projects/core/src/error-handling/multi-error-handler/logging-error-handler.spec.ts new file mode 100644 index 00000000000..5e2f0e27882 --- /dev/null +++ b/projects/core/src/error-handling/multi-error-handler/logging-error-handler.spec.ts @@ -0,0 +1,30 @@ +import { TestBed } from '@angular/core/testing'; +import { LoggerService } from '../../logger'; +import { LoggingErrorHandler } from './logging-error-handler'; + +class MockLoggerService implements Partial { + error = jasmine.createSpy('error'); +} + +describe('LoggingErrorHandler', () => { + let errorHandler: LoggingErrorHandler; + let loggerService: LoggerService; + + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [ + LoggingErrorHandler, + { provide: LoggerService, useClass: MockLoggerService }, + ], + }); + + loggerService = TestBed.inject(LoggerService); + errorHandler = TestBed.inject(LoggingErrorHandler); + }); + + it('should log the error using the logger service', () => { + const error = new Error('Test error'); + errorHandler.handleError(error); + expect(loggerService.error).toHaveBeenCalledWith(error); + }); +}); diff --git a/projects/core/src/error-handling/multi-error-handler/logging-error-handler.ts b/projects/core/src/error-handling/multi-error-handler/logging-error-handler.ts new file mode 100644 index 00000000000..b922c149fbc --- /dev/null +++ b/projects/core/src/error-handling/multi-error-handler/logging-error-handler.ts @@ -0,0 +1,26 @@ +/* + * SPDX-FileCopyrightText: 2024 SAP Spartacus team + * + * SPDX-License-Identifier: Apache-2.0 + */ + +import { Injectable, inject } from '@angular/core'; +import { LoggerService } from '../../logger'; +import { MultiErrorHandler } from './multi-error-handler'; + +/** + * An error handler that logs errors using a logger service. + * Intended to be used as part of a multi-error handler strategy. + * + * @see MultiErrorHandler + */ +@Injectable({ + providedIn: 'root', +}) +export class LoggingErrorHandler implements MultiErrorHandler { + protected logger = inject(LoggerService); + + handleError(error: Error): void { + this.logger.error(error); + } +} diff --git a/projects/core/src/error-handling/multi-error-handler/multi-error-handler.ts b/projects/core/src/error-handling/multi-error-handler/multi-error-handler.ts new file mode 100644 index 00000000000..5d3a05fa9a7 --- /dev/null +++ b/projects/core/src/error-handling/multi-error-handler/multi-error-handler.ts @@ -0,0 +1,27 @@ +/* + * SPDX-FileCopyrightText: 2024 SAP Spartacus team + * + * SPDX-License-Identifier: Apache-2.0 + */ + +import { InjectionToken } from '@angular/core'; + +/** + * Multi error handlers are responsible for act with errors handled by the CxErrorHandler. + * + * @method handleError - Handles the error. + * + * @public + */ +export interface MultiErrorHandler { + handleError(error: unknown): void; +} + +/** + * Injection token for multi error handlers. + * Multi provided error handlers will be called in the order they are provided. + */ + +export const MULTI_ERROR_HANDLER = new InjectionToken( + 'MULTI_ERROR_HANDLER' +); diff --git a/projects/core/src/error-handling/multi-error-handler/provide-multi-error-handler.ts b/projects/core/src/error-handling/multi-error-handler/provide-multi-error-handler.ts new file mode 100644 index 00000000000..43e59eb1cae --- /dev/null +++ b/projects/core/src/error-handling/multi-error-handler/provide-multi-error-handler.ts @@ -0,0 +1,19 @@ +/* + * SPDX-FileCopyrightText: 2024 SAP Spartacus team + * + * SPDX-License-Identifier: Apache-2.0 + */ + +import { Provider } from '@angular/core'; +import { LoggingErrorHandler } from './logging-error-handler'; +import { MULTI_ERROR_HANDLER } from './multi-error-handler'; + +export function provideMultiErrorHandler(): Provider[] { + return [ + { + provide: MULTI_ERROR_HANDLER, + useExisting: LoggingErrorHandler, + multi: true, + }, + ]; +} diff --git a/projects/core/src/features-config/feature-toggles/config/feature-toggles.ts b/projects/core/src/features-config/feature-toggles/config/feature-toggles.ts index 1d45cae3b4f..3358622788d 100644 --- a/projects/core/src/features-config/feature-toggles/config/feature-toggles.ts +++ b/projects/core/src/features-config/feature-toggles/config/feature-toggles.ts @@ -115,6 +115,25 @@ export interface FeatureTogglesInterface { */ productConfiguratorAttributeTypesV2?: boolean; + /** + * In a server environment (SSR or Prerendering) it propagates all errors caught in Angular app + * (in the Angular's `ErrorHandler` class) to the server layer. + * + * In SSR, such a propagation allows the server layer (e.g. ExpressJS) for handling those errors, + * e.g. sending a proper Error Page in response to the client, + * instead of a rendered HTML that is possibly malformed due to the occurred error. + */ + propagateErrorsToServer?: boolean; + + /** + * In SSR, the following errors will be printed to logs (and additionally can also + * be forwarded to ExpressJS if only the other feature toggle `propagateErrorsToServer` is enabled): + * + * 1. any outgoing HTTP request error (4xx-5xx status) + * 2. any NgRx action with the `error` property + */ + ssrStrictErrorHandlingForHttpAndNgrx?: boolean; + /** * The product configuration UI is completely re-rendered after each UI interaction. This may lead to performance issues for large configuration models, * where a lot of attributes (>50) and/or a lot of possible values per attribute (>50) are rendered on the UI. @@ -524,6 +543,8 @@ export const defaultFeatureToggles: Required = { storeFrontLibCardParagraphTruncated: true, useProductCarouselBatchApi: false, productConfiguratorAttributeTypesV2: true, + propagateErrorsToServer: false, + ssrStrictErrorHandlingForHttpAndNgrx: false, productConfiguratorDeltaRendering: false, a11yRequiredAsterisks: false, a11yQuantityOrderTabbing: false, diff --git a/projects/core/src/model/index.ts b/projects/core/src/model/index.ts index ae21915d691..3266e9dcd37 100644 --- a/projects/core/src/model/index.ts +++ b/projects/core/src/model/index.ts @@ -5,7 +5,6 @@ */ export * from './address.model'; -export * from './payment.model'; export * from './cms.model'; export * from './consent.model'; export * from './customer-coupon.model'; @@ -13,6 +12,7 @@ export * from './image.model'; export * from './misc.model'; export * from './notification-preference.model'; export * from './org-unit.model'; +export * from './payment.model'; export * from './point-of-service.model'; export * from './product-interest.model'; export * from './product-search.model'; diff --git a/projects/core/src/process/store/selectors/process.selectors.spec.ts b/projects/core/src/process/store/selectors/process.selectors.spec.ts index 934141fdd36..2c117c7362d 100644 --- a/projects/core/src/process/store/selectors/process.selectors.spec.ts +++ b/projects/core/src/process/store/selectors/process.selectors.spec.ts @@ -81,7 +81,11 @@ describe('Process selectors', () => { describe('getProcessErrorFactory', () => { it('should return success flag', () => { store.dispatch( - new StateUtils.EntityFailAction(PROCESS_FEATURE, MOCK_PROCESS_ID) + new StateUtils.EntityFailAction( + PROCESS_FEATURE, + MOCK_PROCESS_ID, + new Error('error') + ) ); let result = false; diff --git a/projects/core/src/product/store/actions/product-references.action.spec.ts b/projects/core/src/product/store/actions/product-references.action.spec.ts index 578ae9fe9b1..145f5872603 100644 --- a/projects/core/src/product/store/actions/product-references.action.spec.ts +++ b/projects/core/src/product/store/actions/product-references.action.spec.ts @@ -24,11 +24,12 @@ describe('Product References Actions', () => { describe('LOAD_PRODUCT_REFERENCES_FAIL', () => { it('should create the action', () => { - const payload: ErrorModel = { message: 'Load Error' }; - const action = new ProductActions.LoadProductReferencesFail(payload); + const error: ErrorModel = { message: 'Load Error' }; + const action = new ProductActions.LoadProductReferencesFail(error); expect({ ...action }).toEqual({ type: ProductActions.LOAD_PRODUCT_REFERENCES_FAIL, - payload, + payload: error, + error, }); }); }); diff --git a/projects/core/src/product/store/actions/product-references.action.ts b/projects/core/src/product/store/actions/product-references.action.ts index 8e663653256..7bb6c155f32 100644 --- a/projects/core/src/product/store/actions/product-references.action.ts +++ b/projects/core/src/product/store/actions/product-references.action.ts @@ -5,7 +5,7 @@ */ import { Action } from '@ngrx/store'; -import { ErrorModel } from '../../../model/misc.model'; +import { ErrorAction } from '../../../error-handling'; import { ProductReference } from '../../../model/product.model'; export const LOAD_PRODUCT_REFERENCES = '[Product] Load Product References Data'; @@ -17,6 +17,7 @@ export const CLEAN_PRODUCT_REFERENCES = '[Product] Clean Product References'; export class LoadProductReferences implements Action { readonly type = LOAD_PRODUCT_REFERENCES; + constructor( public payload: { productCode: string; @@ -26,13 +27,26 @@ export class LoadProductReferences implements Action { ) {} } -export class LoadProductReferencesFail implements Action { +export class LoadProductReferencesFail implements ErrorAction { readonly type = LOAD_PRODUCT_REFERENCES_FAIL; - constructor(public payload?: ErrorModel) {} + public error: any; + + // eslint-disable-next-line @typescript-eslint/unified-signatures + constructor(payload: any); + /** + * @deprecated Please pass the argument `payload` (i.e. the error object). + * It will become mandatory along with removing + * the feature toggle `ssrStrictErrorHandlingForHttpAndNgrx`. + */ + constructor(); + constructor(public payload?: any) { + this.error = payload; + } } export class LoadProductReferencesSuccess implements Action { readonly type = LOAD_PRODUCT_REFERENCES_SUCCESS; + constructor( public payload: { productCode: string; diff --git a/projects/core/src/product/store/actions/product-reviews.action.spec.ts b/projects/core/src/product/store/actions/product-reviews.action.spec.ts index 370f5e7cb05..b3026e65e25 100644 --- a/projects/core/src/product/store/actions/product-reviews.action.spec.ts +++ b/projects/core/src/product/store/actions/product-reviews.action.spec.ts @@ -17,11 +17,12 @@ describe('Product Review Actions', () => { describe('LOAD_PRODUCT_REVIEWS_FAIL', () => { it('should create the action', () => { - const payload: ErrorModel = { message: 'Load Error' }; - const action = new ProductActions.LoadProductReviewsFail(payload); + const error: ErrorModel = { message: 'Load Error' }; + const action = new ProductActions.LoadProductReviewsFail(error); expect({ ...action }).toEqual({ type: ProductActions.LOAD_PRODUCT_REVIEWS_FAIL, - payload, + payload: error, + error, }); }); }); diff --git a/projects/core/src/product/store/actions/product-reviews.action.ts b/projects/core/src/product/store/actions/product-reviews.action.ts index bb8ef91155f..8c6b8857cd6 100644 --- a/projects/core/src/product/store/actions/product-reviews.action.ts +++ b/projects/core/src/product/store/actions/product-reviews.action.ts @@ -5,7 +5,7 @@ */ import { Action } from '@ngrx/store'; -import { ErrorModel, HttpErrorModel } from '../../../model/misc.model'; +import { ErrorAction } from '../../../error-handling'; import { Review } from '../../../model/product.model'; export const LOAD_PRODUCT_REVIEWS = '[Product] Load Product Reviews Data'; @@ -20,31 +20,59 @@ export const POST_PRODUCT_REVIEW_SUCCESS = export class LoadProductReviews implements Action { readonly type = LOAD_PRODUCT_REVIEWS; + constructor(public payload: string) {} } -export class LoadProductReviewsFail implements Action { +export class LoadProductReviewsFail implements ErrorAction { readonly type = LOAD_PRODUCT_REVIEWS_FAIL; - constructor(public payload?: ErrorModel) {} + public error: any; + + // eslint-disable-next-line @typescript-eslint/unified-signatures + constructor(payload: any); + /** + * @deprecated Please pass the argument `payload` (i.e. the error object). + * It will become mandatory along with removing + * the feature toggle `ssrStrictErrorHandlingForHttpAndNgrx`. + */ + constructor(); + constructor(public payload?: any) { + this.error = payload; + } } export class LoadProductReviewsSuccess implements Action { readonly type = LOAD_PRODUCT_REVIEWS_SUCCESS; + constructor(public payload: { productCode: string; list: Review[] }) {} } export class PostProductReview implements Action { readonly type = POST_PRODUCT_REVIEW; + constructor(public payload: { productCode: string; review: Review }) {} } -export class PostProductReviewFail implements Action { +export class PostProductReviewFail implements ErrorAction { readonly type = POST_PRODUCT_REVIEW_FAIL; - constructor(public payload?: HttpErrorModel) {} + public error: any; + + // eslint-disable-next-line @typescript-eslint/unified-signatures + constructor(payload: any); + /** + * @deprecated Please pass the argument `payload` (i.e. the error object). + * It will become mandatory along with removing + * the feature toggle `ssrStrictErrorHandlingForHttpAndNgrx`. + */ + constructor(); + constructor(public payload?: any) { + this.error = payload; + } } export class PostProductReviewSuccess implements Action { readonly type = POST_PRODUCT_REVIEW_SUCCESS; + constructor(public payload: Review) {} } diff --git a/projects/core/src/product/store/actions/product-search-by-code.action.spec.ts b/projects/core/src/product/store/actions/product-search-by-code.action.spec.ts index 2e6d56bcc75..6b47944919a 100644 --- a/projects/core/src/product/store/actions/product-search-by-code.action.spec.ts +++ b/projects/core/src/product/store/actions/product-search-by-code.action.spec.ts @@ -1,7 +1,7 @@ -import * as fromProductSearchByCode from './product-search-by-code.action'; -import { PRODUCT_SEARCH_RESULTS_BY_CODES_ENTITY } from '../product-state'; -import { EntityScopedLoaderActions } from '../../../state/utils/scoped-loader/entity-scoped-loader.actions'; import { StateUtils } from '@spartacus/core'; +import { EntityScopedLoaderActions } from '../../../state/utils/scoped-loader/entity-scoped-loader.actions'; +import { PRODUCT_SEARCH_RESULTS_BY_CODES_ENTITY } from '../product-state'; +import * as fromProductSearchByCode from './product-search-by-code.action'; describe('ProductSearchLoadByCode Actions', () => { describe('ProductSearchLoadByCode', () => { @@ -62,6 +62,7 @@ describe('ProductSearchLoadByCode Actions', () => { payload.scope, payload.error ), + error: payload.error, }); }); }); diff --git a/projects/core/src/product/store/actions/product-search-by-code.action.ts b/projects/core/src/product/store/actions/product-search-by-code.action.ts index c34310c336a..763f05145bf 100644 --- a/projects/core/src/product/store/actions/product-search-by-code.action.ts +++ b/projects/core/src/product/store/actions/product-search-by-code.action.ts @@ -4,6 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ +import { ErrorAction } from '../../../error-handling'; import { Product } from '../../../model/product.model'; import { StateUtils } from '../../../state/utils'; import { EntityScopedLoaderActions } from '../../../state/utils/scoped-loader/entity-scoped-loader.actions'; @@ -42,7 +43,10 @@ export class ProductSearchLoadByCodeSuccess extends EntityScopedLoaderActions.En } } -export class ProductSearchLoadByCodeFail extends EntityScopedLoaderActions.EntityScopedFailAction { +export class ProductSearchLoadByCodeFail + extends EntityScopedLoaderActions.EntityScopedFailAction + implements ErrorAction +{ readonly type = PRODUCT_SEARCH_LOAD_BY_CODE_FAIL; constructor(payload: { code: string; scope: string; error: any }) { super( diff --git a/projects/core/src/product/store/actions/product-search.action.spec.ts b/projects/core/src/product/store/actions/product-search.action.spec.ts index 5e0424ba973..f5ecf93498a 100644 --- a/projects/core/src/product/store/actions/product-search.action.spec.ts +++ b/projects/core/src/product/store/actions/product-search.action.spec.ts @@ -39,12 +39,13 @@ describe('Product Search Actions', () => { describe('SearchProductsFail', () => { it('should create an action', () => { - const payload: ErrorModel = { message: 'Load Error' }; - const action = new fromProductSearch.SearchProductsFail(payload); + const error: ErrorModel = { message: 'Load Error' }; + const action = new fromProductSearch.SearchProductsFail(error); expect({ ...action }).toEqual({ type: fromProductSearch.SEARCH_PRODUCTS_FAIL, - payload, + payload: error, + error, auxiliary: undefined, }); }); @@ -94,12 +95,13 @@ describe('Product Search Actions', () => { describe('SearchProductSuggestionsFail', () => { it('should create an action', () => { - const payload: ErrorModel = { message: 'Load Error' }; - const action = new fromProductSearch.GetProductSuggestionsFail(payload); + const error: ErrorModel = { message: 'Load Error' }; + const action = new fromProductSearch.GetProductSuggestionsFail(error); expect({ ...action }).toEqual({ type: fromProductSearch.GET_PRODUCT_SUGGESTIONS_FAIL, - payload, + payload: error, + error, }); }); }); diff --git a/projects/core/src/product/store/actions/product-search.action.ts b/projects/core/src/product/store/actions/product-search.action.ts index bd2f269dd40..5c1dc8b0c30 100644 --- a/projects/core/src/product/store/actions/product-search.action.ts +++ b/projects/core/src/product/store/actions/product-search.action.ts @@ -5,7 +5,7 @@ */ import { Action } from '@ngrx/store'; -import { ErrorModel } from '../../../model/misc.model'; +import { ErrorAction } from '../../../error-handling'; import { ClearSearch, ProductSearchPage, @@ -32,12 +32,16 @@ export class SearchProducts implements Action { ) {} } -export class SearchProductsFail implements Action { +export class SearchProductsFail implements ErrorAction { readonly type = SEARCH_PRODUCTS_FAIL; + public error: any; + constructor( - public payload: ErrorModel | undefined, + public payload: any, public auxiliary?: boolean - ) {} + ) { + this.error = payload; + } } export class SearchProductsSuccess implements Action { @@ -58,9 +62,12 @@ export class GetProductSuggestionsSuccess implements Action { constructor(public payload: Suggestion[]) {} } -export class GetProductSuggestionsFail implements Action { +export class GetProductSuggestionsFail implements ErrorAction { + public error: any; readonly type = GET_PRODUCT_SUGGESTIONS_FAIL; - constructor(public payload: ErrorModel | undefined) {} + constructor(public payload: any) { + this.error = payload; + } } export class ClearProductSearchResult implements Action { diff --git a/projects/core/src/product/store/actions/product.action.spec.ts b/projects/core/src/product/store/actions/product.action.spec.ts index 2b2d2326e4f..66a0c72fa20 100644 --- a/projects/core/src/product/store/actions/product.action.spec.ts +++ b/projects/core/src/product/store/actions/product.action.spec.ts @@ -29,6 +29,7 @@ describe('Product Actions', () => { const action = new fromProduct.LoadProductFail(productCode, payload); expect({ ...action }).toEqual({ + error: payload, type: fromProduct.LOAD_PRODUCT_FAIL, payload, meta: EntityScopedLoaderActions.entityScopedFailMeta( diff --git a/projects/core/src/product/store/actions/product.action.ts b/projects/core/src/product/store/actions/product.action.ts index 772193e6410..a9b7afeb885 100644 --- a/projects/core/src/product/store/actions/product.action.ts +++ b/projects/core/src/product/store/actions/product.action.ts @@ -5,6 +5,7 @@ */ import { Action } from '@ngrx/store'; +import { ErrorAction } from '../../../error-handling'; import { Product } from '../../../model/product.model'; import { EntityLoaderMeta } from '../../../state/utils/entity-loader/entity-loader.action'; import { EntityScopedLoaderActions } from '../../../state/utils/scoped-loader/entity-scoped-loader.actions'; @@ -27,6 +28,7 @@ export interface EntityScopedLoaderAction extends Action { export class LoadProduct extends EntityScopedLoaderActions.EntityScopedLoadAction { readonly type = LOAD_PRODUCT; + constructor( public payload: string, scope = '' @@ -35,8 +37,12 @@ export class LoadProduct extends EntityScopedLoaderActions.EntityScopedLoadActio } } -export class LoadProductFail extends EntityScopedLoaderActions.EntityScopedFailAction { +export class LoadProductFail + extends EntityScopedLoaderActions.EntityScopedFailAction + implements ErrorAction +{ readonly type = LOAD_PRODUCT_FAIL; + constructor( productCode: string, public payload: any, @@ -48,6 +54,7 @@ export class LoadProductFail extends EntityScopedLoaderActions.EntityScopedFailA export class LoadProductSuccess extends EntityScopedLoaderActions.EntityScopedSuccessAction { readonly type = LOAD_PRODUCT_SUCCESS; + constructor( public payload: Product, scope = '' @@ -58,6 +65,7 @@ export class LoadProductSuccess extends EntityScopedLoaderActions.EntityScopedSu export class ClearProductPrice extends EntityScopedLoaderActions.EntityScopedResetAction { readonly type = CLEAR_PRODUCT_PRICE; + constructor() { super(PRODUCT_DETAIL_ENTITY, undefined, ProductScope.PRICE); } diff --git a/projects/core/src/product/store/effects/product-references.effect.ts b/projects/core/src/product/store/effects/product-references.effect.ts index 3e15b768e97..0b214fe9ba7 100644 --- a/projects/core/src/product/store/effects/product-references.effect.ts +++ b/projects/core/src/product/store/effects/product-references.effect.ts @@ -10,8 +10,8 @@ import { Observable, of } from 'rxjs'; import { catchError, map, mergeMap } from 'rxjs/operators'; import { ProductReferencesConnector } from '../../connectors/references/product-references.connector'; import { ProductActions } from '../actions/index'; -import { normalizeHttpError } from '../../../util/normalize-http-error'; import { LoggerService } from '../../../logger'; +import { tryNormalizeHttpError } from '../../../util/try-normalize-http-error'; @Injectable() export class ProductReferencesEffects { @@ -36,7 +36,7 @@ export class ProductReferencesEffects { catchError((error) => of( new ProductActions.LoadProductReferencesFail( - normalizeHttpError(error, this.logger) + tryNormalizeHttpError(error, this.logger) ) ) ) diff --git a/projects/core/src/product/store/effects/product-reviews.effect.ts b/projects/core/src/product/store/effects/product-reviews.effect.ts index eb5f62be9f4..c58d175714d 100644 --- a/projects/core/src/product/store/effects/product-reviews.effect.ts +++ b/projects/core/src/product/store/effects/product-reviews.effect.ts @@ -14,8 +14,8 @@ import { GlobalMessageService, GlobalMessageType, } from '../../../global-message/index'; -import { normalizeHttpError } from '../../../util/normalize-http-error'; import { LoggerService } from '../../../logger'; +import { tryNormalizeHttpError } from '../../../util/try-normalize-http-error'; @Injectable() export class ProductReviewsEffects { @@ -38,7 +38,7 @@ export class ProductReviewsEffects { catchError((error) => of( new ProductActions.LoadProductReviewsFail( - normalizeHttpError(error, this.logger) + tryNormalizeHttpError(error, this.logger) ) ) ) @@ -66,7 +66,7 @@ export class ProductReviewsEffects { catchError((error) => of( new ProductActions.PostProductReviewFail( - normalizeHttpError(error, this.logger) + tryNormalizeHttpError(error, this.logger) ) ) ) diff --git a/projects/core/src/product/store/effects/product-search.effect.ts b/projects/core/src/product/store/effects/product-search.effect.ts index e273c8e2b6e..eb74099484b 100644 --- a/projects/core/src/product/store/effects/product-search.effect.ts +++ b/projects/core/src/product/store/effects/product-search.effect.ts @@ -4,12 +4,12 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { Injectable, inject } from '@angular/core'; +import { inject, Injectable } from '@angular/core'; import { Actions, createEffect, ofType } from '@ngrx/effects'; import { Observable, of } from 'rxjs'; import { catchError, groupBy, map, mergeMap, switchMap } from 'rxjs/operators'; import { LoggerService } from '../../../logger'; -import { normalizeHttpError } from '../../../util/normalize-http-error'; +import { tryNormalizeHttpError } from '../../../util/try-normalize-http-error'; import { ProductSearchConnector } from '../../connectors/search/product-search.connector'; import { ProductActions } from '../actions/index'; @@ -38,7 +38,7 @@ export class ProductsSearchEffects { catchError((error) => of( new ProductActions.SearchProductsFail( - normalizeHttpError(error, this.logger), + tryNormalizeHttpError(error, this.logger), action.auxiliary ) ) @@ -72,7 +72,7 @@ export class ProductsSearchEffects { catchError((error) => of( new ProductActions.GetProductSuggestionsFail( - normalizeHttpError(error, this.logger) + tryNormalizeHttpError(error, this.logger) ) ) ) diff --git a/projects/core/src/product/store/effects/product.effect.ts b/projects/core/src/product/store/effects/product.effect.ts index 313a9958498..0fd7b79cd30 100644 --- a/projects/core/src/product/store/effects/product.effect.ts +++ b/projects/core/src/product/store/effects/product.effect.ts @@ -12,7 +12,7 @@ import { catchError, map, mergeMap } from 'rxjs/operators'; import { AuthActions } from '../../../auth/user-auth/store/actions'; import { LoggerService } from '../../../logger'; import { SiteContextActions } from '../../../site-context/store/actions/index'; -import { normalizeHttpError } from '../../../util/normalize-http-error'; +import { tryNormalizeHttpError } from '../../../util/try-normalize-http-error'; import { bufferDebounceTime } from '../../../util/rxjs/buffer-debounce-time'; import { withdrawOn } from '../../../util/rxjs/withdraw-on'; import { ProductConnector } from '../../connectors/product/product.connector'; @@ -74,7 +74,7 @@ export class ProductEffects { return of( new ProductActions.LoadProductFail( productLoad.code, - normalizeHttpError(error, this.logger), + tryNormalizeHttpError(error, this.logger), productLoad.scope ) ); @@ -83,7 +83,7 @@ export class ProductEffects { of( new ProductActions.LoadProductFail( productLoad.code, - 'Scoped product data does not exist', + new Error('Scoped product data does not exist'), productLoad.scope ) ) diff --git a/projects/core/src/site-context/store/actions/base-site.action.spec.ts b/projects/core/src/site-context/store/actions/base-site.action.spec.ts index 89077af6e33..bd037b4076a 100644 --- a/projects/core/src/site-context/store/actions/base-site.action.spec.ts +++ b/projects/core/src/site-context/store/actions/base-site.action.spec.ts @@ -14,12 +14,13 @@ describe('BaseSite Actions', () => { describe('LoadBaseSiteFail', () => { it('should create an action', () => { - const payload = { message: 'Load Error' }; - const action = new SiteContextActions.LoadBaseSiteFail(payload); + const error = { message: 'Load Error' }; + const action = new SiteContextActions.LoadBaseSiteFail(error); expect({ ...action }).toEqual({ type: SiteContextActions.LOAD_BASE_SITE_FAIL, - payload, + payload: error, + error, }); }); }); @@ -53,12 +54,13 @@ describe('BaseSite Actions', () => { describe('LoadBaseSitesFail', () => { it('should create an action', () => { - const payload = { message: 'Load Error' }; - const action = new SiteContextActions.LoadBaseSitesFail(payload); + const error = { message: 'Load Error' }; + const action = new SiteContextActions.LoadBaseSitesFail(error); expect({ ...action }).toEqual({ type: SiteContextActions.LOAD_BASE_SITES_FAIL, - payload, + payload: error, + error, }); }); }); diff --git a/projects/core/src/site-context/store/actions/base-site.action.ts b/projects/core/src/site-context/store/actions/base-site.action.ts index ac10dc1f82d..593437e4633 100644 --- a/projects/core/src/site-context/store/actions/base-site.action.ts +++ b/projects/core/src/site-context/store/actions/base-site.action.ts @@ -5,6 +5,7 @@ */ import { Action } from '@ngrx/store'; +import { ErrorAction } from '../../../error-handling'; import { BaseSite } from '../../../model/misc.model'; export const LOAD_BASE_SITE = '[Site-context] Load BaseSite'; @@ -22,13 +23,18 @@ export class LoadBaseSite implements Action { readonly type = LOAD_BASE_SITE; } -export class LoadBaseSiteFail implements Action { +export class LoadBaseSiteFail implements ErrorAction { + public error: any; readonly type = LOAD_BASE_SITE_FAIL; - constructor(public payload: any) {} + + constructor(public payload: any) { + this.error = payload; + } } export class LoadBaseSiteSuccess implements Action { readonly type = LOAD_BASE_SITE_SUCCESS; + constructor(public payload: BaseSite) {} } @@ -36,18 +42,24 @@ export class LoadBaseSites implements Action { readonly type = LOAD_BASE_SITES; } -export class LoadBaseSitesFail implements Action { +export class LoadBaseSitesFail implements ErrorAction { + public error: any; readonly type = LOAD_BASE_SITES_FAIL; - constructor(public payload: any) {} + + constructor(public payload: any) { + this.error = payload; + } } export class LoadBaseSitesSuccess implements Action { readonly type = LOAD_BASE_SITES_SUCCESS; + constructor(public payload: BaseSite[]) {} } export class SetActiveBaseSite implements Action { readonly type = SET_ACTIVE_BASE_SITE; + constructor(public payload: string) {} } diff --git a/projects/core/src/site-context/store/actions/currencies.action.spec.ts b/projects/core/src/site-context/store/actions/currencies.action.spec.ts index 133b05631f7..a88b39e8a3e 100644 --- a/projects/core/src/site-context/store/actions/currencies.action.spec.ts +++ b/projects/core/src/site-context/store/actions/currencies.action.spec.ts @@ -14,12 +14,13 @@ describe('Currencies Actions', () => { describe('LoadCurrenciesFail', () => { it('should create an action', () => { - const payload = { message: 'Load Error' }; - const action = new SiteContextActions.LoadCurrenciesFail(payload); + const error = { message: 'Load Error' }; + const action = new SiteContextActions.LoadCurrenciesFail(error); expect({ ...action }).toEqual({ type: SiteContextActions.LOAD_CURRENCIES_FAIL, - payload, + payload: error, + error, }); }); }); diff --git a/projects/core/src/site-context/store/actions/currencies.action.ts b/projects/core/src/site-context/store/actions/currencies.action.ts index 1cf47245ffe..725d8eaf84a 100644 --- a/projects/core/src/site-context/store/actions/currencies.action.ts +++ b/projects/core/src/site-context/store/actions/currencies.action.ts @@ -5,6 +5,7 @@ */ import { Action } from '@ngrx/store'; +import { ErrorAction } from '../../../error-handling'; import { Currency } from '../../../model/misc.model'; export const LOAD_CURRENCIES = '[Site-context] Load Currencies'; @@ -17,9 +18,12 @@ export class LoadCurrencies implements Action { readonly type = LOAD_CURRENCIES; } -export class LoadCurrenciesFail implements Action { +export class LoadCurrenciesFail implements ErrorAction { + public error: any; readonly type = LOAD_CURRENCIES_FAIL; - constructor(public payload: any) {} + constructor(public payload: any) { + this.error = payload; + } } export class LoadCurrenciesSuccess implements Action { diff --git a/projects/core/src/site-context/store/actions/languages.action.spec.ts b/projects/core/src/site-context/store/actions/languages.action.spec.ts index e86a0cac248..f7b63f7cc63 100644 --- a/projects/core/src/site-context/store/actions/languages.action.spec.ts +++ b/projects/core/src/site-context/store/actions/languages.action.spec.ts @@ -14,12 +14,13 @@ describe('Languages Actions', () => { describe('LoadLanguagesFail', () => { it('should create an action', () => { - const payload = { message: 'Load Error' }; - const action = new SiteContextActions.LoadLanguagesFail(payload); + const error = { message: 'Load Error' }; + const action = new SiteContextActions.LoadLanguagesFail(error); expect({ ...action }).toEqual({ type: SiteContextActions.LOAD_LANGUAGES_FAIL, - payload, + payload: error, + error, }); }); }); diff --git a/projects/core/src/site-context/store/actions/languages.action.ts b/projects/core/src/site-context/store/actions/languages.action.ts index 90db4b2695b..2bdca3b571f 100644 --- a/projects/core/src/site-context/store/actions/languages.action.ts +++ b/projects/core/src/site-context/store/actions/languages.action.ts @@ -5,6 +5,7 @@ */ import { Action } from '@ngrx/store'; +import { ErrorAction } from '../../../error-handling'; import { Language } from '../../../model/misc.model'; export const LOAD_LANGUAGES = '[Site-context] Load Languages'; @@ -17,23 +18,30 @@ export class LoadLanguages implements Action { readonly type = LOAD_LANGUAGES; } -export class LoadLanguagesFail implements Action { +export class LoadLanguagesFail implements ErrorAction { + public error: any; readonly type = LOAD_LANGUAGES_FAIL; - constructor(public payload: any) {} + + constructor(public payload: any) { + this.error = payload; + } } export class LoadLanguagesSuccess implements Action { readonly type = LOAD_LANGUAGES_SUCCESS; + constructor(public payload: Language[]) {} } export class SetActiveLanguage implements Action { readonly type = SET_ACTIVE_LANGUAGE; + constructor(public payload: string) {} } export class LanguageChange implements Action { readonly type = LANGUAGE_CHANGE; + constructor( public payload: { previous: string | null; current: string | null } ) {} diff --git a/projects/core/src/site-context/store/effects/base-site.effect.ts b/projects/core/src/site-context/store/effects/base-site.effect.ts index ac3c6a03ca5..e89d0384f29 100644 --- a/projects/core/src/site-context/store/effects/base-site.effect.ts +++ b/projects/core/src/site-context/store/effects/base-site.effect.ts @@ -9,7 +9,7 @@ import { Actions, createEffect, ofType } from '@ngrx/effects'; import { Observable, of } from 'rxjs'; import { catchError, exhaustMap, map } from 'rxjs/operators'; import { LoggerService } from '../../../logger'; -import { normalizeHttpError } from '../../../util/normalize-http-error'; +import { tryNormalizeHttpError } from '../../../util/try-normalize-http-error'; import { SiteConnector } from '../../connectors/site.connector'; import { SiteContextActions } from '../actions/index'; @@ -34,7 +34,7 @@ export class BaseSiteEffects { catchError((error) => of( new SiteContextActions.LoadBaseSiteFail( - normalizeHttpError(error, this.logger) + tryNormalizeHttpError(error, this.logger) ) ) ) @@ -58,7 +58,7 @@ export class BaseSiteEffects { catchError((error) => of( new SiteContextActions.LoadBaseSitesFail( - normalizeHttpError(error, this.logger) + tryNormalizeHttpError(error, this.logger) ) ) ) diff --git a/projects/core/src/site-context/store/effects/currencies.effect.ts b/projects/core/src/site-context/store/effects/currencies.effect.ts index b3f96caff35..5cd2403482b 100644 --- a/projects/core/src/site-context/store/effects/currencies.effect.ts +++ b/projects/core/src/site-context/store/effects/currencies.effect.ts @@ -16,7 +16,7 @@ import { map, } from 'rxjs/operators'; import { LoggerService } from '../../../logger'; -import { normalizeHttpError } from '../../../util/normalize-http-error'; +import { tryNormalizeHttpError } from '../../../util/try-normalize-http-error'; import { SiteConnector } from '../../connectors/site.connector'; import { SiteContextActions } from '../actions/index'; import { getActiveCurrency } from '../selectors/currencies.selectors'; @@ -41,7 +41,7 @@ export class CurrenciesEffects { catchError((error) => of( new SiteContextActions.LoadCurrenciesFail( - normalizeHttpError(error, this.logger) + tryNormalizeHttpError(error, this.logger) ) ) ) diff --git a/projects/core/src/site-context/store/effects/languages.effect.ts b/projects/core/src/site-context/store/effects/languages.effect.ts index 04466c514b8..2161543abcf 100644 --- a/projects/core/src/site-context/store/effects/languages.effect.ts +++ b/projects/core/src/site-context/store/effects/languages.effect.ts @@ -16,7 +16,7 @@ import { map, } from 'rxjs/operators'; import { LoggerService } from '../../../logger'; -import { normalizeHttpError } from '../../../util/normalize-http-error'; +import { tryNormalizeHttpError } from '../../../util/try-normalize-http-error'; import { SiteConnector } from '../../connectors/site.connector'; import { SiteContextActions } from '../actions/index'; import { getActiveLanguage } from '../selectors/languages.selectors'; @@ -41,7 +41,7 @@ export class LanguagesEffects { catchError((error) => of( new SiteContextActions.LoadLanguagesFail( - normalizeHttpError(error, this.logger) + tryNormalizeHttpError(error, this.logger) ) ) ) diff --git a/projects/core/src/state/utils/entity-loader/entity-loader.action.spec.ts b/projects/core/src/state/utils/entity-loader/entity-loader.action.spec.ts index f9015135036..7e6da9b51a2 100644 --- a/projects/core/src/state/utils/entity-loader/entity-loader.action.spec.ts +++ b/projects/core/src/state/utils/entity-loader/entity-loader.action.spec.ts @@ -30,14 +30,16 @@ describe('EntityLoader Actions', () => { describe('LoaderFailAction', () => { it('should create an action', () => { + const error = new Error('error'); const action = new EntityFailAction( TEST_ENTITY_TYPE, TEST_ENTITY_ID, - 'error' + error ); expect({ ...action }).toEqual({ type: ENTITY_FAIL_ACTION, - meta: entityFailMeta(TEST_ENTITY_TYPE, TEST_ENTITY_ID, 'error'), + meta: entityFailMeta(TEST_ENTITY_TYPE, TEST_ENTITY_ID, error), + error, }); }); }); diff --git a/projects/core/src/state/utils/entity-loader/entity-loader.action.ts b/projects/core/src/state/utils/entity-loader/entity-loader.action.ts index cb709ca51d3..7d3badac45c 100644 --- a/projects/core/src/state/utils/entity-loader/entity-loader.action.ts +++ b/projects/core/src/state/utils/entity-loader/entity-loader.action.ts @@ -5,6 +5,7 @@ */ import { Action } from '@ngrx/store'; +import { ErrorAction } from '../../../error-handling'; import { EntityId, entityMeta, EntityMeta } from '../entity/entity.action'; import { failMeta, @@ -36,6 +37,21 @@ export function entityLoadMeta( }; } +export function entityFailMeta( + entityType: string, + id: EntityId, + // eslint-disable-next-line @typescript-eslint/unified-signatures + error: any +): EntityLoaderMeta; +/** + * @deprecated Please pass the argument `error`. + * It will become mandatory along with removing + * the feature toggle `ssrStrictErrorHandlingForHttpAndNgrx`. + */ +export function entityFailMeta( + entityType: string, + id: EntityId +): EntityLoaderMeta; export function entityFailMeta( entityType: string, id: EntityId, @@ -75,11 +91,22 @@ export class EntityLoadAction implements EntityLoaderAction { } } -export class EntityFailAction implements EntityLoaderAction { +export class EntityFailAction implements EntityLoaderAction, ErrorAction { type = ENTITY_FAIL_ACTION; readonly meta: EntityLoaderMeta; + public error: any; + + // eslint-disable-next-line @typescript-eslint/unified-signatures + constructor(entityType: string, id: EntityId, error: any); + /** + * @deprecated Please pass the argument `error`. + * It will become mandatory along with removing + * the feature toggle `ssrStrictErrorHandlingForHttpAndNgrx`. + */ + constructor(entityType: string, id: EntityId); constructor(entityType: string, id: EntityId, error?: any) { this.meta = entityFailMeta(entityType, id, error); + this.error = error; } } diff --git a/projects/core/src/state/utils/entity-loader/entity-loader.reducer.spec.ts b/projects/core/src/state/utils/entity-loader/entity-loader.reducer.spec.ts index 2d7c7f3af34..6018b4dad43 100644 --- a/projects/core/src/state/utils/entity-loader/entity-loader.reducer.spec.ts +++ b/projects/core/src/state/utils/entity-loader/entity-loader.reducer.spec.ts @@ -44,7 +44,11 @@ describe('EntityLoader reducer', () => { describe('FAIL ACTION', () => { it('should set load state', () => { - const action = new EntityFailAction(TEST_ENTITY_TYPE, TEST_ENTITY_ID); + const action = new EntityFailAction( + TEST_ENTITY_TYPE, + TEST_ENTITY_ID, + new Error() + ); const state = entityLoaderReducer(TEST_ENTITY_TYPE)(undefined, action); const expectedState = { entities: { @@ -148,7 +152,11 @@ describe('EntityLoader reducer', () => { describe('FAIL ACTION', () => { it('should set load state', () => { - const action = new EntityFailAction(TEST_ENTITY_TYPE, TEST_ENTITIES_ID); + const action = new EntityFailAction( + TEST_ENTITY_TYPE, + TEST_ENTITIES_ID, + new Error() + ); const state = entityLoaderReducer(TEST_ENTITY_TYPE)(undefined, action); const expectedState = { entities: { diff --git a/projects/core/src/state/utils/loader/loader.action.spec.ts b/projects/core/src/state/utils/loader/loader.action.spec.ts index 17d9af5b185..d4bc73f68b9 100644 --- a/projects/core/src/state/utils/loader/loader.action.spec.ts +++ b/projects/core/src/state/utils/loader/loader.action.spec.ts @@ -29,10 +29,12 @@ describe('Loader Actions', () => { describe('LoaderFailAction', () => { it('should create an action', () => { - const action = new LoaderFailAction(TEST_ENTITY_TYPE, 'error'); + const error = new Error('error'); + const action = new LoaderFailAction(TEST_ENTITY_TYPE, error); expect({ ...action }).toEqual({ type: LOADER_FAIL_ACTION, - meta: failMeta(TEST_ENTITY_TYPE, 'error'), + meta: failMeta(TEST_ENTITY_TYPE, error), + error, }); }); }); diff --git a/projects/core/src/state/utils/loader/loader.action.ts b/projects/core/src/state/utils/loader/loader.action.ts index 74a2785ae49..e81a35531e3 100644 --- a/projects/core/src/state/utils/loader/loader.action.ts +++ b/projects/core/src/state/utils/loader/loader.action.ts @@ -5,6 +5,7 @@ */ import { Action } from '@ngrx/store'; +import { ErrorAction } from '../../../error-handling'; export const LOADER_LOAD_ACTION = '[LOADER] LOAD'; export const LOADER_FAIL_ACTION = '[LOADER] FAIL'; @@ -36,6 +37,17 @@ export function loadMeta(entityType: string): LoaderMeta { }; } +export function failMeta( + entityType: string, + // eslint-disable-next-line @typescript-eslint/unified-signatures + error: any +): LoaderMeta; +/** + * @deprecated Please pass the argument `error`. + * It will become mandatory along with removing + * the feature toggle `ssrStrictErrorHandlingForHttpAndNgrx`. + */ +export function failMeta(entityType: string): LoaderMeta; export function failMeta(entityType: string, error?: any): LoaderMeta { return { entityType: entityType, @@ -60,25 +72,39 @@ export function resetMeta(entityType: string): LoaderMeta { loader: {}, }; } + export class LoaderLoadAction implements LoaderAction { type = LOADER_LOAD_ACTION; readonly meta: LoaderMeta; + constructor(entityType: string) { this.meta = loadMeta(entityType); } } -export class LoaderFailAction implements LoaderAction { +export class LoaderFailAction implements LoaderAction, ErrorAction { type = LOADER_FAIL_ACTION; + public error: any; readonly meta: LoaderMeta; + + // eslint-disable-next-line @typescript-eslint/unified-signatures + constructor(entityType: string, error: any); + /** + * @deprecated Please pass the argument `error`. + * It will become mandatory along with removing + * the feature toggle `ssrStrictErrorHandlingForHttpAndNgrx`. + */ + constructor(entityType: string); constructor(entityType: string, error?: any) { this.meta = failMeta(entityType, error); + this.error = error; } } export class LoaderSuccessAction implements LoaderAction { type = LOADER_SUCCESS_ACTION; readonly meta: LoaderMeta; + constructor(entityType: string) { this.meta = successMeta(entityType); } @@ -87,6 +113,7 @@ export class LoaderSuccessAction implements LoaderAction { export class LoaderResetAction implements LoaderAction { type = LOADER_RESET_ACTION; readonly meta: LoaderMeta; + constructor(entityType: string) { this.meta = resetMeta(entityType); } diff --git a/projects/core/src/state/utils/loader/loader.reducer.spec.ts b/projects/core/src/state/utils/loader/loader.reducer.spec.ts index 1205053187c..ef4ccf84ce5 100644 --- a/projects/core/src/state/utils/loader/loader.reducer.spec.ts +++ b/projects/core/src/state/utils/loader/loader.reducer.spec.ts @@ -44,7 +44,8 @@ describe('Loader reducer', () => { describe('FAIL ACTION', () => { it('should set load state', () => { - const action = new LoaderFailAction(TEST_ENTITY_TYPE); + const error = new Error('error'); + const action = new LoaderFailAction(TEST_ENTITY_TYPE, error); const state = loaderReducer(TEST_ENTITY_TYPE)(undefined, action); const expectedState = { loading: false, diff --git a/projects/core/src/state/utils/scoped-loader/entity-scoped-loader.actions.spec.ts b/projects/core/src/state/utils/scoped-loader/entity-scoped-loader.actions.spec.ts index 1981e68e7e1..985ac6f1537 100644 --- a/projects/core/src/state/utils/scoped-loader/entity-scoped-loader.actions.spec.ts +++ b/projects/core/src/state/utils/scoped-loader/entity-scoped-loader.actions.spec.ts @@ -1,10 +1,10 @@ -import { EntityScopedLoaderActions } from './entity-scoped-loader.actions'; import { ENTITY_FAIL_ACTION, ENTITY_LOAD_ACTION, ENTITY_RESET_ACTION, ENTITY_SUCCESS_ACTION, } from '../entity-loader/entity-loader.action'; +import { EntityScopedLoaderActions } from './entity-scoped-loader.actions'; describe('EntityScopedLoaderActions', () => { const TEST_ENTITY_TYPE = 'test'; @@ -32,11 +32,12 @@ describe('EntityScopedLoaderActions', () => { describe('EntityScopedFailAction', () => { it('should create an action', () => { + const error = new Error('error'); const action = new EntityScopedLoaderActions.EntityScopedFailAction( TEST_ENTITY_TYPE, TEST_ENTITY_ID, SCOPE, - 'error' + error ); expect({ ...action }).toEqual({ type: ENTITY_FAIL_ACTION, @@ -44,8 +45,9 @@ describe('EntityScopedLoaderActions', () => { TEST_ENTITY_TYPE, TEST_ENTITY_ID, SCOPE, - 'error' + error ), + error, }); }); }); diff --git a/projects/core/src/state/utils/scoped-loader/entity-scoped-loader.actions.ts b/projects/core/src/state/utils/scoped-loader/entity-scoped-loader.actions.ts index b7d83f33c66..80631ea1707 100644 --- a/projects/core/src/state/utils/scoped-loader/entity-scoped-loader.actions.ts +++ b/projects/core/src/state/utils/scoped-loader/entity-scoped-loader.actions.ts @@ -5,16 +5,17 @@ */ import { Action } from '@ngrx/store'; +import { ErrorAction } from '../../../error-handling'; import { - entityFailMeta, - EntityLoaderMeta, - entityLoadMeta, - entityResetMeta, - entitySuccessMeta, ENTITY_FAIL_ACTION, ENTITY_LOAD_ACTION, ENTITY_RESET_ACTION, ENTITY_SUCCESS_ACTION, + EntityLoaderMeta, + entityFailMeta, + entityLoadMeta, + entityResetMeta, + entitySuccessMeta, } from '../entity-loader/entity-loader.action'; export namespace EntityScopedLoaderActions { @@ -38,6 +39,33 @@ export namespace EntityScopedLoaderActions { }; } + export function entityScopedFailMeta( + entityType: string, + id: string | string[], + scope: string | undefined, + // eslint-disable-next-line @typescript-eslint/unified-signatures + error: any + ): EntityScopedLoaderMeta; + /** + * @deprecated Please pass the argument `error`. + * It will become mandatory along with removing + * the feature toggle `ssrStrictErrorHandlingForHttpAndNgrx`. + */ + export function entityScopedFailMeta( + entityType: string, + id: string | string[], + // eslint-disable-next-line @typescript-eslint/unified-signatures + scope: string | undefined + ): EntityScopedLoaderMeta; + /** + * @deprecated Please pass the argument `scope` and `error` + * They will become mandatory along with removing + * the feature toggle `ssrStrictErrorHandlingForHttpAndNgrx`. + */ + export function entityScopedFailMeta( + entityType: string, + id: string | string[] + ): EntityScopedLoaderMeta; export function entityScopedFailMeta( entityType: string, id: string | string[], @@ -75,14 +103,44 @@ export namespace EntityScopedLoaderActions { export class EntityScopedLoadAction implements EntityScopedLoaderAction { type = ENTITY_LOAD_ACTION; readonly meta: EntityScopedLoaderMeta; + constructor(entityType: string, id: string | string[], scope?: string) { this.meta = entityScopedLoadMeta(entityType, id, scope); } } - export class EntityScopedFailAction implements EntityScopedLoaderAction { + export class EntityScopedFailAction + implements EntityScopedLoaderAction, ErrorAction + { type = ENTITY_FAIL_ACTION; + public error: any; readonly meta: EntityScopedLoaderMeta; + + constructor( + entityType: string, + id: string | string[], + scope: string | undefined, + // eslint-disable-next-line @typescript-eslint/unified-signatures + error: any + ); + /** + * @deprecated Please pass the argument `error`. + * It will become mandatory along with removing + * the feature toggle `ssrStrictErrorHandlingForHttpAndNgrx`. + */ + // eslint-disable-next-line @typescript-eslint/unified-signatures + constructor( + entityType: string, + id: string | string[], + // eslint-disable-next-line @typescript-eslint/unified-signatures + scope: string | undefined + ); + /** + * @deprecated Please pass the argument `scope` and `error`. + * They will become mandatory along with removing + * the feature toggle `ssrStrictErrorHandlingForHttpAndNgrx`. + */ + constructor(entityType: string, id: string | string[]); constructor( entityType: string, id: string | string[], @@ -90,12 +148,14 @@ export namespace EntityScopedLoaderActions { error?: any ) { this.meta = entityScopedFailMeta(entityType, id, scope, error); + this.error = error; } } export class EntityScopedSuccessAction implements EntityScopedLoaderAction { type = ENTITY_SUCCESS_ACTION; readonly meta: EntityScopedLoaderMeta; + constructor( entityType: string, id: string | string[], @@ -109,6 +169,7 @@ export namespace EntityScopedLoaderActions { export class EntityScopedResetAction implements EntityScopedLoaderAction { type = ENTITY_RESET_ACTION; readonly meta: EntityScopedLoaderMeta; + constructor(entityType: string, id?: string | string[], scope?: string) { this.meta = entityScopedResetMeta(entityType, id, scope); } diff --git a/projects/core/src/user/facade/customer-coupon.service.spec.ts b/projects/core/src/user/facade/customer-coupon.service.spec.ts index e25acf02fff..7bcb9b7873a 100644 --- a/projects/core/src/user/facade/customer-coupon.service.spec.ts +++ b/projects/core/src/user/facade/customer-coupon.service.spec.ts @@ -163,7 +163,9 @@ describe('CustomerCouponService', () => { }); it('should getSubscribeCustomerCouponResultError() return the error flag', () => { - store.dispatch(new UserActions.SubscribeCustomerCouponFail('error')); + store.dispatch( + new UserActions.SubscribeCustomerCouponFail(new Error('error')) + ); let result = false; service @@ -214,7 +216,9 @@ describe('CustomerCouponService', () => { }); it('should getUnsubscribeCustomerCouponResultError() return the error flag', () => { - store.dispatch(new UserActions.UnsubscribeCustomerCouponFail('error')); + store.dispatch( + new UserActions.UnsubscribeCustomerCouponFail(new Error('error')) + ); let result = false; service diff --git a/projects/core/src/user/facade/user-consent.service.spec.ts b/projects/core/src/user/facade/user-consent.service.spec.ts index b41f0bd262f..90f4c2c87f7 100644 --- a/projects/core/src/user/facade/user-consent.service.spec.ts +++ b/projects/core/src/user/facade/user-consent.service.spec.ts @@ -188,7 +188,9 @@ describe('UserConsentService', () => { }); describe('getConsentsResultError', () => { it('should return the error flag', () => { - store.dispatch(new UserActions.LoadUserConsentsFail('an error')); + store.dispatch( + new UserActions.LoadUserConsentsFail(new Error('an error')) + ); let result = false; service @@ -391,7 +393,9 @@ describe('UserConsentService', () => { }); describe('getGiveConsentResultError', () => { it('should return the error flag', () => { - store.dispatch(new UserActions.GiveUserConsentFail('an error')); + store.dispatch( + new UserActions.GiveUserConsentFail(new Error('an error')) + ); let result = false; service @@ -457,7 +461,9 @@ describe('UserConsentService', () => { }); describe('getWithdrawConsentResultError', () => { it('should return the error flag', () => { - store.dispatch(new UserActions.WithdrawUserConsentFail('an error')); + store.dispatch( + new UserActions.WithdrawUserConsentFail(new Error('an error')) + ); let result = false; service diff --git a/projects/core/src/user/facade/user-interests.service.spec.ts b/projects/core/src/user/facade/user-interests.service.spec.ts index b08c2d069a2..39e8c19784f 100644 --- a/projects/core/src/user/facade/user-interests.service.spec.ts +++ b/projects/core/src/user/facade/user-interests.service.spec.ts @@ -154,7 +154,7 @@ describe('UserInterestsService', () => { }); it('should be able to get a product interest adding error flag', () => { - store.dispatch(new UserActions.AddProductInterestFail('error')); + store.dispatch(new UserActions.AddProductInterestFail(new Error('error'))); service .getAddProductInterestError() .subscribe((data) => expect(data).toEqual(true)) diff --git a/projects/core/src/user/store/actions/billing-countries.action.spec.ts b/projects/core/src/user/store/actions/billing-countries.action.spec.ts index ce3b574e2c8..c42d6791fb5 100644 --- a/projects/core/src/user/store/actions/billing-countries.action.spec.ts +++ b/projects/core/src/user/store/actions/billing-countries.action.spec.ts @@ -14,12 +14,13 @@ describe('Billing Countries Actions', () => { describe('LoadBillingCountriesFail', () => { it('should create the action', () => { - const sampleError = 'sample error'; - const action = new UserActions.LoadBillingCountriesFail(sampleError); + const error = 'sample error'; + const action = new UserActions.LoadBillingCountriesFail(error); expect({ ...action }).toEqual({ type: UserActions.LOAD_BILLING_COUNTRIES_FAIL, - payload: sampleError, + payload: error, + error, }); }); }); diff --git a/projects/core/src/user/store/actions/billing-countries.action.ts b/projects/core/src/user/store/actions/billing-countries.action.ts index b18521cccbe..b1f8ec923d4 100644 --- a/projects/core/src/user/store/actions/billing-countries.action.ts +++ b/projects/core/src/user/store/actions/billing-countries.action.ts @@ -5,6 +5,7 @@ */ import { Action } from '@ngrx/store'; +import { ErrorAction } from '../../../error-handling'; export const LOAD_BILLING_COUNTRIES = '[User] Load Billing Countries'; export const LOAD_BILLING_COUNTRIES_FAIL = '[User] Load Billing Countries Fail'; @@ -18,9 +19,13 @@ export class LoadBillingCountries implements Action { } } -export class LoadBillingCountriesFail implements Action { +export class LoadBillingCountriesFail implements ErrorAction { readonly type = LOAD_BILLING_COUNTRIES_FAIL; - constructor(public payload: any) {} + public error: any; + + constructor(public payload: any) { + this.error = payload; + } } export class LoadBillingCountriesSuccess implements Action { diff --git a/projects/core/src/user/store/actions/customer-coupon.action.spec.ts b/projects/core/src/user/store/actions/customer-coupon.action.spec.ts index fd54d2c6a00..c934adee2ea 100644 --- a/projects/core/src/user/store/actions/customer-coupon.action.spec.ts +++ b/projects/core/src/user/store/actions/customer-coupon.action.spec.ts @@ -1,23 +1,23 @@ import { - CUSTOMER_COUPONS, - SUBSCRIBE_CUSTOMER_COUPON_PROCESS_ID, - UNSUBSCRIBE_CUSTOMER_COUPON_PROCESS_ID, - CLAIM_CUSTOMER_COUPON_PROCESS_ID, - DISCLAIM_CUSTOMER_COUPON_PROCESS_ID, -} from '../user-state'; + CustomerCoupon, + CustomerCoupon2Customer, + CustomerCouponNotification, + CustomerCouponSearchResult, +} from '../../../model/customer-coupon.model'; +import { StateUtils } from '../../../state/utils/index'; import { - loadMeta, failMeta, - successMeta, + loadMeta, resetMeta, + successMeta, } from '../../../state/utils/loader/loader.action'; -import { StateUtils } from '../../../state/utils/index'; import { - CustomerCoupon, - CustomerCouponSearchResult, - CustomerCouponNotification, - CustomerCoupon2Customer, -} from '../../../model/customer-coupon.model'; + CLAIM_CUSTOMER_COUPON_PROCESS_ID, + CUSTOMER_COUPONS, + DISCLAIM_CUSTOMER_COUPON_PROCESS_ID, + SUBSCRIBE_CUSTOMER_COUPON_PROCESS_ID, + UNSUBSCRIBE_CUSTOMER_COUPON_PROCESS_ID, +} from '../user-state'; import { PROCESS_FEATURE } from '../../../process/store'; @@ -68,6 +68,7 @@ const customerCoupon2Customer: CustomerCoupon2Customer = { coupon: coupon1, customer: {}, }; +const error = new Error('mockError'); describe('Customer Coupon Actions', () => { describe('LoadCustomerCoupons Action', () => { @@ -95,6 +96,7 @@ describe('Customer Coupon Actions', () => { expect({ ...action }).toEqual({ type: UserActions.LOAD_CUSTOMER_COUPONS_FAIL, payload: error, + error, meta: failMeta(CUSTOMER_COUPONS, error), }); }); @@ -145,12 +147,12 @@ describe('Customer Coupon Actions', () => { describe('SubscribeCustomerCouponFail Action', () => { it('should create the action', () => { - const error = 'mockError'; const action = new UserActions.SubscribeCustomerCouponFail(error); expect({ ...action }).toEqual({ type: UserActions.SUBSCRIBE_CUSTOMER_COUPON_FAIL, payload: error, + error, meta: StateUtils.entityFailMeta( PROCESS_FEATURE, SUBSCRIBE_CUSTOMER_COUPON_PROCESS_ID, @@ -211,12 +213,12 @@ describe('Customer Coupon Actions', () => { describe('UnsubscribeCustomerCouponFail Action', () => { it('should create the action', () => { - const error = 'mockError'; const action = new UserActions.UnsubscribeCustomerCouponFail(error); expect({ ...action }).toEqual({ type: UserActions.UNSUBSCRIBE_CUSTOMER_COUPON_FAIL, payload: error, + error, meta: StateUtils.entityFailMeta( PROCESS_FEATURE, UNSUBSCRIBE_CUSTOMER_COUPON_PROCESS_ID, @@ -276,12 +278,12 @@ describe('Customer Coupon Actions', () => { describe('ClaimCustomerCouponFail Action', () => { it('should create the action', () => { - const error = 'mockError'; const action = new UserActions.ClaimCustomerCouponFail(error); expect({ ...action }).toEqual({ type: UserActions.CLAIM_CUSTOMER_COUPON_FAIL, payload: error, + error, meta: StateUtils.entityFailMeta( PROCESS_FEATURE, CLAIM_CUSTOMER_COUPON_PROCESS_ID, diff --git a/projects/core/src/user/store/actions/customer-coupon.action.ts b/projects/core/src/user/store/actions/customer-coupon.action.ts index f2d0295f18f..72acd8593fb 100644 --- a/projects/core/src/user/store/actions/customer-coupon.action.ts +++ b/projects/core/src/user/store/actions/customer-coupon.action.ts @@ -4,6 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ +import { ErrorAction } from '../../../error-handling'; import { CustomerCoupon2Customer, CustomerCouponNotification, @@ -78,7 +79,10 @@ export class LoadCustomerCoupons extends LoaderLoadAction { } } -export class LoadCustomerCouponsFail extends LoaderFailAction { +export class LoadCustomerCouponsFail + extends LoaderFailAction + implements ErrorAction +{ readonly type = LOAD_CUSTOMER_COUPONS_FAIL; constructor(public payload: any) { super(CUSTOMER_COUPONS, payload); @@ -112,7 +116,10 @@ export class SubscribeCustomerCoupon extends EntityLoadAction { } } -export class SubscribeCustomerCouponFail extends EntityFailAction { +export class SubscribeCustomerCouponFail + extends EntityFailAction + implements ErrorAction +{ readonly type = SUBSCRIBE_CUSTOMER_COUPON_FAIL; constructor(public payload: any) { super(PROCESS_FEATURE, SUBSCRIBE_CUSTOMER_COUPON_PROCESS_ID, payload); @@ -145,7 +152,10 @@ export class UnsubscribeCustomerCoupon extends EntityLoadAction { } } -export class UnsubscribeCustomerCouponFail extends EntityFailAction { +export class UnsubscribeCustomerCouponFail + extends EntityFailAction + implements ErrorAction +{ readonly type = UNSUBSCRIBE_CUSTOMER_COUPON_FAIL; constructor(public payload: any) { super(PROCESS_FEATURE, UNSUBSCRIBE_CUSTOMER_COUPON_PROCESS_ID, payload); @@ -197,7 +207,10 @@ export class ResetDisclaimCustomerCoupon extends EntityLoaderResetAction { } } -export class DisclaimCustomerCouponFail extends EntityFailAction { +export class DisclaimCustomerCouponFail + extends EntityFailAction + implements ErrorAction +{ readonly type = DISCLAIM_CUSTOMER_COUPON_FAIL; constructor(public payload: any) { super(PROCESS_FEATURE, DISCLAIM_CUSTOMER_COUPON_PROCESS_ID, payload); @@ -211,7 +224,10 @@ export class DisclaimCustomerCouponSuccess extends EntitySuccessAction { } } -export class ClaimCustomerCouponFail extends EntityFailAction { +export class ClaimCustomerCouponFail + extends EntityFailAction + implements ErrorAction +{ readonly type = CLAIM_CUSTOMER_COUPON_FAIL; constructor(public payload: any) { super(PROCESS_FEATURE, CLAIM_CUSTOMER_COUPON_PROCESS_ID, payload); diff --git a/projects/core/src/user/store/actions/delivery-countries.action.spec.ts b/projects/core/src/user/store/actions/delivery-countries.action.spec.ts index bf5b216d1a7..2b371ee7a1d 100644 --- a/projects/core/src/user/store/actions/delivery-countries.action.spec.ts +++ b/projects/core/src/user/store/actions/delivery-countries.action.spec.ts @@ -13,12 +13,13 @@ describe('Delivery Countries Actions', () => { describe('LoadDeliveryCountriesFail', () => { it('should create the action', () => { - const error = 'anError'; + const error = new Error('anError'); const action = new UserActions.LoadDeliveryCountriesFail(error); expect({ ...action }).toEqual({ type: UserActions.LOAD_DELIVERY_COUNTRIES_FAIL, payload: error, + error, }); }); }); diff --git a/projects/core/src/user/store/actions/delivery-countries.action.ts b/projects/core/src/user/store/actions/delivery-countries.action.ts index 8bb34ae8c2d..778f9465ab9 100644 --- a/projects/core/src/user/store/actions/delivery-countries.action.ts +++ b/projects/core/src/user/store/actions/delivery-countries.action.ts @@ -5,6 +5,7 @@ */ import { Action } from '@ngrx/store'; +import { ErrorAction } from '../../../error-handling'; import { Country } from '../../../model/address.model'; export const LOAD_DELIVERY_COUNTRIES = '[User] Load Delivery Countries'; @@ -15,18 +16,24 @@ export const LOAD_DELIVERY_COUNTRIES_SUCCESS = export class LoadDeliveryCountries implements Action { readonly type = LOAD_DELIVERY_COUNTRIES; + constructor() { // Intentional empty constructor } } -export class LoadDeliveryCountriesFail implements Action { +export class LoadDeliveryCountriesFail implements ErrorAction { + public error: any; readonly type = LOAD_DELIVERY_COUNTRIES_FAIL; - constructor(public payload: any) {} + + constructor(public payload: any) { + this.error = payload; + } } export class LoadDeliveryCountriesSuccess implements Action { readonly type = LOAD_DELIVERY_COUNTRIES_SUCCESS; + constructor(public payload: Country[]) {} } diff --git a/projects/core/src/user/store/actions/notification-preference.action.spec.ts b/projects/core/src/user/store/actions/notification-preference.action.spec.ts index dcf37f58a06..4c212ce6706 100644 --- a/projects/core/src/user/store/actions/notification-preference.action.spec.ts +++ b/projects/core/src/user/store/actions/notification-preference.action.spec.ts @@ -1,12 +1,12 @@ -import { UserActions } from './index'; import { NotificationPreference } from '../../../model/notification-preference.model'; -import { StateUtils } from '../../../state/utils/index'; import { PROCESS_FEATURE } from '../../../process/store/process-state'; +import { StateUtils } from '../../../state/utils/index'; +import { resetMeta } from '../../../state/utils/loader/loader.action'; import { - UPDATE_NOTIFICATION_PREFERENCES_PROCESS_ID, NOTIFICATION_PREFERENCES, + UPDATE_NOTIFICATION_PREFERENCES_PROCESS_ID, } from '../user-state'; -import { resetMeta } from '../../../state/utils/loader/loader.action'; +import { UserActions } from './index'; const userId = 'testUser'; const mockNotificationPreference: NotificationPreference[] = [ @@ -17,7 +17,7 @@ const mockNotificationPreference: NotificationPreference[] = [ visible: true, }, ]; -const error = 'anError'; +const error = new Error('anError'); describe('Notification Preference Actions', () => { describe('LoadNotificationPreferences', () => { @@ -37,6 +37,7 @@ describe('Notification Preference Actions', () => { expect({ ...action }).toEqual({ type: UserActions.LOAD_NOTIFICATION_PREFERENCES_FAIL, payload: error, + error, meta: StateUtils.failMeta(NOTIFICATION_PREFERENCES, error), }); }); @@ -81,6 +82,7 @@ describe('Notification Preference Actions', () => { expect({ ...action }).toEqual({ type: UserActions.UPDATE_NOTIFICATION_PREFERENCES_FAIL, payload: error, + error, meta: StateUtils.entityFailMeta( PROCESS_FEATURE, UPDATE_NOTIFICATION_PREFERENCES_PROCESS_ID, diff --git a/projects/core/src/user/store/actions/notification-preference.action.ts b/projects/core/src/user/store/actions/notification-preference.action.ts index 164c3443a49..5f096590b11 100644 --- a/projects/core/src/user/store/actions/notification-preference.action.ts +++ b/projects/core/src/user/store/actions/notification-preference.action.ts @@ -4,7 +4,8 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { StateUtils } from '../../../state/utils/index'; +import { ErrorAction } from '../../../error-handling'; +import { NotificationPreference } from '../../../model/notification-preference.model'; import { PROCESS_FEATURE } from '../../../process/store/process-state'; import { EntityFailAction, @@ -12,11 +13,11 @@ import { EntityLoaderResetAction, EntitySuccessAction, } from '../../../state/utils/entity-loader/entity-loader.action'; +import { StateUtils } from '../../../state/utils/index'; import { - UPDATE_NOTIFICATION_PREFERENCES_PROCESS_ID, NOTIFICATION_PREFERENCES, + UPDATE_NOTIFICATION_PREFERENCES_PROCESS_ID, } from '../user-state'; -import { NotificationPreference } from '../../../model/notification-preference.model'; export const LOAD_NOTIFICATION_PREFERENCES = '[User] Load Notification Preferences'; @@ -37,13 +38,18 @@ export const CLEAR_NOTIFICATION_PREFERENCES = export class LoadNotificationPreferences extends StateUtils.LoaderLoadAction { readonly type = LOAD_NOTIFICATION_PREFERENCES; + constructor(public payload: string) { super(NOTIFICATION_PREFERENCES); } } -export class LoadNotificationPreferencesFail extends StateUtils.LoaderFailAction { +export class LoadNotificationPreferencesFail + extends StateUtils.LoaderFailAction + implements ErrorAction +{ readonly type = LOAD_NOTIFICATION_PREFERENCES_FAIL; + constructor(public payload: any) { super(NOTIFICATION_PREFERENCES, payload); } @@ -51,6 +57,7 @@ export class LoadNotificationPreferencesFail extends StateUtils.LoaderFailAction export class LoadNotificationPreferencesSuccess extends StateUtils.LoaderSuccessAction { readonly type = LOAD_NOTIFICATION_PREFERENCES_SUCCESS; + constructor(public payload: NotificationPreference[]) { super(NOTIFICATION_PREFERENCES); } @@ -58,6 +65,7 @@ export class LoadNotificationPreferencesSuccess extends StateUtils.LoaderSuccess export class UpdateNotificationPreferences extends EntityLoadAction { readonly type = UPDATE_NOTIFICATION_PREFERENCES; + constructor( public payload: { userId: string; preferences: NotificationPreference[] } ) { @@ -65,8 +73,12 @@ export class UpdateNotificationPreferences extends EntityLoadAction { } } -export class UpdateNotificationPreferencesFail extends EntityFailAction { +export class UpdateNotificationPreferencesFail + extends EntityFailAction + implements ErrorAction +{ readonly type = UPDATE_NOTIFICATION_PREFERENCES_FAIL; + constructor(public payload: any) { super(PROCESS_FEATURE, UPDATE_NOTIFICATION_PREFERENCES_PROCESS_ID, payload); } @@ -74,6 +86,7 @@ export class UpdateNotificationPreferencesFail extends EntityFailAction { export class UpdateNotificationPreferencesSuccess extends EntitySuccessAction { readonly type = UPDATE_NOTIFICATION_PREFERENCES_SUCCESS; + constructor(public payload: NotificationPreference[]) { super(PROCESS_FEATURE, UPDATE_NOTIFICATION_PREFERENCES_PROCESS_ID); } @@ -81,6 +94,7 @@ export class UpdateNotificationPreferencesSuccess extends EntitySuccessAction { export class ResetNotificationPreferences extends EntityLoaderResetAction { readonly type = RESET_NOTIFICATION_PREFERENCES; + constructor() { super(PROCESS_FEATURE, UPDATE_NOTIFICATION_PREFERENCES_PROCESS_ID); } @@ -88,6 +102,7 @@ export class ResetNotificationPreferences extends EntityLoaderResetAction { export class ClearNotificationPreferences extends StateUtils.LoaderResetAction { readonly type = CLEAR_NOTIFICATION_PREFERENCES; + constructor() { super(NOTIFICATION_PREFERENCES); } diff --git a/projects/core/src/user/store/actions/payment-methods.action.spec.ts b/projects/core/src/user/store/actions/payment-methods.action.spec.ts index 0c56726926c..df3f32316b0 100644 --- a/projects/core/src/user/store/actions/payment-methods.action.spec.ts +++ b/projects/core/src/user/store/actions/payment-methods.action.spec.ts @@ -4,6 +4,7 @@ import { USER_PAYMENT_METHODS } from '../user-state'; import { UserActions } from './index'; const userId = '123'; +const error = { message: 'mockError' }; describe('User Payment Methods Actions', () => { describe('LoadUserPaymentMethods Actions', () => { @@ -20,11 +21,11 @@ describe('User Payment Methods Actions', () => { describe('LoadUserPaymentMethodsFail Action', () => { it('should create the action', () => { - const error = 'mockError'; const action = new UserActions.LoadUserPaymentMethodsFail(error); expect({ ...action }).toEqual({ type: UserActions.LOAD_USER_PAYMENT_METHODS_FAIL, + error, payload: error, meta: StateUtils.failMeta(USER_PAYMENT_METHODS, error), }); @@ -62,11 +63,12 @@ describe('User Payment Methods Actions', () => { describe('SetDefaultUserPaymentMethodFail Action', () => { it('should create the action', () => { - const action = new UserActions.SetDefaultUserPaymentMethodFail(false); + const action = new UserActions.SetDefaultUserPaymentMethodFail(error); expect({ ...action }).toEqual({ + payload: error, + error, type: UserActions.SET_DEFAULT_USER_PAYMENT_METHOD_FAIL, - payload: false, - meta: StateUtils.failMeta(USER_PAYMENT_METHODS), + meta: StateUtils.failMeta(USER_PAYMENT_METHODS, error), }); }); }); @@ -95,11 +97,12 @@ describe('User Payment Methods Actions', () => { describe('DeleteUserPaymentMethodFail Action', () => { it('should create the action', () => { - const action = new UserActions.DeleteUserPaymentMethodFail(false); + const action = new UserActions.DeleteUserPaymentMethodFail(error); expect({ ...action }).toEqual({ type: UserActions.DELETE_USER_PAYMENT_METHOD_FAIL, - payload: false, - meta: StateUtils.failMeta(USER_PAYMENT_METHODS), + payload: error, + error, + meta: StateUtils.failMeta(USER_PAYMENT_METHODS, error), }); }); }); diff --git a/projects/core/src/user/store/actions/payment-methods.action.ts b/projects/core/src/user/store/actions/payment-methods.action.ts index 2121b27eb67..b174c079e05 100644 --- a/projects/core/src/user/store/actions/payment-methods.action.ts +++ b/projects/core/src/user/store/actions/payment-methods.action.ts @@ -4,6 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ +import { ErrorAction } from '../../../error-handling'; import { PaymentDetails } from '../../../model/payment.model'; import { StateUtils } from '../../../state/utils/index'; import { USER_PAYMENT_METHODS } from '../user-state'; @@ -29,13 +30,18 @@ export const DELETE_USER_PAYMENT_METHOD_SUCCESS = export class LoadUserPaymentMethods extends StateUtils.LoaderLoadAction { readonly type = LOAD_USER_PAYMENT_METHODS; + constructor(public payload: string) { super(USER_PAYMENT_METHODS); } } -export class LoadUserPaymentMethodsFail extends StateUtils.LoaderFailAction { +export class LoadUserPaymentMethodsFail + extends StateUtils.LoaderFailAction + implements ErrorAction +{ readonly type = LOAD_USER_PAYMENT_METHODS_FAIL; + constructor(public payload: any) { super(USER_PAYMENT_METHODS, payload); } @@ -43,6 +49,7 @@ export class LoadUserPaymentMethodsFail extends StateUtils.LoaderFailAction { export class LoadUserPaymentMethodsSuccess extends StateUtils.LoaderSuccessAction { readonly type = LOAD_USER_PAYMENT_METHODS_SUCCESS; + constructor(public payload: PaymentDetails[]) { super(USER_PAYMENT_METHODS); } @@ -50,13 +57,18 @@ export class LoadUserPaymentMethodsSuccess extends StateUtils.LoaderSuccessActio export class SetDefaultUserPaymentMethod extends StateUtils.LoaderLoadAction { readonly type = SET_DEFAULT_USER_PAYMENT_METHOD; + constructor(public payload: any) { super(USER_PAYMENT_METHODS); } } -export class SetDefaultUserPaymentMethodFail extends StateUtils.LoaderFailAction { +export class SetDefaultUserPaymentMethodFail + extends StateUtils.LoaderFailAction + implements ErrorAction +{ readonly type = SET_DEFAULT_USER_PAYMENT_METHOD_FAIL; + constructor(public payload: any) { super(USER_PAYMENT_METHODS, payload); } @@ -64,6 +76,7 @@ export class SetDefaultUserPaymentMethodFail extends StateUtils.LoaderFailAction export class SetDefaultUserPaymentMethodSuccess extends StateUtils.LoaderSuccessAction { readonly type = SET_DEFAULT_USER_PAYMENT_METHOD_SUCCESS; + constructor(public payload: any) { super(USER_PAYMENT_METHODS); } @@ -71,13 +84,18 @@ export class SetDefaultUserPaymentMethodSuccess extends StateUtils.LoaderSuccess export class DeleteUserPaymentMethod extends StateUtils.LoaderLoadAction { readonly type = DELETE_USER_PAYMENT_METHOD; + constructor(public payload: any) { super(USER_PAYMENT_METHODS); } } -export class DeleteUserPaymentMethodFail extends StateUtils.LoaderFailAction { +export class DeleteUserPaymentMethodFail + extends StateUtils.LoaderFailAction + implements ErrorAction +{ readonly type = DELETE_USER_PAYMENT_METHOD_FAIL; + constructor(public payload: any) { super(USER_PAYMENT_METHODS, payload); } @@ -85,6 +103,7 @@ export class DeleteUserPaymentMethodFail extends StateUtils.LoaderFailAction { export class DeleteUserPaymentMethodSuccess extends StateUtils.LoaderSuccessAction { readonly type = DELETE_USER_PAYMENT_METHOD_SUCCESS; + constructor(public payload: any) { super(USER_PAYMENT_METHODS); } diff --git a/projects/core/src/user/store/actions/product-interests.actions.spec.ts b/projects/core/src/user/store/actions/product-interests.actions.spec.ts index 58b9f2e724c..f494a32b541 100644 --- a/projects/core/src/user/store/actions/product-interests.actions.spec.ts +++ b/projects/core/src/user/store/actions/product-interests.actions.spec.ts @@ -1,24 +1,25 @@ +import { NotificationType } from '../../../model/product-interest.model'; +import { PROCESS_FEATURE } from '../../../process/store/process-state'; import { - PRODUCT_INTERESTS, - REMOVE_PRODUCT_INTERESTS_PROCESS_ID, - ADD_PRODUCT_INTEREST_PROCESS_ID, -} from '../user-state'; -import { UserActions } from './index'; + entityFailMeta, + entityLoadMeta, + entityResetMeta, + entitySuccessMeta, +} from '../../../state/utils/entity-loader/entity-loader.action'; import { - loadMeta, failMeta, - successMeta, + loadMeta, resetMeta, + successMeta, } from '../../../state/utils/loader/loader.action'; -import { NotificationType } from '../../../model/product-interest.model'; import { - entityLoadMeta, - entitySuccessMeta, - entityFailMeta, - entityResetMeta, -} from '../../../state/utils/entity-loader/entity-loader.action'; -import { PROCESS_FEATURE } from '../../../process/store/process-state'; + ADD_PRODUCT_INTEREST_PROCESS_ID, + PRODUCT_INTERESTS, + REMOVE_PRODUCT_INTERESTS_PROCESS_ID, +} from '../user-state'; +import { UserActions } from './index'; +const error = new Error('error'); const userId = 'qingyu@sap.com'; const productCode = '343898'; @@ -43,11 +44,11 @@ describe('Product Interests Actions', () => { }); describe('LoadProductInterestsFail Actions', () => { it('should be able to create the action', () => { - const error = 'error'; const action = new UserActions.LoadProductInterestsFail(error); expect({ ...action }).toEqual({ type: UserActions.LOAD_PRODUCT_INTERESTS_FAIL, payload: error, + error, meta: failMeta(PRODUCT_INTERESTS, error), }); }); @@ -105,11 +106,11 @@ describe('Product Interests Actions', () => { describe('RemoveProductInterestsFail Actions', () => { it('should be able to create the action', () => { - const error = 'remove fail'; const action = new UserActions.RemoveProductInterestFail(error); expect({ ...action }).toEqual({ type: UserActions.REMOVE_PRODUCT_INTEREST_FAIL, payload: error, + error, meta: entityFailMeta( PROCESS_FEATURE, REMOVE_PRODUCT_INTERESTS_PROCESS_ID, @@ -152,11 +153,11 @@ describe('Product Interests Actions', () => { describe('AddProductInterestFail Action', () => { it('should be able to create the action', () => { - const error = 'add fail'; const action = new UserActions.AddProductInterestFail(error); expect({ ...action }).toEqual({ type: UserActions.ADD_PRODUCT_INTEREST_FAIL, payload: error, + error, meta: entityFailMeta( PROCESS_FEATURE, ADD_PRODUCT_INTEREST_PROCESS_ID, diff --git a/projects/core/src/user/store/actions/product-interests.actions.ts b/projects/core/src/user/store/actions/product-interests.actions.ts index f04c73b486a..6c5e7de9195 100644 --- a/projects/core/src/user/store/actions/product-interests.actions.ts +++ b/projects/core/src/user/store/actions/product-interests.actions.ts @@ -4,29 +4,30 @@ * SPDX-License-Identifier: Apache-2.0 */ +import { ErrorAction } from '../../../error-handling'; import { - PRODUCT_INTERESTS, - REMOVE_PRODUCT_INTERESTS_PROCESS_ID, - ADD_PRODUCT_INTEREST_PROCESS_ID, -} from '../user-state'; -import { - ProductInterestSearchResult, - ProductInterestEntryRelation, NotificationType, + ProductInterestEntryRelation, + ProductInterestSearchResult, } from '../../../model/product-interest.model'; import { PROCESS_FEATURE } from '../../../process/store/process-state'; -import { - LoaderLoadAction, - LoaderFailAction, - LoaderSuccessAction, - LoaderResetAction, -} from '../../../state/utils/loader/loader.action'; import { EntityFailAction, EntityLoadAction, - EntitySuccessAction, EntityLoaderResetAction, + EntitySuccessAction, } from '../../../state/utils/entity-loader/entity-loader.action'; +import { + LoaderFailAction, + LoaderLoadAction, + LoaderResetAction, + LoaderSuccessAction, +} from '../../../state/utils/loader/loader.action'; +import { + ADD_PRODUCT_INTEREST_PROCESS_ID, + PRODUCT_INTERESTS, + REMOVE_PRODUCT_INTERESTS_PROCESS_ID, +} from '../user-state'; export const LOAD_PRODUCT_INTERESTS = 'Load Product Interests'; export const LOAD_PRODUCT_INTERESTS_FAIL = 'Load Product Interests Fail'; @@ -48,6 +49,7 @@ export const CLEAR_PRODUCT_INTERESTS = 'Clear Product Interests'; export class LoadProductInterests extends LoaderLoadAction { readonly type = LOAD_PRODUCT_INTERESTS; + constructor( public payload: { userId: string; @@ -62,8 +64,12 @@ export class LoadProductInterests extends LoaderLoadAction { } } -export class LoadProductInterestsFail extends LoaderFailAction { +export class LoadProductInterestsFail + extends LoaderFailAction + implements ErrorAction +{ readonly type = LOAD_PRODUCT_INTERESTS_FAIL; + constructor(public payload: any) { super(PRODUCT_INTERESTS, payload); } @@ -71,6 +77,7 @@ export class LoadProductInterestsFail extends LoaderFailAction { export class LoadProductInterestsSuccess extends LoaderSuccessAction { readonly type = LOAD_PRODUCT_INTERESTS_SUCCESS; + constructor(public payload: ProductInterestSearchResult) { super(PRODUCT_INTERESTS); } @@ -78,6 +85,7 @@ export class LoadProductInterestsSuccess extends LoaderSuccessAction { export class RemoveProductInterest extends EntityLoadAction { readonly type = REMOVE_PRODUCT_INTEREST; + constructor( public payload: { userId: string; @@ -91,13 +99,18 @@ export class RemoveProductInterest extends EntityLoadAction { export class RemoveProductInterestSuccess extends EntitySuccessAction { readonly type = REMOVE_PRODUCT_INTEREST_SUCCESS; + constructor(public payload: any) { super(PROCESS_FEATURE, REMOVE_PRODUCT_INTERESTS_PROCESS_ID); } } -export class RemoveProductInterestFail extends EntityFailAction { +export class RemoveProductInterestFail + extends EntityFailAction + implements ErrorAction +{ readonly type = REMOVE_PRODUCT_INTEREST_FAIL; + constructor(public payload: any) { super(PROCESS_FEATURE, REMOVE_PRODUCT_INTERESTS_PROCESS_ID, payload); } @@ -105,6 +118,7 @@ export class RemoveProductInterestFail extends EntityFailAction { export class AddProductInterest extends EntityLoadAction { readonly type = ADD_PRODUCT_INTEREST; + constructor( public payload: { userId: string; @@ -118,13 +132,18 @@ export class AddProductInterest extends EntityLoadAction { export class AddProductInterestSuccess extends EntitySuccessAction { readonly type = ADD_PRODUCT_INTEREST_SUCCESS; + constructor(public payload: any) { super(PROCESS_FEATURE, ADD_PRODUCT_INTEREST_PROCESS_ID); } } -export class AddProductInterestFail extends EntityFailAction { +export class AddProductInterestFail + extends EntityFailAction + implements ErrorAction +{ readonly type = ADD_PRODUCT_INTEREST_FAIL; + constructor(public payload: any) { super(PROCESS_FEATURE, ADD_PRODUCT_INTEREST_PROCESS_ID, payload); } @@ -132,6 +151,7 @@ export class AddProductInterestFail extends EntityFailAction { export class ResetAddInterestState extends EntityLoaderResetAction { readonly type = ADD_PRODUCT_INTEREST_RESET; + constructor() { super(PROCESS_FEATURE, ADD_PRODUCT_INTEREST_PROCESS_ID); } @@ -139,6 +159,7 @@ export class ResetAddInterestState extends EntityLoaderResetAction { export class ResetRemoveInterestState extends EntityLoaderResetAction { readonly type = REMOVE_PRODUCT_INTEREST_RESET; + constructor() { super(PROCESS_FEATURE, REMOVE_PRODUCT_INTERESTS_PROCESS_ID); } @@ -146,6 +167,7 @@ export class ResetRemoveInterestState extends EntityLoaderResetAction { export class ClearProductInterests extends LoaderResetAction { readonly type = CLEAR_PRODUCT_INTERESTS; + constructor() { super(PRODUCT_INTERESTS); } diff --git a/projects/core/src/user/store/actions/regions.action.spec.ts b/projects/core/src/user/store/actions/regions.action.spec.ts index 523d4f25e67..733093e9edc 100644 --- a/projects/core/src/user/store/actions/regions.action.spec.ts +++ b/projects/core/src/user/store/actions/regions.action.spec.ts @@ -18,12 +18,13 @@ describe('Regions Actions', () => { describe('LoadRegionsFail', () => { it('should create the action', () => { - const error = 'anError'; + const error = new Error('anError'); const action = new UserActions.LoadRegionsFail(error); expect({ ...action }).toEqual({ type: UserActions.LOAD_REGIONS_FAIL, payload: error, + error, meta: StateUtils.failMeta(REGIONS, error), }); }); diff --git a/projects/core/src/user/store/actions/regions.action.ts b/projects/core/src/user/store/actions/regions.action.ts index 2d8364c970f..dba607e68f9 100644 --- a/projects/core/src/user/store/actions/regions.action.ts +++ b/projects/core/src/user/store/actions/regions.action.ts @@ -5,6 +5,7 @@ */ import { Action } from '@ngrx/store'; +import { ErrorAction } from '../../../error-handling'; import { Region } from '../../../model/address.model'; import { StateUtils } from '../../../state/utils/index'; import { REGIONS } from '../user-state'; @@ -16,13 +17,18 @@ export const CLEAR_REGIONS = '[User] Clear Regions'; export class LoadRegions extends StateUtils.LoaderLoadAction { readonly type = LOAD_REGIONS; + constructor(public payload: string) { super(REGIONS); } } -export class LoadRegionsFail extends StateUtils.LoaderFailAction { +export class LoadRegionsFail + extends StateUtils.LoaderFailAction + implements ErrorAction +{ readonly type = LOAD_REGIONS_FAIL; + constructor(public payload: any) { super(REGIONS, payload); } @@ -30,6 +36,7 @@ export class LoadRegionsFail extends StateUtils.LoaderFailAction { export class LoadRegionsSuccess extends StateUtils.LoaderSuccessAction { readonly type = LOAD_REGIONS_SUCCESS; + constructor(public payload: { entities: Region[]; country: string }) { super(REGIONS); } @@ -37,6 +44,7 @@ export class LoadRegionsSuccess extends StateUtils.LoaderSuccessAction { export class ClearRegions implements Action { readonly type = CLEAR_REGIONS; + constructor() { // Intentional empty constructor } diff --git a/projects/core/src/user/store/actions/user-addresses.action.spec.ts b/projects/core/src/user/store/actions/user-addresses.action.spec.ts index 00279e01132..aaadccce2fa 100644 --- a/projects/core/src/user/store/actions/user-addresses.action.spec.ts +++ b/projects/core/src/user/store/actions/user-addresses.action.spec.ts @@ -3,6 +3,7 @@ import { StateUtils } from '../../../state/utils/index'; import { USER_ADDRESSES } from '../user-state'; import { UserActions } from './index'; +const error = new Error('mockError'); const userId = '123'; const address: Address = { companyName: 'sap', @@ -23,12 +24,12 @@ describe('User Addresses Actions', () => { describe('LoadUserAddressesFail Action', () => { it('should create the action', () => { - const error = 'mockError'; const action = new UserActions.LoadUserAddressesFail(error); expect({ ...action }).toEqual({ type: UserActions.LOAD_USER_ADDRESSES_FAIL, payload: error, + error, meta: StateUtils.failMeta(USER_ADDRESSES, error), }); }); @@ -70,12 +71,12 @@ describe('User Addresses Actions', () => { describe('AddUserAddressFail Action', () => { it('should create the action', () => { - const error = 'mockError'; const action = new UserActions.AddUserAddressFail(error); expect({ ...action }).toEqual({ type: UserActions.ADD_USER_ADDRESS_FAIL, payload: error, + error, meta: StateUtils.failMeta(USER_ADDRESSES, error), }); }); @@ -117,12 +118,12 @@ describe('User Addresses Actions', () => { describe('UpdateUserAddressFail Action', () => { it('should create the action', () => { - const error = 'mockError'; const action = new UserActions.UpdateUserAddressFail(error); expect({ ...action }).toEqual({ type: UserActions.UPDATE_USER_ADDRESS_FAIL, payload: error, + error, meta: StateUtils.failMeta(USER_ADDRESSES, error), }); }); diff --git a/projects/core/src/user/store/actions/user-addresses.action.ts b/projects/core/src/user/store/actions/user-addresses.action.ts index 5ec4aa8a337..cecd9929d41 100644 --- a/projects/core/src/user/store/actions/user-addresses.action.ts +++ b/projects/core/src/user/store/actions/user-addresses.action.ts @@ -4,6 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ +import { ErrorAction } from '../../../error-handling'; import { Address } from '../../../model/address.model'; import { StateUtils } from '../../../state/utils/index'; import { USER_ADDRESSES } from '../user-state'; @@ -26,13 +27,18 @@ export const DELETE_USER_ADDRESS_SUCCESS = '[User] Delete User Address Success'; export class LoadUserAddresses extends StateUtils.LoaderLoadAction { readonly type = LOAD_USER_ADDRESSES; + constructor(public payload: string) { super(USER_ADDRESSES); } } -export class LoadUserAddressesFail extends StateUtils.LoaderFailAction { +export class LoadUserAddressesFail + extends StateUtils.LoaderFailAction + implements ErrorAction +{ readonly type = LOAD_USER_ADDRESSES_FAIL; + constructor(public payload: any) { super(USER_ADDRESSES, payload); } @@ -40,6 +46,7 @@ export class LoadUserAddressesFail extends StateUtils.LoaderFailAction { export class LoadUserAddressesSuccess extends StateUtils.LoaderSuccessAction { readonly type = LOAD_USER_ADDRESSES_SUCCESS; + constructor(public payload: Address[]) { super(USER_ADDRESSES); } @@ -48,13 +55,18 @@ export class LoadUserAddressesSuccess extends StateUtils.LoaderSuccessAction { // Adding address actions export class AddUserAddress extends StateUtils.LoaderLoadAction { readonly type = ADD_USER_ADDRESS; + constructor(public payload: { userId: string; address: Address }) { super(USER_ADDRESSES); } } -export class AddUserAddressFail extends StateUtils.LoaderFailAction { +export class AddUserAddressFail + extends StateUtils.LoaderFailAction + implements ErrorAction +{ readonly type = ADD_USER_ADDRESS_FAIL; + constructor(public payload: any) { super(USER_ADDRESSES, payload); } @@ -62,6 +74,7 @@ export class AddUserAddressFail extends StateUtils.LoaderFailAction { export class AddUserAddressSuccess extends StateUtils.LoaderSuccessAction { readonly type = ADD_USER_ADDRESS_SUCCESS; + constructor(public payload: any) { super(USER_ADDRESSES); } @@ -70,6 +83,7 @@ export class AddUserAddressSuccess extends StateUtils.LoaderSuccessAction { // Updating address actions export class UpdateUserAddress extends StateUtils.LoaderLoadAction { readonly type = UPDATE_USER_ADDRESS; + constructor( public payload: { userId: string; addressId: string; address: Address } ) { @@ -77,8 +91,12 @@ export class UpdateUserAddress extends StateUtils.LoaderLoadAction { } } -export class UpdateUserAddressFail extends StateUtils.LoaderFailAction { +export class UpdateUserAddressFail + extends StateUtils.LoaderFailAction + implements ErrorAction +{ readonly type = UPDATE_USER_ADDRESS_FAIL; + constructor(public payload: any) { super(USER_ADDRESSES, payload); } @@ -86,6 +104,7 @@ export class UpdateUserAddressFail extends StateUtils.LoaderFailAction { export class UpdateUserAddressSuccess extends StateUtils.LoaderSuccessAction { readonly type = UPDATE_USER_ADDRESS_SUCCESS; + constructor(public payload: any) { super(USER_ADDRESSES); } @@ -94,13 +113,18 @@ export class UpdateUserAddressSuccess extends StateUtils.LoaderSuccessAction { // Deleting address actions export class DeleteUserAddress extends StateUtils.LoaderLoadAction { readonly type = DELETE_USER_ADDRESS; + constructor(public payload: any) { super(USER_ADDRESSES); } } -export class DeleteUserAddressFail extends StateUtils.LoaderFailAction { +export class DeleteUserAddressFail + extends StateUtils.LoaderFailAction + implements ErrorAction +{ readonly type = DELETE_USER_ADDRESS_FAIL; + constructor(public payload: any) { super(USER_ADDRESSES, payload); } @@ -108,6 +132,7 @@ export class DeleteUserAddressFail extends StateUtils.LoaderFailAction { export class DeleteUserAddressSuccess extends StateUtils.LoaderSuccessAction { readonly type = DELETE_USER_ADDRESS_SUCCESS; + constructor(public payload: any) { super(USER_ADDRESSES); } diff --git a/projects/core/src/user/store/actions/user-consents.action.spec.ts b/projects/core/src/user/store/actions/user-consents.action.spec.ts index 18cce32bf09..1d4f13d69bb 100644 --- a/projects/core/src/user/store/actions/user-consents.action.spec.ts +++ b/projects/core/src/user/store/actions/user-consents.action.spec.ts @@ -20,12 +20,13 @@ describe('user consent actions', () => { }); describe('LoadUserConsentsFail', () => { it('should create the action', () => { - const payload = 'anError'; - const action = new UserActions.LoadUserConsentsFail(payload); + const error = new Error('anError'); + const action = new UserActions.LoadUserConsentsFail(error); expect({ ...action }).toEqual({ type: UserActions.LOAD_USER_CONSENTS_FAIL, - payload, - meta: StateUtils.failMeta(USER_CONSENTS, payload), + payload: error, + error, + meta: StateUtils.failMeta(USER_CONSENTS, error), }); }); }); @@ -72,14 +73,15 @@ describe('user consent actions', () => { }); describe('GiveUserConsentFail', () => { it('should create the action', () => { - const payload = 'anError'; - const action = new UserActions.GiveUserConsentFail(payload); + const error = new Error('anError'); + const action = new UserActions.GiveUserConsentFail(error); expect({ ...action }).toEqual({ + error, type: UserActions.GIVE_USER_CONSENT_FAIL, meta: StateUtils.entityFailMeta( PROCESS_FEATURE, GIVE_CONSENT_PROCESS_ID, - payload + error ), }); }); diff --git a/projects/core/src/user/store/actions/user-consents.action.ts b/projects/core/src/user/store/actions/user-consents.action.ts index c4179cbcc5b..9a39cf2c332 100644 --- a/projects/core/src/user/store/actions/user-consents.action.ts +++ b/projects/core/src/user/store/actions/user-consents.action.ts @@ -4,6 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ +import { ErrorAction } from '../../../error-handling'; import { ConsentTemplate } from '../../../model/consent.model'; import { PROCESS_FEATURE } from '../../../process/store/process-state'; import { StateUtils } from '../../../state/utils/index'; @@ -34,13 +35,18 @@ export const RESET_WITHDRAW_USER_CONSENT_PROCESS = export class LoadUserConsents extends StateUtils.LoaderLoadAction { readonly type = LOAD_USER_CONSENTS; + constructor(public payload: string) { super(USER_CONSENTS); } } -export class LoadUserConsentsFail extends StateUtils.LoaderFailAction { +export class LoadUserConsentsFail + extends StateUtils.LoaderFailAction + implements ErrorAction +{ readonly type = LOAD_USER_CONSENTS_FAIL; + constructor(public payload: any) { super(USER_CONSENTS, payload); } @@ -48,6 +54,7 @@ export class LoadUserConsentsFail extends StateUtils.LoaderFailAction { export class LoadUserConsentsSuccess extends StateUtils.LoaderSuccessAction { readonly type = LOAD_USER_CONSENTS_SUCCESS; + constructor(public payload: ConsentTemplate[]) { super(USER_CONSENTS); } @@ -55,6 +62,7 @@ export class LoadUserConsentsSuccess extends StateUtils.LoaderSuccessAction { export class ResetLoadUserConsents extends StateUtils.LoaderResetAction { readonly type = RESET_LOAD_USER_CONSENTS; + constructor() { super(USER_CONSENTS); } @@ -62,6 +70,7 @@ export class ResetLoadUserConsents extends StateUtils.LoaderResetAction { export class GiveUserConsent extends StateUtils.EntityLoadAction { readonly type = GIVE_USER_CONSENT; + constructor( public payload: { userId: string; @@ -73,8 +82,12 @@ export class GiveUserConsent extends StateUtils.EntityLoadAction { } } -export class GiveUserConsentFail extends StateUtils.EntityFailAction { +export class GiveUserConsentFail + extends StateUtils.EntityFailAction + implements ErrorAction +{ readonly type = GIVE_USER_CONSENT_FAIL; + constructor(payload: any) { super(PROCESS_FEATURE, GIVE_CONSENT_PROCESS_ID, payload); } @@ -82,6 +95,7 @@ export class GiveUserConsentFail extends StateUtils.EntityFailAction { export class GiveUserConsentSuccess extends StateUtils.EntitySuccessAction { readonly type = GIVE_USER_CONSENT_SUCCESS; + constructor(public consentTemplate: ConsentTemplate) { super(PROCESS_FEATURE, GIVE_CONSENT_PROCESS_ID); } @@ -89,6 +103,7 @@ export class GiveUserConsentSuccess extends StateUtils.EntitySuccessAction { export class ResetGiveUserConsentProcess extends StateUtils.EntityLoaderResetAction { readonly type = RESET_GIVE_USER_CONSENT_PROCESS; + constructor() { super(PROCESS_FEATURE, GIVE_CONSENT_PROCESS_ID); } @@ -96,6 +111,7 @@ export class ResetGiveUserConsentProcess extends StateUtils.EntityLoaderResetAct export class TransferAnonymousConsent { readonly type = TRANSFER_ANONYMOUS_CONSENT; + constructor( public payload: { userId: string; @@ -107,6 +123,7 @@ export class TransferAnonymousConsent { export class WithdrawUserConsent extends StateUtils.EntityLoadAction { readonly type = WITHDRAW_USER_CONSENT; + constructor( public payload: { userId: string; @@ -118,8 +135,12 @@ export class WithdrawUserConsent extends StateUtils.EntityLoadAction { } } -export class WithdrawUserConsentFail extends StateUtils.EntityFailAction { +export class WithdrawUserConsentFail + extends StateUtils.EntityFailAction + implements ErrorAction +{ readonly type = WITHDRAW_USER_CONSENT_FAIL; + constructor(payload: any) { super(PROCESS_FEATURE, WITHDRAW_CONSENT_PROCESS_ID, payload); } @@ -127,6 +148,7 @@ export class WithdrawUserConsentFail extends StateUtils.EntityFailAction { export class WithdrawUserConsentSuccess extends StateUtils.EntitySuccessAction { readonly type = WITHDRAW_USER_CONSENT_SUCCESS; + constructor() { super(PROCESS_FEATURE, WITHDRAW_CONSENT_PROCESS_ID); } @@ -134,6 +156,7 @@ export class WithdrawUserConsentSuccess extends StateUtils.EntitySuccessAction { export class ResetWithdrawUserConsentProcess extends StateUtils.EntityLoaderResetAction { readonly type = RESET_WITHDRAW_USER_CONSENT_PROCESS; + constructor() { super(PROCESS_FEATURE, WITHDRAW_CONSENT_PROCESS_ID); } diff --git a/projects/core/src/user/store/actions/user-cost-center.action.spec.ts b/projects/core/src/user/store/actions/user-cost-center.action.spec.ts index 317f7620c26..ebbae394223 100644 --- a/projects/core/src/user/store/actions/user-cost-center.action.spec.ts +++ b/projects/core/src/user/store/actions/user-cost-center.action.spec.ts @@ -21,12 +21,13 @@ describe('User Cost Centers Actions', () => { describe('LoadActiveCostCentersFail Action', () => { it('should create the action', () => { - const error = 'mockError'; + const error = new Error('mockError'); const action = new UserActions.LoadActiveCostCentersFail(error); expect({ ...action }).toEqual({ type: UserActions.LOAD_ACTIVE_COST_CENTERS_FAIL, payload: error, + error, meta: StateUtils.failMeta(USER_COST_CENTERS, error), }); }); diff --git a/projects/core/src/user/store/actions/user-cost-center.action.ts b/projects/core/src/user/store/actions/user-cost-center.action.ts index c9665585f04..424c06130c4 100644 --- a/projects/core/src/user/store/actions/user-cost-center.action.ts +++ b/projects/core/src/user/store/actions/user-cost-center.action.ts @@ -4,6 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ +import { ErrorAction } from '../../../error-handling'; import { CostCenter } from '../../../model/org-unit.model'; import { StateUtils } from '../../../state/utils/index'; import { USER_COST_CENTERS } from '../user-state'; @@ -21,7 +22,10 @@ export class LoadActiveCostCenters extends StateUtils.LoaderLoadAction { } } -export class LoadActiveCostCentersFail extends StateUtils.LoaderFailAction { +export class LoadActiveCostCentersFail + extends StateUtils.LoaderFailAction + implements ErrorAction +{ readonly type = LOAD_ACTIVE_COST_CENTERS_FAIL; constructor(public payload: any) { super(USER_COST_CENTERS, payload); diff --git a/projects/core/src/user/store/effects/billing-countries.effect.ts b/projects/core/src/user/store/effects/billing-countries.effect.ts index b59be7d1391..e298b338f93 100644 --- a/projects/core/src/user/store/effects/billing-countries.effect.ts +++ b/projects/core/src/user/store/effects/billing-countries.effect.ts @@ -4,14 +4,14 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { Injectable, inject } from '@angular/core'; +import { inject, Injectable } from '@angular/core'; import { Actions, createEffect, ofType } from '@ngrx/effects'; import { Observable, of } from 'rxjs'; import { catchError, map, switchMap } from 'rxjs/operators'; import { LoggerService } from '../../../logger'; import { CountryType } from '../../../model/address.model'; import { SiteConnector } from '../../../site-context/connectors/site.connector'; -import { normalizeHttpError } from '../../../util/normalize-http-error'; +import { tryNormalizeHttpError } from '../../../util/try-normalize-http-error'; import { UserActions } from '../actions/index'; @Injectable() @@ -31,7 +31,7 @@ export class BillingCountriesEffect { catchError((error) => of( new UserActions.LoadBillingCountriesFail( - normalizeHttpError(error, this.logger) + tryNormalizeHttpError(error, this.logger) ) ) ) diff --git a/projects/core/src/user/store/effects/customer-coupon.effect.ts b/projects/core/src/user/store/effects/customer-coupon.effect.ts index 7b99d2dcd8d..1cda07b887d 100644 --- a/projects/core/src/user/store/effects/customer-coupon.effect.ts +++ b/projects/core/src/user/store/effects/customer-coupon.effect.ts @@ -10,7 +10,7 @@ import { Observable, of } from 'rxjs'; import { catchError, map, mergeMap } from 'rxjs/operators'; import { LoggerService } from '../../../logger'; import { CustomerCouponSearchResult } from '../../../model/customer-coupon.model'; -import { normalizeHttpError } from '../../../util/normalize-http-error'; +import { tryNormalizeHttpError } from '../../../util/try-normalize-http-error'; import { CustomerCouponConnector } from '../../connectors/customer-coupon/customer-coupon.connector'; import * as fromCustomerCouponsAction from '../actions/customer-coupon.action'; @@ -43,7 +43,7 @@ export class CustomerCouponEffects { catchError((error) => of( new fromCustomerCouponsAction.LoadCustomerCouponsFail( - normalizeHttpError(error, this.logger) + tryNormalizeHttpError(error, this.logger) ) ) ) @@ -72,7 +72,7 @@ export class CustomerCouponEffects { catchError((error) => of( new fromCustomerCouponsAction.SubscribeCustomerCouponFail( - normalizeHttpError(error, this.logger) + tryNormalizeHttpError(error, this.logger) ) ) ) @@ -101,7 +101,7 @@ export class CustomerCouponEffects { catchError((error) => of( new fromCustomerCouponsAction.UnsubscribeCustomerCouponFail( - normalizeHttpError(error, this.logger) + tryNormalizeHttpError(error, this.logger) ) ) ) @@ -130,7 +130,7 @@ export class CustomerCouponEffects { catchError((error) => of( new fromCustomerCouponsAction.ClaimCustomerCouponFail( - normalizeHttpError(error, this.logger) + tryNormalizeHttpError(error, this.logger) ) ) ) @@ -159,7 +159,7 @@ export class CustomerCouponEffects { catchError((error) => of( new fromCustomerCouponsAction.DisclaimCustomerCouponFail( - normalizeHttpError(error, this.logger) + tryNormalizeHttpError(error, this.logger) ) ) ) diff --git a/projects/core/src/user/store/effects/delivery-countries.effect.ts b/projects/core/src/user/store/effects/delivery-countries.effect.ts index 57ede26283b..c66de134353 100644 --- a/projects/core/src/user/store/effects/delivery-countries.effect.ts +++ b/projects/core/src/user/store/effects/delivery-countries.effect.ts @@ -11,7 +11,7 @@ import { catchError, map, switchMap } from 'rxjs/operators'; import { LoggerService } from '../../../logger'; import { CountryType } from '../../../model/address.model'; import { SiteConnector } from '../../../site-context/connectors/site.connector'; -import { normalizeHttpError } from '../../../util/normalize-http-error'; +import { tryNormalizeHttpError } from '../../../util/try-normalize-http-error'; import { UserActions } from '../actions/index'; @Injectable() @@ -31,7 +31,7 @@ export class DeliveryCountriesEffects { catchError((error) => of( new UserActions.LoadDeliveryCountriesFail( - normalizeHttpError(error, this.logger) + tryNormalizeHttpError(error, this.logger) ) ) ) diff --git a/projects/core/src/user/store/effects/notification-preference.effect.spec.ts b/projects/core/src/user/store/effects/notification-preference.effect.spec.ts index cdb60dbfcb7..d2e80f19430 100644 --- a/projects/core/src/user/store/effects/notification-preference.effect.spec.ts +++ b/projects/core/src/user/store/effects/notification-preference.effect.spec.ts @@ -18,7 +18,7 @@ const mockNotificationPreference: NotificationPreference[] = [ visible: true, }, ]; -const error = 'anError'; +const error = new Error('anError'); describe('Notification Preference Effect', () => { let notificationPreferenceEffects: fromEffect.NotificationPreferenceEffects; @@ -67,9 +67,7 @@ describe('Notification Preference Effect', () => { ); const action = new UserActions.LoadNotificationPreferences(userId); - const completion = new UserActions.LoadNotificationPreferencesFail( - undefined - ); + const completion = new UserActions.LoadNotificationPreferencesFail(error); actions$ = hot('-a', { a: action }); const expected = cold('-b', { b: completion }); @@ -112,7 +110,7 @@ describe('Notification Preference Effect', () => { preferences: mockNotificationPreference, }); const completion = new UserActions.UpdateNotificationPreferencesFail( - undefined + error ); actions$ = hot('-a', { a: action }); diff --git a/projects/core/src/user/store/effects/notification-preference.effect.ts b/projects/core/src/user/store/effects/notification-preference.effect.ts index fbc6a5960c1..2c6467e54c9 100644 --- a/projects/core/src/user/store/effects/notification-preference.effect.ts +++ b/projects/core/src/user/store/effects/notification-preference.effect.ts @@ -9,7 +9,7 @@ import { Actions, createEffect, ofType } from '@ngrx/effects'; import { Observable, of } from 'rxjs'; import { catchError, map, mergeMap, switchMap } from 'rxjs/operators'; import { LoggerService } from '../../../logger'; -import { normalizeHttpError } from '../../../util/normalize-http-error'; +import { tryNormalizeHttpError } from '../../../util/try-normalize-http-error'; import { UserNotificationPreferenceConnector } from '../../connectors/notification-preference/user-notification-preference.connector'; import { UserActions } from '../actions/index'; @@ -33,7 +33,7 @@ export class NotificationPreferenceEffects { catchError((error) => of( new UserActions.LoadNotificationPreferencesFail( - normalizeHttpError(error, this.logger) + tryNormalizeHttpError(error, this.logger) ) ) ) @@ -60,7 +60,7 @@ export class NotificationPreferenceEffects { catchError((error) => of( new UserActions.UpdateNotificationPreferencesFail( - normalizeHttpError(error, this.logger) + tryNormalizeHttpError(error, this.logger) ) ) ) diff --git a/projects/core/src/user/store/effects/payment-methods.effect.ts b/projects/core/src/user/store/effects/payment-methods.effect.ts index 5b24aaf367b..3d286419b16 100644 --- a/projects/core/src/user/store/effects/payment-methods.effect.ts +++ b/projects/core/src/user/store/effects/payment-methods.effect.ts @@ -13,7 +13,7 @@ import { GlobalMessageService } from '../../../global-message/facade/global-mess import { GlobalMessageType } from '../../../global-message/models/global-message.model'; import { LoggerService } from '../../../logger'; import { PaymentDetails } from '../../../model/payment.model'; -import { normalizeHttpError } from '../../../util/normalize-http-error'; +import { tryNormalizeHttpError } from '../../../util/try-normalize-http-error'; import { UserPaymentConnector } from '../../connectors/payment/user-payment.connector'; import { UserActions } from '../actions/index'; @@ -33,7 +33,7 @@ export class UserPaymentMethodsEffects { catchError((error) => of( new UserActions.LoadUserPaymentMethodsFail( - normalizeHttpError(error, this.logger) + tryNormalizeHttpError(error, this.logger) ) ) ) @@ -57,7 +57,7 @@ export class UserPaymentMethodsEffects { catchError((error) => of( new UserActions.SetDefaultUserPaymentMethodFail( - normalizeHttpError(error, this.logger) + tryNormalizeHttpError(error, this.logger) ) ) ) @@ -87,7 +87,7 @@ export class UserPaymentMethodsEffects { catchError((error) => of( new UserActions.DeleteUserPaymentMethodFail( - normalizeHttpError(error, this.logger) + tryNormalizeHttpError(error, this.logger) ) ) ) diff --git a/projects/core/src/user/store/effects/product-interests.effect.spec.ts b/projects/core/src/user/store/effects/product-interests.effect.spec.ts index 47f211dbdcc..217cb3a1c96 100644 --- a/projects/core/src/user/store/effects/product-interests.effect.spec.ts +++ b/projects/core/src/user/store/effects/product-interests.effect.spec.ts @@ -19,6 +19,7 @@ const loadParams = { currentPage: 1, sort: 'name:asc', }; +const error = new Error('error'); describe('Product Interests Effect', () => { let actions$: Actions; @@ -63,10 +64,10 @@ describe('Product Interests Effect', () => { }); it('should be able to handle failures for load product interests', () => { spyOn(userInterestConnector, 'getInterests').and.returnValue( - throwError(() => 'Error') + throwError(() => error) ); const action = new UserActions.LoadProductInterests(loadParams); - const completion = new UserActions.LoadProductInterestsFail(undefined); + const completion = new UserActions.LoadProductInterestsFail(error); actions$ = hot('-a', { a: action }); const expected = cold('-b', { b: completion }); @@ -141,10 +142,10 @@ describe('Product Interests Effect', () => { it('should be able to handle failures for remove product interest', () => { spyOn(userInterestConnector, 'removeInterest').and.returnValue( - throwError(() => 'Error') + throwError(() => error) ); const action = new UserActions.RemoveProductInterest(delParams); - const completion = new UserActions.RemoveProductInterestFail(undefined); + const completion = new UserActions.RemoveProductInterestFail(error); actions$ = hot('-a', { a: action }); const expected = cold('-b', { b: completion }); diff --git a/projects/core/src/user/store/effects/product-interests.effect.ts b/projects/core/src/user/store/effects/product-interests.effect.ts index b9d0d0c648a..f35e74e63ae 100644 --- a/projects/core/src/user/store/effects/product-interests.effect.ts +++ b/projects/core/src/user/store/effects/product-interests.effect.ts @@ -4,14 +4,14 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { Injectable, inject } from '@angular/core'; +import { inject, Injectable } from '@angular/core'; import { Actions, createEffect, ofType } from '@ngrx/effects'; import { Action } from '@ngrx/store'; import { Observable, of } from 'rxjs'; import { catchError, map, switchMap } from 'rxjs/operators'; import { LoggerService } from '../../../logger'; import { ProductInterestSearchResult } from '../../../model/product-interest.model'; -import { normalizeHttpError } from '../../../util/normalize-http-error'; +import { tryNormalizeHttpError } from '../../../util/try-normalize-http-error'; import { UserInterestsConnector } from '../../connectors/interests/user-interests.connector'; import { UserActions } from '../actions/index'; @@ -46,7 +46,7 @@ export class ProductInterestsEffect { catchError((error) => of( new UserActions.LoadProductInterestsFail( - normalizeHttpError(error, this.logger) + tryNormalizeHttpError(error, this.logger) ) ) ) @@ -79,7 +79,7 @@ export class ProductInterestsEffect { catchError((error) => of( new UserActions.RemoveProductInterestFail( - normalizeHttpError(error, this.logger) + tryNormalizeHttpError(error, this.logger) ) ) ) @@ -111,7 +111,7 @@ export class ProductInterestsEffect { catchError((error) => of( new UserActions.AddProductInterestFail( - normalizeHttpError(error, this.logger) + tryNormalizeHttpError(error, this.logger) ) ) ) diff --git a/projects/core/src/user/store/effects/regions.effect.ts b/projects/core/src/user/store/effects/regions.effect.ts index 9a177d87e69..35b9d0baf2a 100644 --- a/projects/core/src/user/store/effects/regions.effect.ts +++ b/projects/core/src/user/store/effects/regions.effect.ts @@ -12,7 +12,7 @@ import { catchError, map, switchMap } from 'rxjs/operators'; import { LoggerService } from '../../../logger'; import { SiteConnector } from '../../../site-context/connectors/site.connector'; import { StateUtils } from '../../../state/utils/index'; -import { normalizeHttpError } from '../../../util/normalize-http-error'; +import { tryNormalizeHttpError } from '../../../util/try-normalize-http-error'; import { UserActions } from '../actions/index'; import { REGIONS } from '../user-state'; @@ -38,7 +38,7 @@ export class RegionsEffects { catchError((error) => of( new UserActions.LoadRegionsFail( - normalizeHttpError(error, this.logger) + tryNormalizeHttpError(error, this.logger) ) ) ) diff --git a/projects/core/src/user/store/effects/user-addresses.effect.ts b/projects/core/src/user/store/effects/user-addresses.effect.ts index 8e9f5c3a70d..54274d431f7 100644 --- a/projects/core/src/user/store/effects/user-addresses.effect.ts +++ b/projects/core/src/user/store/effects/user-addresses.effect.ts @@ -14,7 +14,7 @@ import { } from '../../../global-message/index'; import { LoggerService } from '../../../logger'; import { Address } from '../../../model/address.model'; -import { normalizeHttpError } from '../../../util/normalize-http-error'; +import { tryNormalizeHttpError } from '../../../util/try-normalize-http-error'; import { UserAddressConnector } from '../../connectors/address/user-address.connector'; import { UserAddressService } from '../../facade/user-address.service'; import { UserActions } from '../actions/index'; @@ -36,7 +36,7 @@ export class UserAddressesEffects { catchError((error) => of( new UserActions.LoadUserAddressesFail( - normalizeHttpError(error, this.logger) + tryNormalizeHttpError(error, this.logger) ) ) ) @@ -60,7 +60,7 @@ export class UserAddressesEffects { catchError((error) => of( new UserActions.AddUserAddressFail( - normalizeHttpError(error, this.logger) + tryNormalizeHttpError(error, this.logger) ) ) ) @@ -88,7 +88,7 @@ export class UserAddressesEffects { ); return of( new UserActions.UpdateUserAddressFail( - normalizeHttpError(error, this.logger) + tryNormalizeHttpError(error, this.logger) ) ); }) @@ -112,7 +112,7 @@ export class UserAddressesEffects { catchError((error) => of( new UserActions.DeleteUserAddressFail( - normalizeHttpError(error, this.logger) + tryNormalizeHttpError(error, this.logger) ) ) ) diff --git a/projects/core/src/user/store/effects/user-consents.effect.spec.ts b/projects/core/src/user/store/effects/user-consents.effect.spec.ts index 926ee05688a..0594c4d54f0 100644 --- a/projects/core/src/user/store/effects/user-consents.effect.spec.ts +++ b/projects/core/src/user/store/effects/user-consents.effect.spec.ts @@ -8,7 +8,7 @@ import { GlobalMessageType } from '../../../global-message/models/global-message import { GlobalMessageActions } from '../../../global-message/store/actions'; import { ConsentTemplate } from '../../../model/consent.model'; import { SiteContextActions } from '../../../site-context/store/actions/index'; -import { normalizeHttpError } from '../../../util/normalize-http-error'; +import { tryNormalizeHttpError } from '../../../util/try-normalize-http-error'; import { UserConsentAdapter } from '../../connectors/index'; import { UserActions } from '../actions/index'; import * as fromEffect from './user-consents.effect'; @@ -121,7 +121,7 @@ describe('User Consents effect', () => { consentTemplateVersion, }); const completion = new UserActions.GiveUserConsentFail( - normalizeHttpError(mockError, new MockLoggerService()) + tryNormalizeHttpError(mockError, new MockLoggerService()) ); const closeMessage = new GlobalMessageActions.RemoveMessagesByType( GlobalMessageType.MSG_TYPE_ERROR @@ -148,7 +148,7 @@ describe('User Consents effect', () => { consentTemplateVersion, }); const completion = new UserActions.GiveUserConsentFail( - normalizeHttpError(mockError, new MockLoggerService()) + tryNormalizeHttpError(mockError, new MockLoggerService()) ); actions$ = hot('-a', { a: action }); diff --git a/projects/core/src/user/store/effects/user-consents.effect.ts b/projects/core/src/user/store/effects/user-consents.effect.ts index 0b1125f9ef2..9e6b4734de3 100644 --- a/projects/core/src/user/store/effects/user-consents.effect.ts +++ b/projects/core/src/user/store/effects/user-consents.effect.ts @@ -12,7 +12,7 @@ import { GlobalMessageType } from '../../../global-message/models/global-message import { GlobalMessageActions } from '../../../global-message/store/actions'; import { LoggerService } from '../../../logger'; import { SiteContextActions } from '../../../site-context/store/actions/index'; -import { normalizeHttpError } from '../../../util/normalize-http-error'; +import { tryNormalizeHttpError } from '../../../util/try-normalize-http-error'; import { UserConsentConnector } from '../../connectors/consent/user-consent.connector'; import { UserActions } from '../actions/index'; @@ -38,7 +38,7 @@ export class UserConsentsEffect { catchError((error) => of( new UserActions.LoadUserConsentsFail( - normalizeHttpError(error, this.logger) + tryNormalizeHttpError(error, this.logger) ) ) ) @@ -69,7 +69,7 @@ export class UserConsentsEffect { | GlobalMessageActions.RemoveMessagesByType > = [ new UserActions.GiveUserConsentFail( - normalizeHttpError(error, this.logger) + tryNormalizeHttpError(error, this.logger) ), ]; if ( @@ -102,7 +102,7 @@ export class UserConsentsEffect { catchError((error) => of( new UserActions.WithdrawUserConsentFail( - normalizeHttpError(error, this.logger) + tryNormalizeHttpError(error, this.logger) ) ) ) diff --git a/projects/core/src/user/store/effects/user-cost-center.effect.ts b/projects/core/src/user/store/effects/user-cost-center.effect.ts index 8a7d6115246..58169441ed8 100644 --- a/projects/core/src/user/store/effects/user-cost-center.effect.ts +++ b/projects/core/src/user/store/effects/user-cost-center.effect.ts @@ -4,14 +4,14 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { Injectable, inject } from '@angular/core'; +import { inject, Injectable } from '@angular/core'; import { Actions, createEffect, ofType } from '@ngrx/effects'; import { Observable, of } from 'rxjs'; import { catchError, map, switchMap } from 'rxjs/operators'; import { LoggerService } from '../../../logger'; import { EntitiesModel } from '../../../model/misc.model'; import { CostCenter } from '../../../model/org-unit.model'; -import { normalizeHttpError } from '../../../util/normalize-http-error'; +import { tryNormalizeHttpError } from '../../../util/try-normalize-http-error'; import { UserCostCenterConnector } from '../../connectors/cost-center/user-cost-center.connector'; import { UserActions } from '../actions/index'; @@ -34,7 +34,7 @@ export class UserCostCenterEffects { catchError((error) => of( new UserActions.LoadActiveCostCentersFail( - normalizeHttpError(error, this.logger) + tryNormalizeHttpError(error, this.logger) ) ) ) diff --git a/projects/core/src/user/store/reducers/notification-preference.reducer.spec.ts b/projects/core/src/user/store/reducers/notification-preference.reducer.spec.ts index ca39fa60de5..6b4131efd4e 100644 --- a/projects/core/src/user/store/reducers/notification-preference.reducer.spec.ts +++ b/projects/core/src/user/store/reducers/notification-preference.reducer.spec.ts @@ -10,7 +10,7 @@ const mockNotificationPreference: NotificationPreference[] = [ visible: true, }, ]; -const error = 'anError'; +const error = new Error('anError'); describe('Notification Preference Reducer', () => { describe('undefined action', () => { diff --git a/projects/core/src/user/store/selectors/user-consents.selectors.spec.ts b/projects/core/src/user/store/selectors/user-consents.selectors.spec.ts index 45328955e06..19c16d75928 100644 --- a/projects/core/src/user/store/selectors/user-consents.selectors.spec.ts +++ b/projects/core/src/user/store/selectors/user-consents.selectors.spec.ts @@ -96,7 +96,7 @@ describe('User consents selectors', () => { }); describe('getConsentsError', () => { it('should return the error flag', () => { - store.dispatch(new UserActions.LoadUserConsentsFail('error')); + store.dispatch(new UserActions.LoadUserConsentsFail(new Error('error'))); let result = false; store diff --git a/projects/core/src/util/index.ts b/projects/core/src/util/index.ts index 6c5d527cb71..5e72aca6a71 100644 --- a/projects/core/src/util/index.ts +++ b/projects/core/src/util/index.ts @@ -21,5 +21,6 @@ export * from './script-loader.service'; export * from './ssr.tokens'; export * from './testing-time-utils'; export * from './time-utils'; +export * from './try-normalize-http-error'; export * from './type-guards'; export * from './type-utils'; diff --git a/projects/core/src/util/normalize-http-error.ts b/projects/core/src/util/normalize-http-error.ts index 9265f643bcf..37686a54794 100644 --- a/projects/core/src/util/normalize-http-error.ts +++ b/projects/core/src/util/normalize-http-error.ts @@ -16,6 +16,9 @@ import { HttpErrorModel } from '../model/misc.model'; * NgRx Action payload, as it will strip potentially unserializable parts from * it and warn in debug mode if passed error is not instance of HttpErrorModel * (which usually happens when logic in NgRx Effect is not sealed correctly) + * + * @deprecated since 2211.29 - use `tryNormalizeHttpError` instead. The `normalizeHttpError` will be removed from public API + * together with removing the feature toggle `ssrStrictErrorHandlingForHttpAndNgrx`. */ export function normalizeHttpError( error: HttpErrorResponse | HttpErrorModel | any, diff --git a/projects/core/src/util/try-normalize-http-error.spec.ts b/projects/core/src/util/try-normalize-http-error.spec.ts new file mode 100644 index 00000000000..35186f1affc --- /dev/null +++ b/projects/core/src/util/try-normalize-http-error.spec.ts @@ -0,0 +1,33 @@ +import { + HttpErrorModel, + LoggerService, + tryNormalizeHttpError, +} from '@spartacus/core'; + +describe('tryNormalizeHttpError', () => { + let mockLogger: LoggerService; + + beforeEach(() => { + mockLogger = jasmine.createSpyObj('LoggerService', ['error']); + }); + + it('should return the normalized error when input is an HttpErrorModel', () => { + const inputError = new HttpErrorModel(); + const result = tryNormalizeHttpError(inputError, mockLogger); + + expect(result).toBe(inputError); + expect(mockLogger.error).not.toHaveBeenCalled(); + }); + + it('should return the original error when input is not HttpErrorModel or HttpErrorResponse', () => { + const inputError = new Error('An error occurred'); + + const result = tryNormalizeHttpError(inputError, mockLogger); + + expect(result).toBe(inputError); + expect(mockLogger.error).toHaveBeenCalledWith( + 'Error passed to normalizeHttpError is not HttpErrorResponse instance', + inputError + ); + }); +}); diff --git a/projects/core/src/util/try-normalize-http-error.ts b/projects/core/src/util/try-normalize-http-error.ts new file mode 100644 index 00000000000..09f3324c6c5 --- /dev/null +++ b/projects/core/src/util/try-normalize-http-error.ts @@ -0,0 +1,17 @@ +/* + * SPDX-FileCopyrightText: 2024 SAP Spartacus team + * + * SPDX-License-Identifier: Apache-2.0 + */ + +import { HttpErrorResponse } from '@angular/common/http'; +import { LoggerService } from '../logger'; +import { HttpErrorModel } from '../model'; +import { normalizeHttpError } from './normalize-http-error'; + +export function tryNormalizeHttpError( + error: HttpErrorResponse | HttpErrorModel | any, + logger: LoggerService +): HttpErrorModel | Error { + return normalizeHttpError(error, logger) ?? error; +} diff --git a/projects/schematics/src/add-ssr/__snapshots__/index_spec.ts.snap b/projects/schematics/src/add-ssr/__snapshots__/index_spec.ts.snap index bce7410a456..598769c2b12 100644 --- a/projects/schematics/src/add-ssr/__snapshots__/index_spec.ts.snap +++ b/projects/schematics/src/add-ssr/__snapshots__/index_spec.ts.snap @@ -205,14 +205,20 @@ exports[`add-ssr server.ts should be configured properly 1`] = ` "import { APP_BASE_HREF } from '@angular/common'; import { NgExpressEngineDecorator, + defaultExpressErrorHandlers, ngExpressEngine as engine, } from '@spartacus/setup/ssr'; import express from 'express'; +import { readFileSync } from 'node:fs'; import { dirname, join, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; import AppServerModule from './src/main.server'; -const ngExpressEngine = NgExpressEngineDecorator.get(engine); +const ngExpressEngine = NgExpressEngineDecorator.get(engine, { + ssrFeatureToggles: { + avoidCachingErrors: true, + }, +}); // The Express app is exported so that it can be used by serverless Functions. export function app(): express.Express { @@ -220,6 +226,7 @@ export function app(): express.Express { const serverDistFolder = dirname(fileURLToPath(import.meta.url)); const browserDistFolder = resolve(serverDistFolder, '../browser'); const indexHtml = join(browserDistFolder, 'index.html'); + const indexHtmlContent = readFileSync(indexHtml, 'utf-8'); server.set('trust proxy', 'loopback'); @@ -249,6 +256,8 @@ export function app(): express.Express { }); }); + server.use(defaultExpressErrorHandlers(indexHtmlContent)) + return server; } diff --git a/projects/schematics/src/add-ssr/files/server.__typescriptExt__ b/projects/schematics/src/add-ssr/files/server.__typescriptExt__ index cb8f879adab..75e2cac08e0 100644 --- a/projects/schematics/src/add-ssr/files/server.__typescriptExt__ +++ b/projects/schematics/src/add-ssr/files/server.__typescriptExt__ @@ -1,14 +1,20 @@ import { APP_BASE_HREF } from '@angular/common'; import { NgExpressEngineDecorator, + defaultExpressErrorHandlers, ngExpressEngine as engine, } from '@spartacus/setup/ssr'; import express from 'express'; +import { readFileSync } from 'node:fs'; import { dirname, join, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; import AppServerModule from './src/main.server'; -const ngExpressEngine = NgExpressEngineDecorator.get(engine); +const ngExpressEngine = NgExpressEngineDecorator.get(engine, { + ssrFeatureToggles: { + avoidCachingErrors: true, + }, +}); // The Express app is exported so that it can be used by serverless Functions. export function app(): express.Express { @@ -16,6 +22,7 @@ export function app(): express.Express { const serverDistFolder = dirname(fileURLToPath(import.meta.url)); const browserDistFolder = resolve(serverDistFolder, '../browser'); const indexHtml = join(browserDistFolder, 'index.html'); + const indexHtmlContent = readFileSync(indexHtml, 'utf-8'); server.set('trust proxy', 'loopback'); @@ -45,6 +52,8 @@ export function app(): express.Express { }); }); + server.use(defaultExpressErrorHandlers(indexHtmlContent)) + return server; } diff --git a/projects/schematics/src/shared/lib-configs/quote-schematics-config.ts b/projects/schematics/src/shared/lib-configs/quote-schematics-config.ts index 356bfdb1520..7aaf2560f55 100644 --- a/projects/schematics/src/shared/lib-configs/quote-schematics-config.ts +++ b/projects/schematics/src/shared/lib-configs/quote-schematics-config.ts @@ -1,5 +1,4 @@ /* - * SPDX-FileCopyrightText: 2023 SAP Spartacus team * SPDX-FileCopyrightText: 2024 SAP Spartacus team * * SPDX-License-Identifier: Apache-2.0 diff --git a/projects/ssr-tests/README.md b/projects/ssr-tests/README.md index ed58e9896c4..e95e33c8d9e 100644 --- a/projects/ssr-tests/README.md +++ b/projects/ssr-tests/README.md @@ -4,7 +4,7 @@ This project is for testing server-side rendering in Spartacus. ## Running Tests -Before running the test suite, we first need to build our application with `npm install && npm run build:libs && npm run build:ssr:local-http` from the root of the project. +Before running the test suite, we first need to build our application with `npm install && npm run build:libs && npm run build:ssr:local-http-backend` from the root of the project. After the build, use the `npm run test:ssr` and `npm run test:ssr:ci` commands from the root of the project to run the tests. diff --git a/projects/ssr-tests/src/matchers/matchers.ts b/projects/ssr-tests/src/matchers/matchers.ts new file mode 100644 index 00000000000..f21d03ba33c --- /dev/null +++ b/projects/ssr-tests/src/matchers/matchers.ts @@ -0,0 +1,50 @@ +/* + * SPDX-FileCopyrightText: 2024 SAP Spartacus team + * + * SPDX-License-Identifier: Apache-2.0 + */ + +import { getLogMessages } from '../utils/log.utils'; + +expect.extend({ + toContainLogs(received: string[], expected: string[]) { + const receivedString = received.join('\n'); + const expectedString = Array.isArray(expected) + ? expected.join('\n') + : expected; + const pass = receivedString.includes(expectedString); + if (pass) { + return { + message: () => + `Expected log messages not to contain:\n${expectedString}`, + pass: true, + }; + } else { + return { + message: () => + `Expected log messages to contain:\n${expectedString}\n\nbut received:\n${receivedString}`, + pass: false, + }; + } + }, +}); + +interface CustomMatchers { + toContainLogs(expected: string[] | string): R; +} + +declare global { + namespace jest { + interface Expect extends CustomMatchers {} + interface Matchers extends CustomMatchers {} + interface InverseAsymmetricMatchers extends CustomMatchers {} + } +} + +/** + * Return a jest matcher that can be used to check log messages. + * @returns The jest matcher. + */ +export function expectLogMessages(): jest.JestMatchers { + return expect(getLogMessages()); +} diff --git a/projects/ssr-tests/src/proxy.utils.ts b/projects/ssr-tests/src/proxy.utils.ts deleted file mode 100644 index 7051b2128d0..00000000000 --- a/projects/ssr-tests/src/proxy.utils.ts +++ /dev/null @@ -1,86 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2023 SAP Spartacus team - * SPDX-FileCopyrightText: 2024 SAP Spartacus team - * - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as http from 'http'; -import * as httpProxy from 'http-proxy'; - -const proxy = (httpProxy).createProxyServer({ secure: false }); - -/** - * Default settings to send http requests. - */ -const REQUEST_OPTIONS = { - host: 'localhost', - port: 4000, -}; - -interface ProxyOptions { - /** - * The url to reroute requests to. - */ - target: string; - /** - * Number of seconds to delay requests before sending. - */ - delay?: number; - /** - * Number of status code to set response to. - */ - throwStatus?: number; -} - -/** - * Starts an http proxy server on port 9002 with the provided options. - */ -export async function startProxyServer(options: ProxyOptions) { - return new Promise((resolve) => { - const server = http.createServer((req: any, res: any) => { - const forwardRequest = () => - proxy.web(req, res, { target: options.target }); - - if (options.throwStatus) { - proxy.on('proxyRes', (proxyRes: any) => { - proxyRes.statusCode = options.throwStatus; - }); - } - - if (options.delay) { - setTimeout(forwardRequest, options.delay); - } else { - forwardRequest(); - } - }); - - server.listen(9002, () => { - resolve(server); - }); - }); -} - -/** - * Send an http GET request to a given url. - */ -export async function sendRequest(path: string) { - return new Promise((resolve, reject) => { - const req = http.get({ ...REQUEST_OPTIONS, path }, (res: any) => { - const bodyChunks: string[] = []; - - res - .on('data', (chunk: any) => { - bodyChunks.push(chunk); - }) - .on('end', () => { - res.bodyChunks = bodyChunks; - return resolve(res); - }); - }); - - req.on('error', (e: Error) => { - reject(e); - }); - }); -} diff --git a/projects/ssr-tests/src/ssr-testing.spec.ts b/projects/ssr-tests/src/ssr-testing.spec.ts index 75ece7c640c..c4f11b49fcf 100644 --- a/projects/ssr-tests/src/ssr-testing.spec.ts +++ b/projects/ssr-tests/src/ssr-testing.spec.ts @@ -1,54 +1,134 @@ -import * as Log from './log.utils'; -import * as ProxyServer from './proxy.utils'; -import * as Ssr from './ssr.utils'; +import { Server } from 'http'; +import { expectLogMessages } from './matchers/matchers'; +import * as HttpUtils from './utils/http.utils'; +import * as LogUtils from './utils/log.utils'; +import * as ProxyUtils from './utils/proxy.utils'; +import * as SsrUtils from './utils/ssr.utils'; const BACKEND_BASE_URL: string = process.env.CX_BASE_URL || ''; +jest.setTimeout(SsrUtils.DEFAULT_SSR_TIMEOUT); // set timeout to at least 1x DEFAULT_SSR_TIMEOUT seconds for each test in this file to increase stability of the tests + describe('SSR E2E', () => { - let proxy: any; - const REQUEST_PATH = '/electronics-spa/en/USD/'; + let backendProxy: Server; + const REQUEST_PATH = '/contact'; // path to the page that is less "busy" than the homepage - beforeEach(async () => { - Log.clearSsrLogFile(); - await Ssr.startSsrServer(); + beforeEach(() => { + LogUtils.clearSsrLogFile(); }); afterEach(async () => { - await proxy.close(); - await Ssr.killSsrServer(); + backendProxy.close(); + await SsrUtils.killSsrServer(); }); - it('should receive success response with request', async () => { - proxy = await ProxyServer.startProxyServer({ - target: BACKEND_BASE_URL, - }); - const response: any = await ProxyServer.sendRequest(REQUEST_PATH); - expect(response.statusCode).toEqual(200); - - // Rendering should not complete in the first request. - // App should fall back to csr. - Log.assertMessages([ - `Rendering started (${REQUEST_PATH})`, - `Request is waiting for the SSR rendering to complete (${REQUEST_PATH})`, - ]); - }); + describe('With SSR error handling', () => { + describe('Common behavior', () => { + beforeEach(async () => { + await SsrUtils.startSsrServer(); + }); + + it('should receive success response with request', async () => { + backendProxy = await ProxyUtils.startBackendProxyServer({ + target: BACKEND_BASE_URL, + }); + const response: any = + await HttpUtils.sendRequestToSsrServer(REQUEST_PATH); + expect(response.statusCode).toEqual(200); + + expectLogMessages().toContainLogs([ + `Rendering started (${REQUEST_PATH})`, + `Request is waiting for the SSR rendering to complete (${REQUEST_PATH})`, + ]); + }); + + it('should receive response with 404 when page does not exist', async () => { + backendProxy = await ProxyUtils.startBackendProxyServer({ + target: BACKEND_BASE_URL, + }); + const response = await HttpUtils.sendRequestToSsrServer( + REQUEST_PATH + 'not-existing-page' + ); + expect(response.statusCode).toEqual(404); + }); + + it('should receive response with status 404 if HTTP error occurred when calling cms/pages API URL', async () => { + backendProxy = await ProxyUtils.startBackendProxyServer({ + target: BACKEND_BASE_URL, + callback: (proxyRes, req) => { + if (req.url?.includes('cms/pages')) { + proxyRes.statusCode = 404; + } + }, + }); + const response = await HttpUtils.sendRequestToSsrServer(REQUEST_PATH); + expect(response.statusCode).toEqual(404); + }); - it('should receive 404 response when page is not existing', async () => { - proxy = await ProxyServer.startProxyServer({ - target: BACKEND_BASE_URL, + it('should receive response with status 500 if HTTP error occurred when calling other than cms/pages API URL', async () => { + backendProxy = await ProxyUtils.startBackendProxyServer({ + target: BACKEND_BASE_URL, + callback: (proxyRes, req) => { + if (req.url?.includes('cms/components')) { + proxyRes.statusCode = 404; + } + }, + }); + const response = await HttpUtils.sendRequestToSsrServer(REQUEST_PATH); + expect(response.statusCode).toEqual(500); + }); }); - const response: any = await ProxyServer.sendRequest( - REQUEST_PATH + '/not-existing-page' - ); - expect(response.statusCode).toEqual(404); - }); - it('should receive 500 error response when a backend API returned server error', async () => { - proxy = await ProxyServer.startProxyServer({ - target: BACKEND_BASE_URL, - throwStatus: 500, + describe('With caching enabled', () => { + beforeEach(async () => { + await SsrUtils.startSsrServer({ cache: true }); + }); + + it( + 'should take the response from cache for the next request if previous render succeeded', + async () => { + backendProxy = await ProxyUtils.startBackendProxyServer({ + target: BACKEND_BASE_URL, + }); + let response: HttpUtils.SsrResponse; + response = await HttpUtils.sendRequestToSsrServer(REQUEST_PATH); + expect(response.statusCode).toEqual(200); + + expectLogMessages().toContainLogs([ + `Rendering started (${REQUEST_PATH})`, + `Request is waiting for the SSR rendering to complete (${REQUEST_PATH})`, + ]); + + response = await HttpUtils.sendRequestToSsrServer(REQUEST_PATH); + expect(response.statusCode).toEqual(200); + expectLogMessages().toContain(`Render from cache (${REQUEST_PATH})`); + }, + 2 * SsrUtils.DEFAULT_SSR_TIMEOUT // increase timeout for this test as it calls the SSR server twice + ); + + it( + 'should render for the next request if previous render failed', + async () => { + backendProxy = await ProxyUtils.startBackendProxyServer({ + target: BACKEND_BASE_URL, + callback: (proxyRes, req) => { + if (req.url?.includes('cms/pages')) { + proxyRes.statusCode = 404; + } + }, + }); + let response: HttpUtils.SsrResponse; + response = await HttpUtils.sendRequestToSsrServer(REQUEST_PATH); + expect(response.statusCode).toEqual(404); + + response = await HttpUtils.sendRequestToSsrServer(REQUEST_PATH); + expect(response.statusCode).toEqual(404); + expectLogMessages().not.toContain( + `Render from cache (${REQUEST_PATH})` + ); + }, + 2 * SsrUtils.DEFAULT_SSR_TIMEOUT // increase timeout for this test as it calls the SSR server twice + ); }); - const response: any = await ProxyServer.sendRequest('/'); - expect(response.statusCode).toEqual(500); }); }); diff --git a/projects/ssr-tests/src/utils/http.utils.ts b/projects/ssr-tests/src/utils/http.utils.ts new file mode 100644 index 00000000000..504abe5b4b8 --- /dev/null +++ b/projects/ssr-tests/src/utils/http.utils.ts @@ -0,0 +1,51 @@ +/* + * SPDX-FileCopyrightText: 2024 SAP Spartacus team + * + * SPDX-License-Identifier: Apache-2.0 + */ + +import * as http from 'http'; + +/** + * Default settings to send http requests. + */ +const REQUEST_OPTIONS = { + host: 'localhost', + port: 4000, +}; + +/** + * Response from SSR server. + */ +export interface SsrResponse { + statusCode: number | undefined; + headers: http.IncomingHttpHeaders; + body: string; +} + +/** + * Send an http GET request with given URL to the SSR server. + */ +export async function sendRequestToSsrServer( + path: string +): Promise { + return new Promise((resolve, reject) => { + http + .get({ ...REQUEST_OPTIONS, path }, (res) => { + let body = ''; + res.on('data', (chunk) => { + body += chunk; + }); + res.on('end', () => { + resolve({ + statusCode: res.statusCode, + headers: res.headers, + body, + }); + }); + }) + .on('error', (e: Error) => { + reject(e); + }); + }); +} diff --git a/projects/ssr-tests/src/log.utils.ts b/projects/ssr-tests/src/utils/log.utils.ts similarity index 84% rename from projects/ssr-tests/src/log.utils.ts rename to projects/ssr-tests/src/utils/log.utils.ts index 9e6284c5c5f..91d03bcdfce 100644 --- a/projects/ssr-tests/src/log.utils.ts +++ b/projects/ssr-tests/src/utils/log.utils.ts @@ -43,21 +43,12 @@ export function getLogMessages(): string[] { // We're interested only in JSON logs from Spartacus SSR app. // We ignore plain text logs coming from other sources, like `Node Express server listening on http://localhost:4200` .filter((text: string) => text.charAt(0) === '{') - .map((text: any) => JSON.parse(text).message) + .map((text: any) => { + return JSON.parse(text).message; + }) ); } -/** - * Check that log contains expected messages in string array. - * Fail test if log does not contain expected messages. - */ -export function assertMessages(expected: string[]): void { - const messages = getLogMessages(); - for (const message of expected) { - expect(messages).toContain(message); - } -} - /** * Check log every interval to see if log contains text. * Keeps waiting until log contains text or test times out. diff --git a/projects/ssr-tests/src/utils/proxy.utils.ts b/projects/ssr-tests/src/utils/proxy.utils.ts new file mode 100644 index 00000000000..931ef6ccfd0 --- /dev/null +++ b/projects/ssr-tests/src/utils/proxy.utils.ts @@ -0,0 +1,61 @@ +/* + * SPDX-FileCopyrightText: 2023 SAP Spartacus team + * SPDX-FileCopyrightText: 2024 SAP Spartacus team + * + * SPDX-License-Identifier: Apache-2.0 + */ + +import * as http from 'http'; +import httpProxy from 'http-proxy'; + +/** + * Options to start a proxy server. + */ +interface ProxyOptions { + /** + * The url to reroute requests to. + */ + target: string; + /** + * Number of seconds to delay requests before sending. + */ + delay?: number; + /** + * Callback to be executed if the request to the target got a response. + */ + callback?: httpProxy.ProxyResCallback; +} + +/** + * Starts an http proxy server on port 9002 with the provided options. + */ +export async function startBackendProxyServer( + options: ProxyOptions +): Promise { + const proxy = httpProxy.createProxyServer({ + secure: false, + }); + return new Promise((resolve) => { + const server = http.createServer((req, res) => { + const forwardRequest = () => + proxy.web(req, res, { target: options.target }); + + if (options.callback) { + // Add one-time listener for the proxy response that stays until `proxyRes` event is triggered next time. + proxy.once('proxyRes', options.callback); + } + + if (options.delay) { + setTimeout(() => { + forwardRequest(); + }, options.delay); + } else { + forwardRequest(); + } + }); + + server.listen(9002, () => { + resolve(server); + }); + }); +} diff --git a/projects/ssr-tests/src/ssr.utils.ts b/projects/ssr-tests/src/utils/ssr.utils.ts similarity index 58% rename from projects/ssr-tests/src/ssr.utils.ts rename to projects/ssr-tests/src/utils/ssr.utils.ts index 167751e84ba..3e5c01b671d 100644 --- a/projects/ssr-tests/src/ssr.utils.ts +++ b/projects/ssr-tests/src/utils/ssr.utils.ts @@ -12,6 +12,29 @@ import * as childProcess from 'child_process'; import * as Log from './log.utils'; +/** + * Default timeout for SSR rendering to happen. + */ +export const DEFAULT_SSR_TIMEOUT = 20000; + +export interface SsrServerOptions { + /** + * The port the server should run on. + * Default is 4000. + */ + port?: number; + /** + * Whether to enable caching on the server. + * Default is false. + */ + cache?: boolean; + /** + * Time in milliseconds to wait for SSR rendering to happen. + * Default is 20000. + */ + timeout?: number; +} + /** * Used to track the spawned child process running the server. */ @@ -20,11 +43,15 @@ let child: childProcess.ChildProcess | any; /** * Start an ssr server instance at the given port (default 4000). * The server will output a log file at the test project root named ".ssr.log". - * Funtion finishes once the server is initialized. + * Function finishes once the server is initialized. */ -export async function startSsrServer(port = 4000) { +export async function startSsrServer({ + port = 4000, + cache = false, + timeout = DEFAULT_SSR_TIMEOUT, +}: SsrServerOptions = {}) { child = childProcess.spawn( - `NODE_TLS_REJECT_UNAUTHORIZED=0 PORT=${port} npm run serve:ssr --prefix ../../> .ssr.log`, + `NODE_TLS_REJECT_UNAUTHORIZED=0 SSR_CACHE=${cache} SSR_TIMEOUT=${timeout} PORT=${port} npm run serve:ssr --prefix ../../> .ssr.log`, { detached: true, shell: true } ); diff --git a/projects/ssr-tests/tsconfig.json b/projects/ssr-tests/tsconfig.json index 74bac1c56e0..8da3769aebe 100644 --- a/projects/ssr-tests/tsconfig.json +++ b/projects/ssr-tests/tsconfig.json @@ -17,6 +17,5 @@ "strictNullChecks": true, "resolveJsonModule": true, "esModuleInterop": true - }, - "include": ["src/**.ts"] + } } diff --git a/projects/storefrontapp-e2e-cypress/cypress/e2e/regression/my-account/accessibility/my-account-v2-email-management.ts b/projects/storefrontapp-e2e-cypress/cypress/e2e/regression/my-account/accessibility/my-account-v2-email-management.ts index c8c7247a2c5..75bc08b6e3d 100644 --- a/projects/storefrontapp-e2e-cypress/cypress/e2e/regression/my-account/accessibility/my-account-v2-email-management.ts +++ b/projects/storefrontapp-e2e-cypress/cypress/e2e/regression/my-account/accessibility/my-account-v2-email-management.ts @@ -1,5 +1,4 @@ /* - * SPDX-FileCopyrightText: 2023 SAP Spartacus team * SPDX-FileCopyrightText: 2024 SAP Spartacus team * * SPDX-License-Identifier: Apache-2.0 diff --git a/projects/storefrontapp-e2e-cypress/cypress/e2e/regression/my-account/accessibility/my-account-v2-profile-management.ts b/projects/storefrontapp-e2e-cypress/cypress/e2e/regression/my-account/accessibility/my-account-v2-profile-management.ts index a21125ba74b..bfe3d090c3d 100644 --- a/projects/storefrontapp-e2e-cypress/cypress/e2e/regression/my-account/accessibility/my-account-v2-profile-management.ts +++ b/projects/storefrontapp-e2e-cypress/cypress/e2e/regression/my-account/accessibility/my-account-v2-profile-management.ts @@ -1,5 +1,4 @@ /* - * SPDX-FileCopyrightText: 2023 SAP Spartacus team * SPDX-FileCopyrightText: 2024 SAP Spartacus team * * SPDX-License-Identifier: Apache-2.0 diff --git a/projects/storefrontapp-e2e-cypress/cypress/e2e/regression/my-account/accessibility/tabbing-order.config.ts b/projects/storefrontapp-e2e-cypress/cypress/e2e/regression/my-account/accessibility/tabbing-order.config.ts index af64c5dfb4d..920091a5bcb 100644 --- a/projects/storefrontapp-e2e-cypress/cypress/e2e/regression/my-account/accessibility/tabbing-order.config.ts +++ b/projects/storefrontapp-e2e-cypress/cypress/e2e/regression/my-account/accessibility/tabbing-order.config.ts @@ -1,13 +1,12 @@ /* - * SPDX-FileCopyrightText: 2023 SAP Spartacus team * SPDX-FileCopyrightText: 2024 SAP Spartacus team * * SPDX-License-Identifier: Apache-2.0 */ import { - TabbingOrderTypes, TabbingOrderConfig, + TabbingOrderTypes, } from '../../../../helpers/accessibility/tabbing-order.model'; export const tabbingOrderConfig: TabbingOrderConfig = { diff --git a/projects/storefrontapp-e2e-cypress/cypress/e2e/regression/my-account/accessibility/tabbing-order.e2e-my-account-v2.cy.ts b/projects/storefrontapp-e2e-cypress/cypress/e2e/regression/my-account/accessibility/tabbing-order.e2e-my-account-v2.cy.ts index a19d1baf7d5..c2cad46bf44 100644 --- a/projects/storefrontapp-e2e-cypress/cypress/e2e/regression/my-account/accessibility/tabbing-order.e2e-my-account-v2.cy.ts +++ b/projects/storefrontapp-e2e-cypress/cypress/e2e/regression/my-account/accessibility/tabbing-order.e2e-my-account-v2.cy.ts @@ -1,5 +1,4 @@ /* - * SPDX-FileCopyrightText: 2023 SAP Spartacus team * SPDX-FileCopyrightText: 2024 SAP Spartacus team * * SPDX-License-Identifier: Apache-2.0 diff --git a/projects/storefrontapp-e2e-cypress/cypress/e2e/regression/my-account/my-account-v2-consent-management.e2e.cy.ts b/projects/storefrontapp-e2e-cypress/cypress/e2e/regression/my-account/my-account-v2-consent-management.e2e.cy.ts index 6af9b31fc16..1c4b13e57a9 100644 --- a/projects/storefrontapp-e2e-cypress/cypress/e2e/regression/my-account/my-account-v2-consent-management.e2e.cy.ts +++ b/projects/storefrontapp-e2e-cypress/cypress/e2e/regression/my-account/my-account-v2-consent-management.e2e.cy.ts @@ -1,5 +1,4 @@ /* - * SPDX-FileCopyrightText: 2023 SAP Spartacus team * SPDX-FileCopyrightText: 2024 SAP Spartacus team * * SPDX-License-Identifier: Apache-2.0 diff --git a/projects/storefrontapp-e2e-cypress/cypress/e2e/regression/my-account/my-account-v2-email.e2e.cy.ts b/projects/storefrontapp-e2e-cypress/cypress/e2e/regression/my-account/my-account-v2-email.e2e.cy.ts index b187ac61267..0834d5dcd59 100644 --- a/projects/storefrontapp-e2e-cypress/cypress/e2e/regression/my-account/my-account-v2-email.e2e.cy.ts +++ b/projects/storefrontapp-e2e-cypress/cypress/e2e/regression/my-account/my-account-v2-email.e2e.cy.ts @@ -1,5 +1,4 @@ /* - * SPDX-FileCopyrightText: 2023 SAP Spartacus team * SPDX-FileCopyrightText: 2024 SAP Spartacus team * * SPDX-License-Identifier: Apache-2.0 diff --git a/projects/storefrontapp-e2e-cypress/cypress/e2e/regression/my-account/my-account-v2-notification-preference.e2e.cy.ts b/projects/storefrontapp-e2e-cypress/cypress/e2e/regression/my-account/my-account-v2-notification-preference.e2e.cy.ts index 71de54e30ec..367f0658a8a 100644 --- a/projects/storefrontapp-e2e-cypress/cypress/e2e/regression/my-account/my-account-v2-notification-preference.e2e.cy.ts +++ b/projects/storefrontapp-e2e-cypress/cypress/e2e/regression/my-account/my-account-v2-notification-preference.e2e.cy.ts @@ -1,5 +1,4 @@ /* - * SPDX-FileCopyrightText: 2023 SAP Spartacus team * SPDX-FileCopyrightText: 2024 SAP Spartacus team * * SPDX-License-Identifier: Apache-2.0 diff --git a/projects/storefrontapp-e2e-cypress/cypress/e2e/regression/my-account/my-account-v2-password.e2e.cy.ts b/projects/storefrontapp-e2e-cypress/cypress/e2e/regression/my-account/my-account-v2-password.e2e.cy.ts index aa9ff1db434..9c40565e39f 100644 --- a/projects/storefrontapp-e2e-cypress/cypress/e2e/regression/my-account/my-account-v2-password.e2e.cy.ts +++ b/projects/storefrontapp-e2e-cypress/cypress/e2e/regression/my-account/my-account-v2-password.e2e.cy.ts @@ -1,5 +1,4 @@ /* - * SPDX-FileCopyrightText: 2023 SAP Spartacus team * SPDX-FileCopyrightText: 2024 SAP Spartacus team * * SPDX-License-Identifier: Apache-2.0 diff --git a/projects/storefrontapp-e2e-cypress/cypress/e2e/regression/my-account/my-account-v2-profile.e2e.cy.ts b/projects/storefrontapp-e2e-cypress/cypress/e2e/regression/my-account/my-account-v2-profile.e2e.cy.ts index ee359627742..bb558efcff6 100644 --- a/projects/storefrontapp-e2e-cypress/cypress/e2e/regression/my-account/my-account-v2-profile.e2e.cy.ts +++ b/projects/storefrontapp-e2e-cypress/cypress/e2e/regression/my-account/my-account-v2-profile.e2e.cy.ts @@ -1,12 +1,11 @@ /* - * SPDX-FileCopyrightText: 2023 SAP Spartacus team * SPDX-FileCopyrightText: 2024 SAP Spartacus team * * SPDX-License-Identifier: Apache-2.0 */ -import * as updateProfile from '../../../helpers/update-profile'; import * as login from '../../../helpers/login'; +import * as updateProfile from '../../../helpers/update-profile'; import { viewportContext } from '../../../helpers/viewport-context'; describe('My Account - Update Profile', () => { diff --git a/projects/storefrontapp-e2e-cypress/cypress/helpers/accessibility/tabbing-order/my-account/my-account-v2-consent-management.ts b/projects/storefrontapp-e2e-cypress/cypress/helpers/accessibility/tabbing-order/my-account/my-account-v2-consent-management.ts index 087b046ac32..108c2356b16 100644 --- a/projects/storefrontapp-e2e-cypress/cypress/helpers/accessibility/tabbing-order/my-account/my-account-v2-consent-management.ts +++ b/projects/storefrontapp-e2e-cypress/cypress/helpers/accessibility/tabbing-order/my-account/my-account-v2-consent-management.ts @@ -1,5 +1,4 @@ /* - * SPDX-FileCopyrightText: 2023 SAP Spartacus team * SPDX-FileCopyrightText: 2024 SAP Spartacus team * * SPDX-License-Identifier: Apache-2.0 diff --git a/projects/storefrontapp-e2e-cypress/cypress/helpers/accessibility/tabbing-order/my-account/my-account-v2-notification-preference.ts b/projects/storefrontapp-e2e-cypress/cypress/helpers/accessibility/tabbing-order/my-account/my-account-v2-notification-preference.ts index 64a66fb5ded..28859d083af 100644 --- a/projects/storefrontapp-e2e-cypress/cypress/helpers/accessibility/tabbing-order/my-account/my-account-v2-notification-preference.ts +++ b/projects/storefrontapp-e2e-cypress/cypress/helpers/accessibility/tabbing-order/my-account/my-account-v2-notification-preference.ts @@ -1,5 +1,4 @@ /* - * SPDX-FileCopyrightText: 2023 SAP Spartacus team * SPDX-FileCopyrightText: 2024 SAP Spartacus team * * SPDX-License-Identifier: Apache-2.0 diff --git a/projects/storefrontapp-e2e-cypress/cypress/helpers/accessibility/tabbing-order/my-account/my-account-v2-password.ts b/projects/storefrontapp-e2e-cypress/cypress/helpers/accessibility/tabbing-order/my-account/my-account-v2-password.ts index 177aa9c57db..a857d99a2ea 100644 --- a/projects/storefrontapp-e2e-cypress/cypress/helpers/accessibility/tabbing-order/my-account/my-account-v2-password.ts +++ b/projects/storefrontapp-e2e-cypress/cypress/helpers/accessibility/tabbing-order/my-account/my-account-v2-password.ts @@ -1,5 +1,4 @@ /* - * SPDX-FileCopyrightText: 2023 SAP Spartacus team * SPDX-FileCopyrightText: 2024 SAP Spartacus team * * SPDX-License-Identifier: Apache-2.0 diff --git a/projects/storefrontapp/server.ts b/projects/storefrontapp/server.ts index 470661211a2..77ac8f6a53a 100644 --- a/projects/storefrontapp/server.ts +++ b/projects/storefrontapp/server.ts @@ -8,12 +8,13 @@ import { APP_BASE_HREF } from '@angular/common'; import { NgExpressEngineDecorator, SsrOptimizationOptions, + defaultExpressErrorHandlers, defaultSsrOptimizationOptions, ngExpressEngine as engine, } from '@spartacus/setup/ssr'; import express from 'express'; -import { existsSync } from 'node:fs'; +import { existsSync, readFileSync } from 'node:fs'; import { join } from 'path'; import 'zone.js/node'; import AppServerModule from './src/main.server'; @@ -22,6 +23,10 @@ const ssrOptions: SsrOptimizationOptions = { timeout: Number( process.env['SSR_TIMEOUT'] ?? defaultSsrOptimizationOptions.timeout ), + cache: process.env['SSR_CACHE'] === 'true', + ssrFeatureToggles: { + avoidCachingErrors: true, + }, }; const ngExpressEngine = NgExpressEngineDecorator.get(engine, ssrOptions); @@ -33,6 +38,7 @@ export function app(): express.Express { const indexHtml = existsSync(join(distFolder, 'index.original.html')) ? join(distFolder, 'index.original.html') : join(distFolder, 'index.html'); + const indexHtmlContent = readFileSync(indexHtml, 'utf-8'); server.set('trust proxy', 'loopback'); @@ -63,6 +69,8 @@ export function app(): express.Express { }); }); + server.use(defaultExpressErrorHandlers(indexHtmlContent)); + return server; } diff --git a/projects/storefrontapp/src/app/spartacus/features/quote-feature.module.ts b/projects/storefrontapp/src/app/spartacus/features/quote-feature.module.ts index e9562a08414..744728d5a1c 100644 --- a/projects/storefrontapp/src/app/spartacus/features/quote-feature.module.ts +++ b/projects/storefrontapp/src/app/spartacus/features/quote-feature.module.ts @@ -1,5 +1,4 @@ /* - * SPDX-FileCopyrightText: 2023 SAP Spartacus team * SPDX-FileCopyrightText: 2024 SAP Spartacus team * * SPDX-License-Identifier: Apache-2.0 diff --git a/projects/storefrontapp/src/app/spartacus/spartacus-features.module.ts b/projects/storefrontapp/src/app/spartacus/spartacus-features.module.ts index 678896c6a4a..36285893250 100644 --- a/projects/storefrontapp/src/app/spartacus/spartacus-features.module.ts +++ b/projects/storefrontapp/src/app/spartacus/spartacus-features.module.ts @@ -78,6 +78,7 @@ import { CustomerTicketingFeatureModule } from './features/customer-ticketing/cu import { DigitalPaymentsFeatureModule } from './features/digital-payments/digital-payments-feature.module'; import { EpdVisualizationFeatureModule } from './features/epd-visualization/epd-visualization-feature.module'; import { EstimatedDeliveryDateFeatureModule } from './features/estimated-delivery-date/estimated-delivery-date-feature.module'; +import { OmfFeatureModule } from './features/omf/omf-feature.module'; import { OppsFeatureModule } from './features/opps/opps-feature.module'; import { OrderFeatureModule } from './features/order/order-feature.module'; import { AccountSummaryFeatureModule } from './features/organization/organization-account-summary-feature.module'; @@ -88,6 +89,8 @@ import { PDFInvoicesFeatureModule } from './features/pdf-invoices/pdf-invoices-f import { PickupInStoreFeatureModule } from './features/pickup-in-store/pickup-in-store-feature.module'; import { ProductConfiguratorRulebasedFeatureModule } from './features/product-configurator/product-configurator-rulebased-feature.module'; import { ProductConfiguratorTextfieldFeatureModule } from './features/product-configurator/product-configurator-textfield-feature.module'; +import { ProductMultiDimensionalListFeatureModule } from './features/product-multi-dimensional/product-multi-dimensional-list-feature.module'; +import { ProductMultiDimensionalSelectorFeatureModule } from './features/product-multi-dimensional/product-multi-dimensional-selector-feature.module'; import { BulkPricingFeatureModule } from './features/product/product-bulk-pricing-feature.module'; import { FutureStockFeatureModule } from './features/product/product-future-stock-feature.module'; import { ImageZoomFeatureModule } from './features/product/product-image-zoom-feature.module'; @@ -103,9 +106,6 @@ import { SmartEditFeatureModule } from './features/smartedit/smartedit-feature.m import { StorefinderFeatureModule } from './features/storefinder/storefinder-feature.module'; import { TrackingFeatureModule } from './features/tracking/tracking-feature.module'; import { UserFeatureModule } from './features/user/user-feature.module'; -import { OmfFeatureModule } from './features/omf/omf-feature.module'; -import { ProductMultiDimensionalSelectorFeatureModule } from './features/product-multi-dimensional/product-multi-dimensional-selector-feature.module'; -import { ProductMultiDimensionalListFeatureModule } from './features/product-multi-dimensional/product-multi-dimensional-list-feature.module'; const featureModules = []; @@ -297,6 +297,8 @@ if (environment.cpq) { storeFrontLibCardParagraphTruncated: true, useProductCarouselBatchApi: true, productConfiguratorAttributeTypesV2: true, + propagateErrorsToServer: true, + ssrStrictErrorHandlingForHttpAndNgrx: true, productConfiguratorDeltaRendering: true, a11yRequiredAsterisks: true, a11yQuantityOrderTabbing: true, diff --git a/projects/storefrontlib/cms-components/myaccount/my-account-v2/my-account-v2-consent-management/components/consent-form/my-account-v2-consent-management-form.component.ts b/projects/storefrontlib/cms-components/myaccount/my-account-v2/my-account-v2-consent-management/components/consent-form/my-account-v2-consent-management-form.component.ts index 60464a77ba9..0ee5481af9d 100644 --- a/projects/storefrontlib/cms-components/myaccount/my-account-v2/my-account-v2-consent-management/components/consent-form/my-account-v2-consent-management-form.component.ts +++ b/projects/storefrontlib/cms-components/myaccount/my-account-v2/my-account-v2-consent-management/components/consent-form/my-account-v2-consent-management-form.component.ts @@ -1,5 +1,4 @@ /* - * SPDX-FileCopyrightText: 2023 SAP Spartacus team * SPDX-FileCopyrightText: 2024 SAP Spartacus team * * SPDX-License-Identifier: Apache-2.0 diff --git a/projects/storefrontlib/cms-components/myaccount/my-account-v2/my-account-v2-consent-management/components/my-account-v2-consent-management.component.ts b/projects/storefrontlib/cms-components/myaccount/my-account-v2/my-account-v2-consent-management/components/my-account-v2-consent-management.component.ts index fc7b34f3e51..3997e86821c 100644 --- a/projects/storefrontlib/cms-components/myaccount/my-account-v2/my-account-v2-consent-management/components/my-account-v2-consent-management.component.ts +++ b/projects/storefrontlib/cms-components/myaccount/my-account-v2/my-account-v2-consent-management/components/my-account-v2-consent-management.component.ts @@ -1,5 +1,4 @@ /* - * SPDX-FileCopyrightText: 2023 SAP Spartacus team * SPDX-FileCopyrightText: 2024 SAP Spartacus team * * SPDX-License-Identifier: Apache-2.0 diff --git a/projects/storefrontlib/cms-components/myaccount/my-account-v2/my-account-v2-consent-management/index.ts b/projects/storefrontlib/cms-components/myaccount/my-account-v2/my-account-v2-consent-management/index.ts index c51abfaac02..7383ae2b91e 100644 --- a/projects/storefrontlib/cms-components/myaccount/my-account-v2/my-account-v2-consent-management/index.ts +++ b/projects/storefrontlib/cms-components/myaccount/my-account-v2/my-account-v2-consent-management/index.ts @@ -1,5 +1,4 @@ /* - * SPDX-FileCopyrightText: 2023 SAP Spartacus team * SPDX-FileCopyrightText: 2024 SAP Spartacus team * * SPDX-License-Identifier: Apache-2.0 diff --git a/projects/storefrontlib/cms-components/myaccount/my-account-v2/my-account-v2-consent-management/my-account-v2-consent-management.module.ts b/projects/storefrontlib/cms-components/myaccount/my-account-v2/my-account-v2-consent-management/my-account-v2-consent-management.module.ts index a23c1cc4eb8..be41a90b59a 100644 --- a/projects/storefrontlib/cms-components/myaccount/my-account-v2/my-account-v2-consent-management/my-account-v2-consent-management.module.ts +++ b/projects/storefrontlib/cms-components/myaccount/my-account-v2/my-account-v2-consent-management/my-account-v2-consent-management.module.ts @@ -1,5 +1,4 @@ /* - * SPDX-FileCopyrightText: 2023 SAP Spartacus team * SPDX-FileCopyrightText: 2024 SAP Spartacus team * * SPDX-License-Identifier: Apache-2.0 diff --git a/projects/storefrontlib/cms-components/myaccount/my-account-v2/my-account-v2-notification-preference/index.ts b/projects/storefrontlib/cms-components/myaccount/my-account-v2/my-account-v2-notification-preference/index.ts index 74a80738c57..1f6cd952968 100644 --- a/projects/storefrontlib/cms-components/myaccount/my-account-v2/my-account-v2-notification-preference/index.ts +++ b/projects/storefrontlib/cms-components/myaccount/my-account-v2/my-account-v2-notification-preference/index.ts @@ -1,5 +1,4 @@ /* - * SPDX-FileCopyrightText: 2023 SAP Spartacus team * SPDX-FileCopyrightText: 2024 SAP Spartacus team * * SPDX-License-Identifier: Apache-2.0 diff --git a/projects/storefrontlib/cms-components/myaccount/my-account-v2/my-account-v2-notification-preference/my-account-v2-notification-preference.component.ts b/projects/storefrontlib/cms-components/myaccount/my-account-v2/my-account-v2-notification-preference/my-account-v2-notification-preference.component.ts index b058d4a6d8d..0358570ab44 100644 --- a/projects/storefrontlib/cms-components/myaccount/my-account-v2/my-account-v2-notification-preference/my-account-v2-notification-preference.component.ts +++ b/projects/storefrontlib/cms-components/myaccount/my-account-v2/my-account-v2-notification-preference/my-account-v2-notification-preference.component.ts @@ -1,5 +1,4 @@ /* - * SPDX-FileCopyrightText: 2023 SAP Spartacus team * SPDX-FileCopyrightText: 2024 SAP Spartacus team * * SPDX-License-Identifier: Apache-2.0 diff --git a/projects/storefrontlib/cms-components/myaccount/my-account-v2/my-account-v2-notification-preference/my-account-v2-notification-preference.module.ts b/projects/storefrontlib/cms-components/myaccount/my-account-v2/my-account-v2-notification-preference/my-account-v2-notification-preference.module.ts index 43287c4070a..51c70ee3251 100644 --- a/projects/storefrontlib/cms-components/myaccount/my-account-v2/my-account-v2-notification-preference/my-account-v2-notification-preference.module.ts +++ b/projects/storefrontlib/cms-components/myaccount/my-account-v2/my-account-v2-notification-preference/my-account-v2-notification-preference.module.ts @@ -1,5 +1,4 @@ /* - * SPDX-FileCopyrightText: 2023 SAP Spartacus team * SPDX-FileCopyrightText: 2024 SAP Spartacus team * * SPDX-License-Identifier: Apache-2.0 diff --git a/projects/storefrontlib/cms-components/myaccount/my-account-v2/use-my-account-v2-consent-notification-perference.ts b/projects/storefrontlib/cms-components/myaccount/my-account-v2/use-my-account-v2-consent-notification-perference.ts index 9fd162949f2..8947f04363e 100644 --- a/projects/storefrontlib/cms-components/myaccount/my-account-v2/use-my-account-v2-consent-notification-perference.ts +++ b/projects/storefrontlib/cms-components/myaccount/my-account-v2/use-my-account-v2-consent-notification-perference.ts @@ -1,5 +1,4 @@ /* - * SPDX-FileCopyrightText: 2023 SAP Spartacus team * SPDX-FileCopyrightText: 2024 SAP Spartacus team * * SPDX-License-Identifier: Apache-2.0 diff --git a/projects/storefrontlib/shared/components/media/media-sources.pipe.spec.ts b/projects/storefrontlib/shared/components/media/media-sources.pipe.spec.ts index 180f64edddc..c2107601c62 100644 --- a/projects/storefrontlib/shared/components/media/media-sources.pipe.spec.ts +++ b/projects/storefrontlib/shared/components/media/media-sources.pipe.spec.ts @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2023 SAP Spartacus team + * SPDX-FileCopyrightText: 2024 SAP Spartacus team * * SPDX-License-Identifier: Apache-2.0 */ diff --git a/tools/eslint-rules/index.ts b/tools/eslint-rules/index.ts index 7489e4b58e2..a7cedb0a7ba 100644 --- a/tools/eslint-rules/index.ts +++ b/tools/eslint-rules/index.ts @@ -24,6 +24,11 @@ import { RULE_NAME as useProvideDefaultFeatureTogglesFactoryName, } from './rules/use-provide-default-feature-toggles-factory'; +import { + rule as noNgrxFailActionWithoutErrorActionImplementation, + RULE_NAME as noNgrxFailActionWithoutErrorActionImplementationName, +} from './rules/no-ngrx-fail-action-without-error-action-implementation'; + /** * Import your custom workspace rules at the top of this file. * @@ -55,5 +60,7 @@ module.exports = { [useProvideDefaultFeatureTogglesName]: useProvideDefaultFeatureToggles, [useProvideDefaultFeatureTogglesFactoryName]: useProvideDefaultFeatureTogglesFactory, + [noNgrxFailActionWithoutErrorActionImplementationName]: + noNgrxFailActionWithoutErrorActionImplementation, }, }; diff --git a/tools/eslint-rules/rules/no-ngrx-fail-action-without-error-action-implementation.spec.ts b/tools/eslint-rules/rules/no-ngrx-fail-action-without-error-action-implementation.spec.ts new file mode 100644 index 00000000000..c81fb5d44b5 --- /dev/null +++ b/tools/eslint-rules/rules/no-ngrx-fail-action-without-error-action-implementation.spec.ts @@ -0,0 +1,178 @@ +import { convertAnnotatedSourceToFailureCase } from '@angular-eslint/utils'; +import { TSESLint } from '@typescript-eslint/utils'; +import { + rule, + RULE_NAME, +} from './no-ngrx-fail-action-without-error-action-implementation'; + +const ruleTester = new TSESLint.RuleTester({ + parser: require.resolve('@typescript-eslint/parser'), +}); + +ruleTester.run(RULE_NAME, rule, { + valid: [ + // actions with `Fail` in name, that implement `ErrorAction` + ` + import { ErrorAction } from '@spartacus/core'; + export class LoadProductFail implements ErrorAction {} + `, + ` + import { ErrorAction } from '@spartacus/core'; + export class LoadProductFail implements Action, ErrorAction {} + `, + ` + import { ErrorAction } from '@spartacus/core'; + export class LoadProductFail implements Action, SomeOtherInterface, ErrorAction {} + `, + ` + import { ErrorAction } from '@spartacus/core'; + export class LoadProductFail extends SuperClass implements ErrorAction {} + `, + ` + import { ErrorAction } from '@spartacus/core'; + export class LoadProductFail extends SuperClass implements SomeOtherInterface, ErrorAction {} + `, + + // actions without `Fail` in name, that implement `ErrorAction` + ` + import { ErrorAction } from '@spartacus/core'; + export class LoadProduct implements ErrorAction {} + `, + ` + import { ErrorAction } from '@spartacus/core'; + export class LoadProduct implements Action, ErrorAction {} + `, + ` + import { ErrorAction } from '@spartacus/core'; + export class LoadProduct implements Action, SomeOtherInterface, ErrorAction {} + `, + ` + import { ErrorAction } from '@spartacus/core'; + export class LoadProduct extends SuperClass implements ErrorAction {} + `, + ` + import { ErrorAction } from '@spartacus/core'; + export class LoadProduct extends SuperClass implements SomeOtherInterface, ErrorAction {} + `, + + // actions without `Fail` in name, that don't implement `ErrorAction` + ` + export class LoadProduct {} + `, + ` + export class LoadProduct implements Action {} + `, + ` + export class LoadProduct implements Action, SomeOtherInterface {} + `, + ` + export class LoadProduct extends SuperClass {} + `, + ` + export class LoadProduct extends SuperClass implements SomeOtherInterface {} + `, + ], + invalid: [ + convertAnnotatedSourceToFailureCase({ + description: + 'Fail action has no `implements ErrorAction`, and no other implements', + annotatedSource: ` + export class LoadProductFail { + ~~~~~~~~~~~~~~~ + } + `, + annotatedOutput: ` + import { ErrorAction } from '@spartacus/core'; +export class LoadProductFail implements ErrorAction { + + } + `, + messageId: 'missingImplementsErrorAction', + }) as TSESLint.InvalidTestCase<'missingImplementsErrorAction', never[]>, // type cast used as convertAnnotatedSourceToFailureCase simplifies testing, but TSESLint v6 requires never[] instead of readonly unknown[] + + convertAnnotatedSourceToFailureCase({ + description: + 'Fail action has no `implements ErrorAction`, but only 1 other `implements`', + annotatedSource: ` + export class LoadProductFail implements Action { + ~~~~~~~~~~~~~~~ + } + `, + annotatedOutput: ` + import { ErrorAction } from '@spartacus/core'; +export class LoadProductFail implements Action, ErrorAction { + + } + `, + messageId: 'missingImplementsErrorAction', + }) as TSESLint.InvalidTestCase<'missingImplementsErrorAction', never[]>, // type cast used as convertAnnotatedSourceToFailureCase simplifies testing, but TSESLint v6 requires never[] instead of readonly unknown[] + + convertAnnotatedSourceToFailureCase({ + description: + 'Fail action has no `implements ErrorAction`, but only 2 other `implements`', + annotatedSource: ` + export class LoadProductFail implements Action, SomeOtherInterface { + ~~~~~~~~~~~~~~~ + } + `, + annotatedOutput: ` + import { ErrorAction } from '@spartacus/core'; +export class LoadProductFail implements Action, SomeOtherInterface, ErrorAction { + + } + `, + messageId: 'missingImplementsErrorAction', + }) as TSESLint.InvalidTestCase<'missingImplementsErrorAction', never[]>, // type cast used as convertAnnotatedSourceToFailureCase simplifies testing, but TSESLint v6 requires never[] instead of readonly unknown[] + + convertAnnotatedSourceToFailureCase({ + description: + 'Fail action has no `implements ErrorAction`, but only 1 other `extends`', + annotatedSource: ` + export class LoadProductFail extends EntityScopedLoaderActions.EntityScopedFailAction { + ~~~~~~~~~~~~~~~ + } + `, + annotatedOutput: ` + import { ErrorAction } from '@spartacus/core'; +export class LoadProductFail extends EntityScopedLoaderActions.EntityScopedFailAction implements ErrorAction { + + } + `, + messageId: 'missingImplementsErrorAction', + }) as TSESLint.InvalidTestCase<'missingImplementsErrorAction', never[]>, // type cast used as convertAnnotatedSourceToFailureCase simplifies testing, but TSESLint v6 requires never[] instead of readonly unknown[] + + convertAnnotatedSourceToFailureCase({ + description: + 'Fail action has no `implements ErrorAction`, but only 1 other `extends` and 1 other `implements`', + annotatedSource: ` + export class LoadProductFail extends EntityScopedLoaderActions.EntityScopedFailAction implements Action { + ~~~~~~~~~~~~~~~ + } + `, + annotatedOutput: ` + import { ErrorAction } from '@spartacus/core'; +export class LoadProductFail extends EntityScopedLoaderActions.EntityScopedFailAction implements Action, ErrorAction { + + } + `, + messageId: 'missingImplementsErrorAction', + }) as TSESLint.InvalidTestCase<'missingImplementsErrorAction', never[]>, // type cast used as convertAnnotatedSourceToFailureCase simplifies testing, but TSESLint v6 requires never[] instead of readonly unknown[] + + convertAnnotatedSourceToFailureCase({ + description: + 'Fail action has no `implements ErrorAction`, but only 1 other `extends` and 2 other `implements`', + annotatedSource: ` + export class LoadProductFail extends EntityScopedLoaderActions.EntityScopedFailAction implements Action, SomeOtherInterface { + ~~~~~~~~~~~~~~~ + } + `, + annotatedOutput: ` + import { ErrorAction } from '@spartacus/core'; +export class LoadProductFail extends EntityScopedLoaderActions.EntityScopedFailAction implements Action, SomeOtherInterface, ErrorAction { + + } + `, + messageId: 'missingImplementsErrorAction', + }) as TSESLint.InvalidTestCase<'missingImplementsErrorAction', never[]>, // type cast used as convertAnnotatedSourceToFailureCase simplifies testing, but TSESLint v6 requires never[] instead of readonly unknown[] + ], +}); diff --git a/tools/eslint-rules/rules/no-ngrx-fail-action-without-error-action-implementation.ts b/tools/eslint-rules/rules/no-ngrx-fail-action-without-error-action-implementation.ts new file mode 100644 index 00000000000..4cd07883f54 --- /dev/null +++ b/tools/eslint-rules/rules/no-ngrx-fail-action-without-error-action-implementation.ts @@ -0,0 +1,82 @@ +/* + * SPDX-FileCopyrightText: 2024 SAP Spartacus team + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * This file sets you up with structure needed for an ESLint rule. + * + * It leverages utilities from @typescript-eslint to allow TypeScript to + * provide autocompletions etc for the configuration. + * + * Your rule's custom logic will live within the create() method below + * and you can learn more about writing ESLint rules on the official guide: + * + * https://eslint.org/docs/developer-guide/working-with-rules + * + */ + +import { ESLintUtils, TSESTree } from '@typescript-eslint/utils'; +import { + fixMissingImplementsInterface, + hasImplementsInterface, +} from './utils/implements-interface-utils'; +import { fixPossiblyMissingImport } from './utils/import-utils'; + +// NOTE: The rule will be available in ESLint configs as "@nrwl/nx/workspace/no-ngrx-fail-action-without-error-action-implementation" +export const RULE_NAME = + 'no-ngrx-fail-action-without-error-action-implementation'; + +export const rule = ESLintUtils.RuleCreator(() => __filename)({ + name: RULE_NAME, + meta: { + type: 'problem', + docs: { + description: ``, + recommended: 'recommended', + }, + schema: [], // no options + messages: { + missingImplementsErrorAction: + '[Spartacus] NgRx Failure Action class should have `implements ErrorAction`. Otherwise it might be not handled properly by `ErrorActionService`', + }, + fixable: 'code', + }, + defaultOptions: [], + create(context) { + return { + 'ClassDeclaration[id.name=/Fail/]'(node: TSESTree.ClassDeclaration) { + const Constants = { + ErrorAction: 'ErrorAction', + SpartacusCore: '@spartacus/core', + }; + + if (!hasImplementsInterface(node, Constants.ErrorAction)) { + context.report({ + node: node.id ?? node, + messageId: 'missingImplementsErrorAction', + fix(fixer) { + const sourceCode = context.sourceCode; + return [ + ...fixMissingImplementsInterface({ + node, + interfaceName: Constants.ErrorAction, + sourceCode, + fixer, + }), + + ...fixPossiblyMissingImport({ + importedIdentifier: Constants.ErrorAction, + importPath: Constants.SpartacusCore, + sourceCode, + fixer, + }), + ]; + }, + }); + } + }, + }; + }, +}); diff --git a/tools/eslint-rules/rules/utils/implements-interface-utils.spec.ts b/tools/eslint-rules/rules/utils/implements-interface-utils.spec.ts new file mode 100644 index 00000000000..356b2c210db --- /dev/null +++ b/tools/eslint-rules/rules/utils/implements-interface-utils.spec.ts @@ -0,0 +1,88 @@ +import { ESLintUtils, TSESTree } from '@typescript-eslint/utils'; + +import { TSESLint } from '@typescript-eslint/utils'; +import { + fixMissingImplementsInterface, + hasImplementsInterface, +} from './implements-interface-utils'; + +describe('implements-interface-utils', () => { + const rule = ESLintUtils.RuleCreator(() => __filename)({ + name: 'test-rule', + meta: { + messages: { + missingImplementsMyInterface: 'test error action', + }, + docs: { + description: 'test description', + }, + schema: [], + type: 'problem', + fixable: 'code', + }, + defaultOptions: [], + create(context) { + return { + ClassDeclaration(node: TSESTree.ClassDeclaration) { + const interfaceName = 'MyInterface'; // Example interface name to test + if ( + // check if interface is implemented using hasImplementsInterface utility + !hasImplementsInterface(node, interfaceName) + ) { + context.report({ + node: node.id ?? node, + messageId: 'missingImplementsMyInterface', + fix: (fixer) => { + // fix using fixMissingImplementsInterface utility + return fixMissingImplementsInterface({ + fixer, + interfaceName, + node, + sourceCode: context.sourceCode, + }); + }, + }); + } + }, + }; + }, + }); + + // Instantiate RuleTester with TypeScript parser + const ruleTester = new TSESLint.RuleTester({ + parser: require.resolve('@typescript-eslint/parser'), + parserOptions: { + ecmaVersion: 2020, + sourceType: 'module', + }, + }); + + ruleTester.run('hasImplementsInterface', rule, { + valid: [ + `class MyClass implements MyInterface {}`, + `class MyClass implements AnotherInterface, MyInterface {}`, + `class MyClass implements MyInterface, AnotherInterface {}`, + `class MyClass extends AnotherClass implements MyInterface {}`, + ], + invalid: [ + { + // if there is no superclass, it adds the new interface after the class name + code: `class MyClass {}`, + errors: [{ messageId: 'missingImplementsMyInterface' }], + output: `class MyClass implements MyInterface {}`, + }, + { + // if there are already implemented interfaces, it adds the new one at the end + code: `class MyClass implements AnotherInterface {}`, + errors: [{ messageId: 'missingImplementsMyInterface' }], + output: `class MyClass implements AnotherInterface, MyInterface {}`, + }, + { + // if there is a superclass, it adds the new interface after it + code: `class MyClass extends SuperClass {}`, + errors: [{ messageId: 'missingImplementsMyInterface' }], + output: `class MyClass extends SuperClass implements MyInterface {}`, + }, + ], + }); +}); diff --git a/tools/eslint-rules/rules/utils/implements-interface-utils.ts b/tools/eslint-rules/rules/utils/implements-interface-utils.ts new file mode 100644 index 00000000000..7d2d0a6c825 --- /dev/null +++ b/tools/eslint-rules/rules/utils/implements-interface-utils.ts @@ -0,0 +1,85 @@ +/* + * SPDX-FileCopyrightText: 2024 SAP Spartacus team + * + * SPDX-License-Identifier: Apache-2.0 + */ + +import { AST_NODE_TYPES, TSESTree } from '@typescript-eslint/utils'; +import { + RuleFix, + RuleFixer, + SourceCode, +} from '@typescript-eslint/utils/ts-eslint'; + +/** + * Tells whether the class declaration `node` has an `implements` clause with the given `interfaceName`. + */ +export function hasImplementsInterface( + node: TSESTree.ClassDeclaration, + interfaceName: string +): boolean { + return node.implements?.some( + (impl) => + impl.expression.type === AST_NODE_TYPES.Identifier && + impl.expression.name === interfaceName + ); +} + +/** + * Adds missing `implements` clause for the class declaration. + * + * - if there are already implemented interfaces, it adds the new one at the end + * - if there is a superclass, it adds the new interface after it + * - if there is no superclass, it adds the new interface after the class name + */ +export function fixMissingImplementsInterface({ + fixer, + interfaceName, + node, + sourceCode, +}: { + node: TSESTree.ClassDeclaration; + interfaceName: string; + sourceCode: SourceCode; + fixer: RuleFixer; +}): RuleFix[] { + const implementsText = createImplementsText({ + node, + interfaceName, + sourceCode, + }); + + const fixes = []; + if (node.implements?.length > 0) { + const lastImplementsNode = node.implements[node.implements.length - 1]; + fixes.push(fixer.insertTextAfter(lastImplementsNode, `, ${interfaceName}`)); + } else if (node.superClass) { + fixes.push(fixer.insertTextAfter(node.superClass, implementsText)); + } else if (node.id) { + fixes.push(fixer.insertTextAfter(node.id, implementsText)); + } + return fixes; +} + +/** + * Returns a string with the `implements` clause for the class declaration. + * + * - if there are already implemented interfaces, it adds the new one at the end + * - if there is a superclass, it adds the new interface after it + * - if there is no superclass, it adds the new interface after the class name + */ +function createImplementsText({ + node, + interfaceName, + sourceCode, +}: { + node: TSESTree.ClassDeclaration; + interfaceName: string; + sourceCode: SourceCode; +}): string { + let otherImplements = node.implements + ? node.implements.map((impl) => sourceCode.getText(impl)).join(', ') + : ''; + const optionalComma = otherImplements ? ', ' : ''; + return ` implements ${otherImplements}${optionalComma}${interfaceName}`; +} diff --git a/tools/eslint-rules/rules/utils/import-utils.spec.ts b/tools/eslint-rules/rules/utils/import-utils.spec.ts new file mode 100644 index 00000000000..89fcc75a85a --- /dev/null +++ b/tools/eslint-rules/rules/utils/import-utils.spec.ts @@ -0,0 +1,96 @@ +import { ESLintUtils, TSESTree } from '@typescript-eslint/utils'; + +import { TSESLint } from '@typescript-eslint/utils'; +import { fixPossiblyMissingImport, isIdentifierImported } from './import-utils'; + +describe('import-utils', () => { + const rule = ESLintUtils.RuleCreator(() => __filename)({ + name: 'test-rule', + meta: { + messages: { + missingIdentifierImport: 'test error action', + }, + docs: { + description: 'test description', + }, + schema: [], + type: 'problem', + fixable: 'code', + }, + defaultOptions: [], + create(context) { + return { + ClassDeclaration(node: TSESTree.ClassDeclaration) { + const superClassName = 'MySuperClass'; // Example super class name to test + if ( + // check if super class is imported using isIdentifierImported utility + !isIdentifierImported({ + importedIdentifier: superClassName, + importPath: 'my-superclass', + sourceCode: context.sourceCode, + }) + ) { + context.report({ + node: node.id ?? node, + messageId: 'missingIdentifierImport', + fix: (fixer) => { + // fix using fixPossiblyMissingImport utility + return fixPossiblyMissingImport({ + fixer, + importedIdentifier: superClassName, + importPath: 'my-superclass', + sourceCode: context.sourceCode, + }); + }, + }); + } + }, + }; + }, + }); + + const ruleTester = new TSESLint.RuleTester({ + parser: require.resolve('@typescript-eslint/parser'), + parserOptions: { + ecmaVersion: 2020, + sourceType: 'module', + }, + }); + + ruleTester.run('isIdentifierImported', rule, { + valid: [ + // This class does not implement 'MyInterface', so it's considered valid under our rule + `import { MySuperClass } from 'my-superclass'; + + class MyClass extends MySuperClass {} + `, + ], + invalid: [ + { + // adds missing import + code: ` + class MyClass extends MySuperClass {} + `, + errors: [{ messageId: 'missingIdentifierImport' }], + output: ` + import { MySuperClass } from 'my-superclass'; +class MyClass extends MySuperClass {} + `, + }, + { + // adds missing import as last import + code: ` + import { MyInterface } from 'my-interface'; + class MyClass extends MySuperClass implements MyInterface {} + `, + errors: [{ messageId: 'missingIdentifierImport' }], + output: ` + import { MyInterface } from 'my-interface'; +import { MySuperClass } from 'my-superclass'; + + class MyClass extends MySuperClass implements MyInterface {} + `, + }, + ], + }); +}); diff --git a/tools/eslint-rules/rules/utils/import-utils.ts b/tools/eslint-rules/rules/utils/import-utils.ts new file mode 100644 index 00000000000..91cfa5c77d6 --- /dev/null +++ b/tools/eslint-rules/rules/utils/import-utils.ts @@ -0,0 +1,99 @@ +/* + * SPDX-FileCopyrightText: 2024 SAP Spartacus team + * + * SPDX-License-Identifier: Apache-2.0 + */ + +import { AST_NODE_TYPES } from '@typescript-eslint/utils'; +import { + RuleFix, + RuleFixer, + SourceCode, +} from '@typescript-eslint/utils/ts-eslint'; + +/** + * Adds an import in the file for `importedIdentifier` from `importPath`, + * if it's missing in the file `sourceCode`. + */ +export function fixPossiblyMissingImport({ + fixer, + importedIdentifier, + importPath, + sourceCode, +}: { + fixer: RuleFixer; + importedIdentifier: string; + importPath: string; + sourceCode: SourceCode; +}): RuleFix[] { + if (isIdentifierImported({ sourceCode, importedIdentifier, importPath })) { + return []; + } + + return fixMissingImport({ + sourceCode, + importedIdentifier, + importPath, + fixer, + }); +} + +/** + * Tells whether the `importedIdentifier` is imported from `importPath` in the file of `sourceCode`. + */ +export function isIdentifierImported({ + importedIdentifier, + importPath, + sourceCode, +}: { + importedIdentifier: string; + importPath: string; + sourceCode: SourceCode; +}): boolean { + const importDeclarations = sourceCode.ast.body.filter( + (statement) => statement.type === AST_NODE_TYPES.ImportDeclaration + ); + return importDeclarations.some( + (declaration) => + declaration.type === AST_NODE_TYPES.ImportDeclaration && + declaration.source.value === importPath && + declaration.specifiers.some( + (specifier) => + specifier.type === AST_NODE_TYPES.ImportSpecifier && + specifier.imported.name === importedIdentifier + ) + ); +} + +/** + * Adds an import in the file for `importedIdentifier` from `importPath`, + * in the file `sourceCode`. + */ +export function fixMissingImport({ + fixer, + importedIdentifier, + importPath, + sourceCode, +}: { + fixer: RuleFixer; + importedIdentifier: string; + importPath: string; + sourceCode: SourceCode; +}): RuleFix[] { + const fixes = []; + const importStatementText = `import { ${importedIdentifier} } from '${importPath}';\n`; + const importDeclarations = sourceCode.ast.body.filter( + (statement) => statement.type === AST_NODE_TYPES.ImportDeclaration + ); + + if (importDeclarations.length > 0) { + const lastImport = importDeclarations[importDeclarations.length - 1]; + fixes.push(fixer.insertTextAfter(lastImport, `\n${importStatementText}`)); + } else { + fixes.push( + fixer.insertTextBefore(sourceCode.ast.body[0], importStatementText) + ); + } + + return fixes; +}