SAP / spartacus

Spartacus is a lean, Angular-based JavaScript storefront for SAP Commerce Cloud that communicates exclusively through the Commerce REST API.
Apache License 2.0
726 stars 372 forks source link

refactor: Handle breaking changes in NgRx actions #19013

Open Platonn opened 1 week ago

Platonn commented 1 week ago

In previous past PR within this Epic branch, we've made breaking changes - changing constructor signatures of ngrx actions from having error: any to error: <something more specific> (i.e. error: ErrorActionType). In this PR, we revert this change - allowing for error: any but at the same time deprecating it. Moreover, the non-deprecated constructor requires the value to be non-null, non-undefined.

fixes https://jira.tools.sap/browse/CXSPA-7198 (it's part 1/2; the part 2/2 - adding implements ErrorAction - will arrive in a separate PR for the breviety of this PR)

QA steps To conduct the QA steps, please add the code snippets given below to your app.module.ts (please make sure to import the pasted items; note: for auto-adding missing imports in VSCode you might find this trick helpful).

Table of contents:

  1. ⚠️ deprecated optional error (just in EntityFailAction)
  2. ⚠️ deprecated undefined error
  3. ⚠️ deprecated null error
  4. ⚠️ deprecated any error
  5. ⚠️ deprecated unknown error
  6. ✅ valid plain object error
  7. ✅ valid empty object error
  8. ✅ valid empty string error

  1. ⚠️ DEPRECATED OPTIONAL verify it's deprecated to omit the optional error? 3rd argument in the action EntityFailAction (it's the only one example with optional error? argument. the rest of actions in examples below have a required error param).
    export const entityLoaderAction_optionalLastArgError = new EntityFailAction(
    'entityType',
    'entityId'
    );

(screenshot: ) image

  1. ⚠️ DEPRECATED UNDEFINED - Verify it's deprecated to pass undefined error object to various actions, e.g. that in your VSCode the names of the constructed classes are crossed out:

    const errorUndefined = undefined;
    export const actionsUsing_errorUndefined = [
    new LoadCdcUserTokenFail({
    initialActionPayload: {
      baseSite: 'testBaseSite',
      idToken: 'testIdToken',
      signatureTimestamp: 'testSignatureTimestamp',
      UID: 'testUID',
      UIDSignature: 'testUIDSignature',
    },
    error: errorUndefined,
    }),
    new CustomerSearchFail(errorUndefined),
    new CartAddEntryFail({
    cartId: 'testCartId',
    error: errorUndefined,
    productCode: 'testProductCode',
    quantity: 1,
    userId: 'testUserId',
    pickupStore: 'testPickupStore',
    }),
    new EditSavedCartFail({
    cartId: 'testCartId',
    error: errorUndefined,
    userId: 'testUserId',
    saveCartName: 'testSaveCartName',
    }),
    new LoadWishListFail({
    cartId: 'testCartId',
    error: errorUndefined,
    }),
    new CancelReplenishmentOrderFail({
    message: 'error',
    }),
    new UnassignB2BUserUserGroupFail({
    error: errorUndefined,
    orgCustomerId: 'testOrgCustomerId',
    userGroupId: 'testUserGroupId',
    }),
    new AssignBudgetFail({
    budgetCode: 'testBudgetCode',
    error: errorUndefined,
    }),
    new LoadApprovalProcessesFail({
    error: errorUndefined,
    }),
    new LoadPermissionTypesFail({
    error: errorUndefined,
    }),
    new LoadUnitOrdersFail(errorUndefined),
    new LoadCmsComponentFail({
    error: errorUndefined,
    pageContext: { id: 'testPageId', type: PageType.CONTENT_PAGE },
    uid: 'testUid',
    }),
    new LoadCmsNavigationItemsFail('nodeId', errorUndefined),
    new LoadProductFail('productCode123', errorUndefined, 'scope'),
    new LoadUserConsentsFail(errorUndefined),
    new EntityFailAction('entityType', 'entityId', errorUndefined),
    ];

    (screenshot: ) image

  2. ⚠️ DEPREACTED NULL

    const errorNull = null;
    export const actionsUsing_errorNull = [
    new LoadCdcUserTokenFail({
    initialActionPayload: {
      baseSite: 'testBaseSite',
      idToken: 'testIdToken',
      signatureTimestamp: 'testSignatureTimestamp',
      UID: 'testUID',
      UIDSignature: 'testUIDSignature',
    },
    error: errorNull,
    }),
    new CustomerSearchFail(errorNull),
    new CartAddEntryFail({
    cartId: 'testCartId',
    error: errorNull,
    productCode: 'testProductCode',
    quantity: 1,
    userId: 'testUserId',
    pickupStore: 'testPickupStore',
    }),
    new EditSavedCartFail({
    cartId: 'testCartId',
    error: errorNull,
    userId: 'testUserId',
    saveCartName: 'testSaveCartName',
    }),
    new LoadWishListFail({
    cartId: 'testCartId',
    error: errorNull,
    }),
    new CancelReplenishmentOrderFail({
    message: 'error',
    }),
    new UnassignB2BUserUserGroupFail({
    error: errorNull,
    orgCustomerId: 'testOrgCustomerId',
    userGroupId: 'testUserGroupId',
    }),
    new AssignBudgetFail({
    budgetCode: 'testBudgetCode',
    error: errorNull,
    }),
    new LoadApprovalProcessesFail({
    error: errorNull,
    }),
    new LoadPermissionTypesFail({
    error: errorNull,
    }),
    new LoadUnitOrdersFail(errorNull),
    new LoadCmsComponentFail({
    error: errorNull,
    pageContext: { id: 'testPageId', type: PageType.CONTENT_PAGE },
    uid: 'testUid',
    }),
    new LoadCmsNavigationItemsFail('nodeId', errorNull),
    new LoadProductFail('productCode123', errorNull, 'scope'),
    new LoadUserConsentsFail(errorNull),
    new EntityFailAction('entityType', 'entityId', errorNull),
    ];

    (screenshot: ) image

  3. ⚠️ DEPRECATED ANY

    const errorAny: any = <any>{ something: 'but casted to any' };
    export const actionsUsing_errorAny = [
    new LoadCdcUserTokenFail({
    initialActionPayload: {
      baseSite: 'testBaseSite',
      idToken: 'testIdToken',
      signatureTimestamp: 'testSignatureTimestamp',
      UID: 'testUID',
      UIDSignature: 'testUIDSignature',
    },
    error: errorAny,
    }),
    new CustomerSearchFail(errorAny),
    new CartAddEntryFail({
    cartId: 'testCartId',
    error: errorAny,
    productCode: 'testProductCode',
    quantity: 1,
    userId: 'testUserId',
    pickupStore: 'testPickupStore',
    }),
    new EditSavedCartFail({
    cartId: 'testCartId',
    error: errorAny,
    userId: 'testUserId',
    saveCartName: 'testSaveCartName',
    }),
    new LoadWishListFail({
    cartId: 'testCartId',
    error: errorAny,
    }),
    new CancelReplenishmentOrderFail({
    message: 'error',
    }),
    new UnassignB2BUserUserGroupFail({
    error: errorAny,
    orgCustomerId: 'testOrgCustomerId',
    userGroupId: 'testUserGroupId',
    }),
    new AssignBudgetFail({
    budgetCode: 'testBudgetCode',
    error: errorAny,
    }),
    new LoadApprovalProcessesFail({
    error: errorAny,
    }),
    new LoadPermissionTypesFail({
    error: errorAny,
    }),
    new LoadUnitOrdersFail(errorAny),
    new LoadCmsComponentFail({
    error: errorAny,
    pageContext: { id: 'testPageId', type: PageType.CONTENT_PAGE },
    uid: 'testUid',
    }),
    new LoadCmsNavigationItemsFail('nodeId', errorAny),
    new LoadProductFail('productCode123', errorAny, 'scope'),
    new LoadUserConsentsFail(errorAny),
    new EntityFailAction('entityType', 'entityId', errorAny),
    ];

    (screenshot: ) image

  4. ⚠️ DEPRECATED UNKNOWN

    const errorUnknown: unknown = <unknown>{ something: 'but casted to unknown' };
    export const actionsUsing_errorUnknown = [
    new LoadCdcUserTokenFail({
    initialActionPayload: {
      baseSite: 'testBaseSite',
      idToken: 'testIdToken',
      signatureTimestamp: 'testSignatureTimestamp',
      UID: 'testUID',
      UIDSignature: 'testUIDSignature',
    },
    error: errorUnknown,
    }),
    new CustomerSearchFail(errorUnknown),
    new CartAddEntryFail({
    cartId: 'testCartId',
    error: errorUnknown,
    productCode: 'testProductCode',
    quantity: 1,
    userId: 'testUserId',
    pickupStore: 'testPickupStore',
    }),
    new EditSavedCartFail({
    cartId: 'testCartId',
    error: errorUnknown,
    userId: 'testUserId',
    saveCartName: 'testSaveCartName',
    }),
    new LoadWishListFail({
    cartId: 'testCartId',
    error: errorUnknown,
    }),
    new CancelReplenishmentOrderFail({
    message: 'error',
    }),
    new UnassignB2BUserUserGroupFail({
    error: errorUnknown,
    orgCustomerId: 'testOrgCustomerId',
    userGroupId: 'testUserGroupId',
    }),
    new AssignBudgetFail({
    budgetCode: 'testBudgetCode',
    error: errorUnknown,
    }),
    new LoadApprovalProcessesFail({
    error: errorUnknown,
    }),
    new LoadPermissionTypesFail({
    error: errorUnknown,
    }),
    new LoadUnitOrdersFail(errorUnknown),
    new LoadCmsComponentFail({
    error: errorUnknown,
    pageContext: { id: 'testPageId', type: PageType.CONTENT_PAGE },
    uid: 'testUid',
    }),
    new LoadCmsNavigationItemsFail('nodeId', errorUnknown),
    new LoadProductFail('productCode123', errorUnknown, 'scope'),
    new LoadUserConsentsFail(errorUnknown),
    new EntityFailAction('entityType', 'entityId', errorUnknown),
    ];

    (screenshot: ) image

  5. VALID PLAIN OBJECT

    const errorPlainObject = { message: 'error message 1' };
    export const actionsUsing_errorPlainObject = [
    new LoadCdcUserTokenFail({
    initialActionPayload: {
      baseSite: 'testBaseSite',
      idToken: 'testIdToken',
      signatureTimestamp: 'testSignatureTimestamp',
      UID: 'testUID',
      UIDSignature: 'testUIDSignature',
    },
    error: errorPlainObject,
    }),
    new CustomerSearchFail(errorPlainObject),
    new CartAddEntryFail({
    cartId: 'testCartId',
    error: errorPlainObject,
    productCode: 'testProductCode',
    quantity: 1,
    userId: 'testUserId',
    pickupStore: 'testPickupStore',
    }),
    new EditSavedCartFail({
    cartId: 'testCartId',
    error: errorPlainObject,
    userId: 'testUserId',
    saveCartName: 'testSaveCartName',
    }),
    new LoadWishListFail({
    cartId: 'testCartId',
    error: errorPlainObject,
    }),
    new CancelReplenishmentOrderFail({
    message: 'error',
    }),
    new UnassignB2BUserUserGroupFail({
    error: errorPlainObject,
    orgCustomerId: 'testOrgCustomerId',
    userGroupId: 'testUserGroupId',
    }),
    new AssignBudgetFail({
    budgetCode: 'testBudgetCode',
    error: errorPlainObject,
    }),
    new LoadApprovalProcessesFail({
    error: errorPlainObject,
    }),
    new LoadPermissionTypesFail({
    error: errorPlainObject,
    }),
    new LoadUnitOrdersFail(errorPlainObject),
    new LoadCmsComponentFail({
    error: errorPlainObject,
    pageContext: { id: 'testPageId', type: PageType.CONTENT_PAGE },
    uid: 'testUid',
    }),
    new LoadCmsNavigationItemsFail('nodeId', errorPlainObject),
    new LoadProductFail('productCode123', errorPlainObject, 'scope'),
    new LoadUserConsentsFail(errorPlainObject),
    new EntityFailAction('entityType', 'entityId', errorPlainObject),
    ];

    (screenshot: ) image

  6. VALID EMPTY OBJECT

    const errorEmptyObject = {};
    export const actionsUsing_errorEmptyObject = [
    new LoadCdcUserTokenFail({
    initialActionPayload: {
      baseSite: 'testBaseSite',
      idToken: 'testIdToken',
      signatureTimestamp: 'testSignatureTimestamp',
      UID: 'testUID',
      UIDSignature: 'testUIDSignature',
    },
    error: errorEmptyObject,
    }),
    new CustomerSearchFail(errorEmptyObject),
    new CartAddEntryFail({
    cartId: 'testCartId',
    error: errorEmptyObject,
    productCode: 'testProductCode',
    quantity: 1,
    userId: 'testUserId',
    pickupStore: 'testPickupStore',
    }),
    new EditSavedCartFail({
    cartId: 'testCartId',
    error: errorEmptyObject,
    userId: 'testUserId',
    saveCartName: 'testSaveCartName',
    }),
    new LoadWishListFail({
    cartId: 'testCartId',
    error: errorEmptyObject,
    }),
    new CancelReplenishmentOrderFail({
    message: 'error',
    }),
    new UnassignB2BUserUserGroupFail({
    error: errorEmptyObject,
    orgCustomerId: 'testOrgCustomerId',
    userGroupId: 'testUserGroupId',
    }),
    new AssignBudgetFail({
    budgetCode: 'testBudgetCode',
    error: errorEmptyObject,
    }),
    new LoadApprovalProcessesFail({
    error: errorEmptyObject,
    }),
    new LoadPermissionTypesFail({
    error: errorEmptyObject,
    }),
    new LoadUnitOrdersFail(errorEmptyObject),
    new LoadCmsComponentFail({
    error: errorEmptyObject,
    pageContext: { id: 'testPageId', type: PageType.CONTENT_PAGE },
    uid: 'testUid',
    }),
    new LoadCmsNavigationItemsFail('nodeId', errorEmptyObject),
    new LoadProductFail('productCode123', errorEmptyObject, 'scope'),
    new LoadUserConsentsFail(errorEmptyObject),
    new EntityFailAction('entityType', 'entityId', errorEmptyObject),
    ];

    (screenshot: ) image

  7. VALID EMPTY STRING

    const errorEmptyString = '';
    export const actionsUsing_errorEmptyString = [
    new LoadCdcUserTokenFail({
    initialActionPayload: {
      baseSite: 'testBaseSite',
      idToken: 'testIdToken',
      signatureTimestamp: 'testSignatureTimestamp',
      UID: 'testUID',
      UIDSignature: 'testUIDSignature',
    },
    error: errorEmptyString,
    }),
    new CustomerSearchFail(errorEmptyString),
    new CartAddEntryFail({
    cartId: 'testCartId',
    error: errorEmptyString,
    productCode: 'testProductCode',
    quantity: 1,
    userId: 'testUserId',
    pickupStore: 'testPickupStore',
    }),
    new EditSavedCartFail({
    cartId: 'testCartId',
    error: errorEmptyString,
    userId: 'testUserId',
    saveCartName: 'testSaveCartName',
    }),
    new LoadWishListFail({
    cartId: 'testCartId',
    error: errorEmptyString,
    }),
    new CancelReplenishmentOrderFail({
    message: 'error',
    }),
    new UnassignB2BUserUserGroupFail({
    error: errorEmptyString,
    orgCustomerId: 'testOrgCustomerId',
    userGroupId: 'testUserGroupId',
    }),
    new AssignBudgetFail({
    budgetCode: 'testBudgetCode',
    error: errorEmptyString,
    }),
    new LoadApprovalProcessesFail({
    error: errorEmptyString,
    }),
    new LoadPermissionTypesFail({
    error: errorEmptyString,
    }),
    new LoadUnitOrdersFail(errorEmptyString),
    new LoadCmsComponentFail({
    error: errorEmptyString,
    pageContext: { id: 'testPageId', type: PageType.CONTENT_PAGE },
    uid: 'testUid',
    }),
    new LoadCmsNavigationItemsFail('nodeId', errorEmptyString),
    new LoadProductFail('productCode123', errorEmptyString, 'scope'),
    new LoadUserConsentsFail(errorEmptyString),
    new EntityFailAction('entityType', 'entityId', errorEmptyString),
    ];

    (screenshot: ) image

pawelfras commented 3 days ago

Have you considered creating a separate ticket to track deprecations which eventually will be removed and attaching its ID to all related places?

pawelfras commented 2 days ago

During QA, I faced an error:

image

Probably due to missing unknown type in DeprecatedLoadWishListFailPayload

interface DeprecatedLoadWishListFailPayload
  extends Omit<LoadWishListFailPayload, 'error'> {
  error: null | undefined;
}