diff --git a/package-lock.json b/package-lock.json index 4f41eba048..822badbe33 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2386,6 +2386,25 @@ "prop-types": "15.6.2" }, "dependencies": { + "@edx/frontend-enterprise-utils": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/@edx/frontend-enterprise-utils/-/frontend-enterprise-utils-0.1.7.tgz", + "integrity": "sha512-rLS/Fmq+TQPFhy1yMli4e9DsCxGAKcpZp55HvjdiiIbuMpUrWqXuP/UFemL8w45yo9osw6vH0vKhqb14RX8y4A==", + "requires": { + "@testing-library/react": "11.2.6", + "history": "4.10.1", + "query-string": "5.1.1" + } + }, + "@testing-library/react": { + "version": "11.2.6", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-11.2.6.tgz", + "integrity": "sha512-TXMCg0jT8xmuU8BkKMtp8l7Z50Ykew5WNX8UoIKTaLFwKkP2+1YDhOLA2Ga3wY4x29jyntk7EWfum0kjlYiSjQ==", + "requires": { + "@babel/runtime": "^7.12.5", + "@testing-library/dom": "^7.28.1" + } + }, "classnames": { "version": "2.2.5", "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.2.5.tgz", @@ -2410,12 +2429,33 @@ "@edx/frontend-enterprise-utils": "^0.1.7", "prop-types": "15.7.2", "query-string": "5.1.1" + }, + "dependencies": { + "@edx/frontend-enterprise-utils": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/@edx/frontend-enterprise-utils/-/frontend-enterprise-utils-0.1.7.tgz", + "integrity": "sha512-rLS/Fmq+TQPFhy1yMli4e9DsCxGAKcpZp55HvjdiiIbuMpUrWqXuP/UFemL8w45yo9osw6vH0vKhqb14RX8y4A==", + "requires": { + "@testing-library/react": "11.2.6", + "history": "4.10.1", + "query-string": "5.1.1" + } + }, + "@testing-library/react": { + "version": "11.2.6", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-11.2.6.tgz", + "integrity": "sha512-TXMCg0jT8xmuU8BkKMtp8l7Z50Ykew5WNX8UoIKTaLFwKkP2+1YDhOLA2Ga3wY4x29jyntk7EWfum0kjlYiSjQ==", + "requires": { + "@babel/runtime": "^7.12.5", + "@testing-library/dom": "^7.28.1" + } + } } }, "@edx/frontend-enterprise-utils": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/@edx/frontend-enterprise-utils/-/frontend-enterprise-utils-0.1.7.tgz", - "integrity": "sha512-rLS/Fmq+TQPFhy1yMli4e9DsCxGAKcpZp55HvjdiiIbuMpUrWqXuP/UFemL8w45yo9osw6vH0vKhqb14RX8y4A==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@edx/frontend-enterprise-utils/-/frontend-enterprise-utils-1.1.0.tgz", + "integrity": "sha512-Km0WspRcFZ6VlnXLNq8VGzHgFRcB3n3TDqLYlnfdANR+ZszYMH6aEhNgQ2DQW9HKRO5geMOUVEDXjqpmuCkw8Q==", "requires": { "@testing-library/react": "11.2.6", "history": "4.10.1", diff --git a/package.json b/package.json index 05dfaf229c..9b09d1f4ae 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "@edx/brand": "npm:@edx/brand-edx.org@^2.0.3", "@edx/frontend-enterprise-catalog-search": "0.1.10", "@edx/frontend-enterprise-logistration": "0.1.11", - "@edx/frontend-enterprise-utils": "0.1.7", + "@edx/frontend-enterprise-utils": "1.1.0", "@edx/frontend-platform": "1.11.0", "@edx/paragon": "^16.8.0", "@fortawesome/fontawesome-svg-core": "1.2.35", diff --git a/src/components/subscriptions/expiration/SubscriptionExpirationBanner.jsx b/src/components/subscriptions/expiration/SubscriptionExpirationBanner.jsx index 0928b2a1e9..ac52376bd7 100644 --- a/src/components/subscriptions/expiration/SubscriptionExpirationBanner.jsx +++ b/src/components/subscriptions/expiration/SubscriptionExpirationBanner.jsx @@ -1,7 +1,7 @@ import React, { useContext, useState } from 'react'; import { Alert } from '@edx/paragon'; import PropTypes from 'prop-types'; -import { sendTrackEvent } from '@edx/frontend-platform/analytics'; +import { sendEnterpriseTrackEvent } from '@edx/frontend-enterprise-utils'; import { SUBSCRIPTION_DAYS_REMAINING_MODERATE, @@ -20,6 +20,7 @@ const SubscriptionExpirationBanner = ({ isSubscriptionPlanDetails }) => { daysUntilExpiration: daysUntilPlanExpiration, expirationDate, showExpirationNotifications, + enterpriseCustomerUuid, }, } = useContext(SubscriptionDetailContext); const [showBanner, setShowBanner] = useState(true); @@ -82,17 +83,25 @@ const SubscriptionExpirationBanner = ({ isSubscriptionPlanDetails }) => { } const emitAlertActionEvent = () => { - sendTrackEvent('edx.ui.admin_portal.subscriptions.expiration.alert.support_cta.clicked', { - expiration_threshold: subscriptionExpirationThreshold, - days_until_expiration: daysUntilExpiration, - }); + sendEnterpriseTrackEvent( + enterpriseCustomerUuid, + 'edx.ui.admin_portal.subscriptions.expiration.alert.support_cta.clicked', + { + expiration_threshold: subscriptionExpirationThreshold, + days_until_expiration: daysUntilExpiration, + }, + ); }; const emitAlertDismissedEvent = () => { - sendTrackEvent('edx.ui.admin_portal.subscriptions.expiration.alert.dismissed', { - expiration_threshold: subscriptionExpirationThreshold, - days_until_expiration: daysUntilExpiration, - }); + sendEnterpriseTrackEvent( + enterpriseCustomerUuid, + 'edx.ui.admin_portal.subscriptions.expiration.alert.dismissed', + { + expiration_threshold: subscriptionExpirationThreshold, + days_until_expiration: daysUntilExpiration, + }, + ); }; const actions = []; diff --git a/src/components/subscriptions/expiration/SubscriptionExpirationModals.jsx b/src/components/subscriptions/expiration/SubscriptionExpirationModals.jsx index b4a68b526e..86d22d7fce 100644 --- a/src/components/subscriptions/expiration/SubscriptionExpirationModals.jsx +++ b/src/components/subscriptions/expiration/SubscriptionExpirationModals.jsx @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import Cookies from 'universal-cookie'; import { useToggle } from '@edx/paragon'; -import { sendTrackEvent } from '@edx/frontend-platform/analytics'; +import { sendEnterpriseTrackEvent } from '@edx/frontend-enterprise-utils'; import SubscriptionExpiredModal from './SubscriptionExpiredModal'; import SubscriptionExpiringModal from './SubscriptionExpiringModal'; @@ -30,6 +30,7 @@ const SubscriptionExpirationModals = ({ enterpriseId }) => { const { subscription: { agreementNetDaysUntilExpiration, showExpirationNotifications, + enterpriseCustomerUuid, }, } = useContext(SubscriptionDetailContext); const isSubscriptionExpired = agreementNetDaysUntilExpiration <= 0; @@ -96,17 +97,25 @@ const SubscriptionExpirationModals = ({ enterpriseId }) => { }, [isSubscriptionExpired]); const emitAlertActionEvent = () => { - sendTrackEvent('edx.ui.admin_portal.subscriptions.expiration.modal.support_cta.clicked', { - expiration_threshold: subscriptionExpirationThreshold, - days_until_expiration: agreementNetDaysUntilExpiration, - }); + sendEnterpriseTrackEvent( + enterpriseCustomerUuid, + 'edx.ui.admin_portal.subscriptions.expiration.modal.support_cta.clicked', + { + expiration_threshold: subscriptionExpirationThreshold, + days_until_expiration: agreementNetDaysUntilExpiration, + }, + ); }; const emitAlertDismissedEvent = () => { - sendTrackEvent('edx.ui.admin_portal.subscriptions.expiration.modal.dismissed', { - expiration_threshold: subscriptionExpirationThreshold, - days_until_expiration: agreementNetDaysUntilExpiration, - }); + sendEnterpriseTrackEvent( + enterpriseCustomerUuid, + 'edx.ui.admin_portal.subscriptions.expiration.modal.dismissed', + { + expiration_threshold: subscriptionExpirationThreshold, + days_until_expiration: agreementNetDaysUntilExpiration, + }, + ); }; const handleCloseModal = (closeModal) => { diff --git a/src/components/subscriptions/tests/TestUtilities.jsx b/src/components/subscriptions/tests/TestUtilities.jsx index 925691ea62..c1fbae058d 100644 --- a/src/components/subscriptions/tests/TestUtilities.jsx +++ b/src/components/subscriptions/tests/TestUtilities.jsx @@ -13,10 +13,10 @@ import SubscriptionDetailContextProvider from '../SubscriptionDetailContextProvi import * as hooks from '../data/hooks'; export const TEST_ENTERPRISE_CUSTOMER_SLUG = 'test-enterprise'; -const TEST_ENTERPRISE_CUSTOMER_UUID = 'b5f07fee-1b34-458f-b672-19b55fc1bd10'; -const TEST_ENTERPRISE_CUSTOMER_CATALOG_UUID = 'ff7acb5e-584a-4e5f-bacc-33a9995794f9'; -const TEST_SUBSCRIPTION_PLAN_TITLE = 'Test Subscription Plan'; -const TEST_SUBSCRIPTION_PLAN_UUID = '28d4dcdc-c026-4c02-a263-82dd9c0d8b43'; +export const TEST_ENTERPRISE_CUSTOMER_UUID = 'b5f07fee-1b34-458f-b672-19b55fc1bd10'; +export const TEST_ENTERPRISE_CUSTOMER_CATALOG_UUID = 'ff7acb5e-584a-4e5f-bacc-33a9995794f9'; +export const TEST_SUBSCRIPTION_PLAN_TITLE = 'Test Subscription Plan'; +export const TEST_SUBSCRIPTION_PLAN_UUID = '28d4dcdc-c026-4c02-a263-82dd9c0d8b43'; export const SUBSCRIPTION_PLAN_ZERO_STATE = { daysUntilExpiration: 240, diff --git a/src/components/subscriptions/tests/expiration/SubscriptionExpirationBanner.test.jsx b/src/components/subscriptions/tests/expiration/SubscriptionExpirationBanner.test.jsx index 0038ffb3e5..8494add79c 100644 --- a/src/components/subscriptions/tests/expiration/SubscriptionExpirationBanner.test.jsx +++ b/src/components/subscriptions/tests/expiration/SubscriptionExpirationBanner.test.jsx @@ -4,6 +4,7 @@ import { } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; +import * as enterpriseUtils from '@edx/frontend-enterprise-utils'; import SubscriptionExpirationBanner from '../../expiration/SubscriptionExpirationBanner'; import { SUBSCRIPTION_DAYS_REMAINING_MODERATE, @@ -13,10 +14,11 @@ import { import { SUBSCRIPTION_PLAN_ZERO_STATE, SubscriptionManagementContext, + TEST_ENTERPRISE_CUSTOMER_UUID, } from '../TestUtilities'; -jest.mock('@edx/frontend-platform/analytics', () => ({ - sendTrackEvent: jest.fn(), +jest.mock('@edx/frontend-enterprise-utils', () => ({ + sendEnterpriseTrackEvent: jest.fn(), })); // PropType validation for state is done by SubscriptionManagementContext @@ -28,6 +30,8 @@ const ExpirationBannerWithContext = ({ detailState, isSubscriptionPlanDetails = ); describe('', () => { + afterEach(() => jest.clearAllMocks()); + test('does not render an alert before the "moderate" days until expiration threshold', () => { const detailStateCopy = { ...SUBSCRIPTION_PLAN_ZERO_STATE, @@ -63,6 +67,14 @@ describe('', () => { userEvent.click(screen.getByText('Dismiss')); await waitForElementToBeRemoved(screen.queryByRole('alert')); expect(screen.queryByRole('alert')).toBeNull(); + expect(enterpriseUtils.sendEnterpriseTrackEvent).toHaveBeenCalledWith( + TEST_ENTERPRISE_CUSTOMER_UUID, + 'edx.ui.admin_portal.subscriptions.expiration.alert.dismissed', + { + expiration_threshold: threshold, + days_until_expiration: threshold, + }, + ); }); test('does not render an alert when subscription expiration notifications are disabled', () => { @@ -97,4 +109,23 @@ describe('', () => { expect(screen.queryByText("This subscription plan's end date has passed")).toBeNull(); } }); + + test('handles customer support button click', () => { + const agreementNetDaysUntilExpiration = 0; + const detailStateCopy = { + ...SUBSCRIPTION_PLAN_ZERO_STATE, + agreementNetDaysUntilExpiration, + }; + + render(); + userEvent.click(screen.getByText('Contact customer support')); + expect(enterpriseUtils.sendEnterpriseTrackEvent).toHaveBeenCalledWith( + TEST_ENTERPRISE_CUSTOMER_UUID, + 'edx.ui.admin_portal.subscriptions.expiration.alert.support_cta.clicked', + { + expiration_threshold: SUBSCRIPTION_DAYS_REMAINING_EXCEPTIONAL, + days_until_expiration: agreementNetDaysUntilExpiration, + }, + ); + }); }); diff --git a/src/components/subscriptions/tests/expiration/SubscriptionExpirationModals.test.jsx b/src/components/subscriptions/tests/expiration/SubscriptionExpirationModals.test.jsx index 7d34026a77..faebb8be10 100644 --- a/src/components/subscriptions/tests/expiration/SubscriptionExpirationModals.test.jsx +++ b/src/components/subscriptions/tests/expiration/SubscriptionExpirationModals.test.jsx @@ -5,6 +5,7 @@ import { import '@testing-library/jest-dom/extend-expect'; import userEvent from '@testing-library/user-event'; +import * as enterpriseUtils from '@edx/frontend-enterprise-utils'; import SubscriptionExpirationModals from '../../expiration/SubscriptionExpirationModals'; import { EXPIRED_MODAL_TITLE } from '../../expiration/SubscriptionExpiredModal'; import { EXPIRING_MODAL_TITLE } from '../../expiration/SubscriptionExpiringModal'; @@ -14,12 +15,13 @@ import { SUBSCRIPTION_DAYS_REMAINING_EXCEPTIONAL, } from '../../data/constants'; import { + TEST_ENTERPRISE_CUSTOMER_UUID, SUBSCRIPTION_PLAN_ZERO_STATE, SubscriptionManagementContext, } from '../TestUtilities'; -jest.mock('@edx/frontend-platform/analytics', () => ({ - sendTrackEvent: jest.fn(), +jest.mock('@edx/frontend-enterprise-utils', () => ({ + sendEnterpriseTrackEvent: jest.fn(), })); // PropType validation for state is done by SubscriptionManagementContext @@ -31,6 +33,8 @@ const ExpirationModalsWithContext = ({ detailState }) => ( ); describe('', () => { + afterEach(() => jest.clearAllMocks()); + describe('non-expired and non-expiring', () => { test('does not render any expiration modals', () => { render(); @@ -62,14 +66,42 @@ describe('', () => { }); test('expired modal is dismissible', () => { + const agreementNetDaysUntilExpiration = 0; const detailStateCopy = { ...SUBSCRIPTION_PLAN_ZERO_STATE, - agreementNetDaysUntilExpiration: 0, + agreementNetDaysUntilExpiration, }; render(); expect(screen.queryByLabelText(EXPIRED_MODAL_TITLE)).toBeTruthy(); userEvent.click(screen.getByText('Dismiss')); expect(screen.queryByLabelText(EXPIRED_MODAL_TITLE)).toBeFalsy(); + expect(enterpriseUtils.sendEnterpriseTrackEvent).toHaveBeenCalledWith( + TEST_ENTERPRISE_CUSTOMER_UUID, + 'edx.ui.admin_portal.subscriptions.expiration.modal.dismissed', + { + expiration_threshold: SUBSCRIPTION_DAYS_REMAINING_EXCEPTIONAL, + days_until_expiration: agreementNetDaysUntilExpiration, + }, + ); + }); + + test('handles customer support button click', () => { + const agreementNetDaysUntilExpiration = 0; + const detailStateCopy = { + ...SUBSCRIPTION_PLAN_ZERO_STATE, + agreementNetDaysUntilExpiration, + }; + + render(); + userEvent.click(screen.getByText('Contact customer support')); + expect(enterpriseUtils.sendEnterpriseTrackEvent).toHaveBeenCalledWith( + TEST_ENTERPRISE_CUSTOMER_UUID, + 'edx.ui.admin_portal.subscriptions.expiration.modal.support_cta.clicked', + { + expiration_threshold: SUBSCRIPTION_DAYS_REMAINING_EXCEPTIONAL, + days_until_expiration: agreementNetDaysUntilExpiration, + }, + ); }); }); @@ -112,6 +144,33 @@ describe('', () => { expect(screen.queryByLabelText(EXPIRING_MODAL_TITLE)).toBeTruthy(); userEvent.click(screen.getByText('Dismiss')); expect(screen.queryByLabelText(EXPIRING_MODAL_TITLE)).toBeFalsy(); + expect(enterpriseUtils.sendEnterpriseTrackEvent).toHaveBeenCalledWith( + TEST_ENTERPRISE_CUSTOMER_UUID, + 'edx.ui.admin_portal.subscriptions.expiration.modal.dismissed', + { + expiration_threshold: threshold, + days_until_expiration: threshold, + }, + ); + }); + + test('handles customer support button click', () => { + const agreementNetDaysUntilExpiration = 0; + const detailStateCopy = { + ...SUBSCRIPTION_PLAN_ZERO_STATE, + agreementNetDaysUntilExpiration, + }; + + render(); + userEvent.click(screen.getByText('Contact customer support')); + expect(enterpriseUtils.sendEnterpriseTrackEvent).toHaveBeenCalledWith( + TEST_ENTERPRISE_CUSTOMER_UUID, + 'edx.ui.admin_portal.subscriptions.expiration.modal.support_cta.clicked', + { + expiration_threshold: SUBSCRIPTION_DAYS_REMAINING_EXCEPTIONAL, + days_until_expiration: agreementNetDaysUntilExpiration, + }, + ); }); }); });