diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2ee52c636..4a60444c0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -50,9 +50,9 @@ jobs: deploy: runs-on: ubuntu-latest environment: - name: ${{ (github.ref == 'refs/heads/master' && 'production') || (github.ref == 'refs/heads/ep-upgrade-stage' && 'nonprod') || 'staging' }} + name: ${{ (github.ref == 'refs/heads/master' && 'production') || (github.ref == 'refs/heads/ep-upgrade-stage' && 'nonprod') || (github.ref == 'refs/heads/ep-upgrade-prod' && 'preprod') || 'staging' }} needs: [lint, test] - if: github.event_name == 'push' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/staging' || github.ref == 'refs/heads/ep-upgrade-stage') + if: github.event_name == 'push' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/staging' || github.ref == 'refs/heads/ep-upgrade-stage' || github.ref == 'refs/heads/ep-upgrade-prod') steps: - uses: actions/checkout@v2 @@ -67,6 +67,7 @@ jobs: env: S3_GIVE_DOMAIN: //${{ secrets.GIVE_WEB_HOSTNAME }} ROLLBAR_ACCESS_TOKEN: ${{ secrets.ROLLBAR_ACCESS_TOKEN }} + DATADOG_RUM_CLIENT_TOKEN: ${{ secrets.DATADOG_RUM_CLIENT_TOKEN }} run: yarn run build - name: Configure AWS Credentials diff --git a/package.json b/package.json index 08a5318cb..430e5f851 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "@babel/polyfill": "^7.7.0", "@babel/runtime-corejs2": "^7.0.0", "@cruglobal/cru-payments": "^1.2.4", + "@datadog/browser-rum": "^4.48.2", "angular": "^1.8.2", "angular-cookies": "^1.8.2", "angular-environment": "https://github.com/jonshaffer/angular-environment.git#d3082c06fb16804d324faac9b7e753fd64a44e5d", diff --git a/src/app/designationEditor/pageOptionsModal/pageOptions.modal.js b/src/app/designationEditor/pageOptionsModal/pageOptions.modal.js index 4961ba20c..7f267d22e 100644 --- a/src/app/designationEditor/pageOptionsModal/pageOptions.modal.js +++ b/src/app/designationEditor/pageOptionsModal/pageOptions.modal.js @@ -12,7 +12,7 @@ class ModalInstanceCtrl { this.facebookPixelId = facebookPixelId this.suggestedAmounts = transform(suggestedAmounts, (result, value, key) => { - if (key === 'jcr:primaryType') { return } + if (key === 'jcr:primaryType' || !value?.amount) return result.push({ amount: Number(value.amount), description: value.description, @@ -23,9 +23,9 @@ class ModalInstanceCtrl { } transformSuggestedAmounts () { - return transform(this.suggestedAmounts, (result, value, i) => { + const filterOutZeroAmounts = this.suggestedAmounts.filter((amount) => amount?.amount) + return transform(filterOutZeroAmounts, (result, value, i) => { delete value.order - value.amount = value.amount || 0 result[i + 1] = value }, {}) } diff --git a/src/app/designationEditor/pageOptionsModal/pageOptions.spec.js b/src/app/designationEditor/pageOptionsModal/pageOptions.spec.js index a2d5ca7c4..a465597dc 100644 --- a/src/app/designationEditor/pageOptionsModal/pageOptions.spec.js +++ b/src/app/designationEditor/pageOptionsModal/pageOptions.spec.js @@ -15,7 +15,9 @@ describe('Designation Editor Page Options', function () { facebookPixelId: '635334562464', suggestedAmounts: { 'jcr:primaryType': 'nt:unstructured', 1: { 'jcr:primaryType': 'nt:unstructured', description: '1 Bible', amount: 100 }, - 2: { 'jcr:primaryType': 'nt:unstructured', description: '2 Bibles', amount: 200 } }, + 2: { 'jcr:primaryType': 'nt:unstructured', description: '2 Bibles', amount: 200 }, + 3: { 'jcr:primaryType': 'nt:unstructured', description: '2 Bibles', amount: 0 }, + 4: { }, }, $scope: $scope }) })) @@ -42,4 +44,24 @@ describe('Designation Editor Page Options', function () { 2: { amount: 200, description: '2 Bibles' } }) }) + + it('should remove zero or empty objects', function () { + $ctrl.suggestedAmounts.push({ + amount: 0, + description: '3 Bibles', + }); + $ctrl.suggestedAmounts.push({ + amount: 400, + description: '4 Bibles', + }); + $ctrl.suggestedAmounts.push({ description: '5 Bibles' }); + + expect($ctrl.transformSuggestedAmounts()).toEqual({ + 1: { amount: 100, description: '1 Bible' }, + 2: { amount: 200, description: '2 Bibles' }, + 3: { amount: 400, description: '4 Bibles' } + }) + }) + + }) diff --git a/src/app/productConfig/productConfigForm/productConfigForm.component.js b/src/app/productConfig/productConfigForm/productConfigForm.component.js index fa53c564a..c4b182acb 100644 --- a/src/app/productConfig/productConfigForm/productConfigForm.component.js +++ b/src/app/productConfig/productConfigForm/productConfigForm.component.js @@ -141,7 +141,7 @@ class ProductConfigFormController { const suggestedAmountsObservable = this.designationsService.suggestedAmounts(this.code, this.itemConfig) .do(suggestedAmounts => { - this.suggestedAmounts = suggestedAmounts + this.suggestedAmounts = suggestedAmounts.filter((amount) => amount?.amount) this.useSuggestedAmounts = !isEmpty(this.suggestedAmounts) }) diff --git a/src/app/productConfig/productConfigForm/productConfigForm.component.spec.js b/src/app/productConfig/productConfigForm/productConfigForm.component.spec.js index a950f6570..b920d2555 100644 --- a/src/app/productConfig/productConfigForm/productConfigForm.component.spec.js +++ b/src/app/productConfig/productConfigForm/productConfigForm.component.spec.js @@ -161,7 +161,7 @@ describe('product config form component', function () { jest.spyOn($ctrl.commonService, 'getNextDrawDate').mockReturnValue(Observable.of('2016-10-02')) - jest.spyOn($ctrl.designationsService, 'suggestedAmounts').mockReturnValue(Observable.of([{ amount: 5 }, { amount: 10 }])) + jest.spyOn($ctrl.designationsService, 'suggestedAmounts').mockReturnValue(Observable.of([{ amount: 5 }, { amount: 10 }, { amount: 0 }, { }])) jest.spyOn($ctrl.designationsService, 'givingLinks').mockReturnValue(Observable.of([])) jest.spyOn($ctrl.analyticsFactory, 'giveGiftModal').mockReturnValue(() => {}) @@ -231,6 +231,19 @@ describe('product config form component', function () { }) }) + + describe('loadData but suggestedAmounts only has 0 amounts or empty objects', () => { + beforeEach(() => { + jest.spyOn($ctrl.designationsService, 'suggestedAmounts').mockReturnValue(Observable.of([{ amount: 0 }, { }])) + }) + + it('should use default amounts', () => { + $ctrl.loadData() + expect($ctrl.suggestedAmounts).toEqual([]) + expect($ctrl.useSuggestedAmounts).toEqual(false) + }) + }) + describe('setDefaultAmount', () => { beforeEach(() => { $ctrl.itemConfig = {} diff --git a/src/common/app.config.js b/src/common/app.config.js index 58e888313..39069c898 100644 --- a/src/common/app.config.js +++ b/src/common/app.config.js @@ -3,6 +3,7 @@ import 'angular-environment' import 'angular-translate' import rollbarConfig from './rollbar.config' +import dataDogConfig from './datadog.config.js' const appConfig = /* @ngInject */ function (envServiceProvider, $compileProvider, $logProvider, $httpProvider, $locationProvider, $qProvider, $translateProvider) { $httpProvider.useApplyAsync(true) @@ -39,6 +40,11 @@ const appConfig = /* @ngInject */ function (envServiceProvider, $compileProvider 'cru-give-web-assets-nonprod.s3.amazonaws.com', 'give-nonprod-static.cru.org' ], + preprod: [ + 'give-prod-static.cru.org', + 'cru-give-web-assets-preprodInfo.s3.amazonaws.com', + 'give-preprod.cru.org' + ], production: [] }, vars: { @@ -91,6 +97,14 @@ const appConfig = /* @ngInject */ function (envServiceProvider, $compileProvider acsUrl: 'https://cru-mkt-stage1.adobe-campaign.com/lp/LP63?_uuid=f1938f90-38ea-41a6-baad-9ac133f6d2ec&service=%404k83N_C5RZnLNvwz7waA2SwyzIuP6ATcN8vJjmT5km0iZPYKUUYk54sthkZjj-hltAuOKDYocuEi5Pxv8BSICoA4uppcvU_STKCzjv9RzLpE4hqj&pkey=', isBrandedCheckout: false }, + preprod: { + apiUrl: 'https://give-preprod.cru.org', + imgDomain: '//give-prod-static.cru.org', + imgDomainDesignation: 'https://give-preprod.cru.org', + publicCru: 'https://www.cru.org', + publicGive: 'https://give-preprod.cru.org', + acsUrl: 'https://cru-mkt-prod1-m.adobe-campaign.com/lp/LPEmailPrefCenter?_uuid=8831d67a-0d46-406b-8987-fd07c97c4ca7&service=%400fAlW4GPmxXExp8qlx7HDlAM6FSZUd0yYRlQg6HRsO_kglfi0gs650oHPZX6LrOvg7OHoIWWpobOeGZduxdNU_m5alc&pkey=' + }, production: { apiUrl: 'https://give.cru.org', imgDomain: '//give-static.cru.org', @@ -559,3 +573,4 @@ export default angular.module('appConfig', [ ]) .config(appConfig) .config(rollbarConfig) + .config(dataDogConfig) diff --git a/src/common/components/paymentMethods/bankAccountForm/bankAccountForm.component.js b/src/common/components/paymentMethods/bankAccountForm/bankAccountForm.component.js index 91b973e4a..44123c6d5 100644 --- a/src/common/components/paymentMethods/bankAccountForm/bankAccountForm.component.js +++ b/src/common/components/paymentMethods/bankAccountForm/bankAccountForm.component.js @@ -89,7 +89,7 @@ class BankAccountController { state: 'encrypting' } }) - const productionEnvironments = ['production', 'prodcloud'] + const productionEnvironments = ['production', 'prodcloud', 'preprod'] const actualEnvironment = this.envService.get() const ccpEnvironment = productionEnvironments.includes(actualEnvironment) ? 'production' : actualEnvironment cruPayments.bankAccount.init(ccpEnvironment, ccpEnvironment === 'production' ? ccpKey : ccpStagingKey) diff --git a/src/common/components/paymentMethods/bankAccountForm/bankAccountForm.component.spec.js b/src/common/components/paymentMethods/bankAccountForm/bankAccountForm.component.spec.js index fe733ba59..40507acac 100644 --- a/src/common/components/paymentMethods/bankAccountForm/bankAccountForm.component.spec.js +++ b/src/common/components/paymentMethods/bankAccountForm/bankAccountForm.component.spec.js @@ -218,6 +218,34 @@ describe('bank account form', () => { expect(self.outerScope.onPaymentFormStateChange).toHaveBeenCalledWith({ state: 'loading', payload: expectedData }) }) + it('should send a request to save the bank account payment info using the preprod env', () => { + self.controller.bankPayment = { + accountType: 'checking', + bankName: 'First Bank', + routingNumber: '123456789', + accountNumber: '123456789012' + } + self.formController.$valid = true + self.controller.envService.set('preprod') + self.controller.savePayment() + + expect(cruPayments.bankAccount.init).toHaveBeenCalledWith('production', ccpKey) + expect(cruPayments.bankAccount.encrypt).toHaveBeenCalledWith('123456789012') + expect(self.formController.$setSubmitted).toHaveBeenCalled() + const expectedData = { + bankAccount: { + 'account-type': 'checking', + 'bank-name': 'First Bank', + 'encrypted-account-number': encryptedAccountNumber, + 'display-account-number': '9012', + 'routing-number': '123456789' + } + } + + expect(self.controller.onPaymentFormStateChange).toHaveBeenCalledWith({ $event: { state: 'loading', payload: expectedData } }) + expect(self.outerScope.onPaymentFormStateChange).toHaveBeenCalledWith({ state: 'loading', payload: expectedData }) + }) + it('should send a request to save the bank account payment info with an existing payment method where the accountNumber is empty', () => { self.controller.paymentMethod = { 'display-account-number': '9012' diff --git a/src/common/components/paymentMethods/creditCardForm/creditCardForm.component.js b/src/common/components/paymentMethods/creditCardForm/creditCardForm.component.js index 1803802ad..6e152e545 100644 --- a/src/common/components/paymentMethods/creditCardForm/creditCardForm.component.js +++ b/src/common/components/paymentMethods/creditCardForm/creditCardForm.component.js @@ -138,7 +138,7 @@ class CreditCardController { }) // Send masked card number when card number is not updated : this.tsysService.getManifest() .mergeMap(data => { - const productionEnvironments = ['production', 'prodcloud'] + const productionEnvironments = ['production', 'prodcloud', 'preprod'] const actualEnvironment = this.envService.get() const ccpEnvironment = productionEnvironments.includes(actualEnvironment) ? 'production' : actualEnvironment diff --git a/src/common/components/paymentMethods/creditCardForm/creditCardForm.component.spec.js b/src/common/components/paymentMethods/creditCardForm/creditCardForm.component.spec.js index 0e10ef090..c500bbfce 100644 --- a/src/common/components/paymentMethods/creditCardForm/creditCardForm.component.spec.js +++ b/src/common/components/paymentMethods/creditCardForm/creditCardForm.component.spec.js @@ -269,6 +269,22 @@ describe('credit card form', () => { expect(cruPayments.creditCard.init).toHaveBeenCalledWith('production', '', '') }) + it('should handle preprod environment properly', () => { + self.controller.envService.set('preprod') + self.controller.creditCardPayment = { + cardNumber: '4111111111111111', + cardholderName: 'Person Name', + expiryMonth: 12, + expiryYear: 2019, + securityCode: '123' + } + self.controller.useMailingAddress = true + self.formController.$valid = true + self.controller.savePayment() + + expect(cruPayments.creditCard.init).toHaveBeenCalledWith('production', '', '') + }) + it('should handle an error retrieving the manifest from TSYS', () => { self.formController.$valid = true self.controller.tsysService.getManifest.mockReturnValue(Observable.throw('some error')) diff --git a/src/common/datadog.config.js b/src/common/datadog.config.js new file mode 100644 index 000000000..935458bc1 --- /dev/null +++ b/src/common/datadog.config.js @@ -0,0 +1,26 @@ +import 'angular-environment' +import { datadogRum } from '@datadog/browser-rum' + +const dataDogConfig = /* @ngInject */ function (envServiceProvider) { + const config = { + applicationId: '3937053e-386b-4b5b-ab4a-c83217d2f953', + clientToken: process.env.DATADOG_RUM_CLIENT_TOKEN, + site: 'datadoghq.com', + service: 'give-web', + env: envServiceProvider.get(), + version: process.env.GITHUB_SHA, + sessionSampleRate: envServiceProvider.is('staging') ? 100 : 10, + sessionReplaySampleRate: envServiceProvider.is('staging') ? 100 : 1, + trackUserInteractions: true, + trackResources: true, + trackLongTasks: true, + defaultPrivacyLevel: 'mask-user-input' + } + + window.datadogRum = datadogRum + window.datadogRum && window.datadogRum.init(config) + window.datadogRum && window.datadogRum.startSessionReplayRecording() +} +export { + dataDogConfig as default +} diff --git a/webpack.config.js b/webpack.config.js index 7f04c9310..8fb4400af 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -55,7 +55,8 @@ const sharedConfig = { new webpack.EnvironmentPlugin({ GITHUB_SHA: 'development', S3_GIVE_DOMAIN: '', - ROLLBAR_ACCESS_TOKEN: JSON.stringify(process.env.ROLLBAR_ACCESS_TOKEN) || 'development-token' + ROLLBAR_ACCESS_TOKEN: JSON.stringify(process.env.ROLLBAR_ACCESS_TOKEN) || 'development-token', + DATADOG_RUM_CLIENT_TOKEN: process.env.DATADOG_RUM_CLIENT_TOKEN || '' }), // To strip all locales except “en” new MomentLocalesPlugin() diff --git a/yarn.lock b/yarn.lock index bfa232497..6e66470c1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1544,6 +1544,26 @@ dependencies: "@cspotcode/source-map-consumer" "0.8.0" +"@datadog/browser-core@4.48.2": + version "4.48.2" + resolved "https://registry.yarnpkg.com/@datadog/browser-core/-/browser-core-4.48.2.tgz#6606878660a2dad528a7c5c4aa5465169cb2c33a" + integrity sha512-ewQDLouh9jymJPTRq5M8Hz0FFzMaVlJCwHHY5gMUmGyIaBEzS8wd3AeZ56kQ32ZOcyz83au1/yZ/zcIB8LDoyA== + +"@datadog/browser-rum-core@4.48.2": + version "4.48.2" + resolved "https://registry.yarnpkg.com/@datadog/browser-rum-core/-/browser-rum-core-4.48.2.tgz#4fc13aedc6170d51c3c024327da27af937fd0186" + integrity sha512-LUlaYAC7MGXZbIPT6LvSVxaZBG0irer+meQp1LfJBdSy/lLb4OrNowrc9dcHeh4Fw9rNNLWl2Pz+4dBqRtR87g== + dependencies: + "@datadog/browser-core" "4.48.2" + +"@datadog/browser-rum@^4.48.2": + version "4.48.2" + resolved "https://registry.yarnpkg.com/@datadog/browser-rum/-/browser-rum-4.48.2.tgz#95ab72a0fd6b9f82b69cca6a351d9a91bcd8d5da" + integrity sha512-SGxjKJLUJq8AjWedhHTQicIr4rvAcDMwBr6gHvZr3h9llCBpDxPzd/RPKCK69HMxBGt/Cw7KCpjAjy9IaCURWQ== + dependencies: + "@datadog/browser-core" "4.48.2" + "@datadog/browser-rum-core" "4.48.2" + "@eslint/eslintrc@^0.3.0": version "0.3.0" resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.3.0.tgz#d736d6963d7003b6514e6324bec9c602ac340318"