diff --git a/src/components/learner-credit-management/AssignmentStatusTableCell.jsx b/src/components/learner-credit-management/AssignmentStatusTableCell.jsx
index d68cf57d9f..8dc0c0da52 100644
--- a/src/components/learner-credit-management/AssignmentStatusTableCell.jsx
+++ b/src/components/learner-credit-management/AssignmentStatusTableCell.jsx
@@ -1,5 +1,7 @@
import { Chip } from '@edx/paragon';
import PropTypes from 'prop-types';
+import { sendEnterpriseTrackEvent } from '@edx/frontend-enterprise-utils';
+import { connect } from 'react-redux';
import FailedBadEmail from './assignments-status-chips/FailedBadEmail';
import FailedCancellation from './assignments-status-chips/FailedCancellation';
import FailedRedemption from './assignments-status-chips/FailedRedemption';
@@ -8,14 +10,61 @@ import FailedSystem from './assignments-status-chips/FailedSystem';
import NotifyingLearner from './assignments-status-chips/NotifyingLearner';
import WaitingForLearner from './assignments-status-chips/WaitingForLearner';
import { capitalizeFirstLetter } from '../../utils';
+import { useBudgetId, useSubsidyAccessPolicy } from './data';
-const AssignmentStatusTableCell = ({ row }) => {
+const AssignmentStatusTableCell = ({ enterpriseId, row }) => {
const { original } = row;
const {
learnerEmail,
learnerState,
errorReason,
} = original;
+ const { subsidyAccessPolicyId } = useBudgetId();
+ const { data: subsidyAccessPolicy } = useSubsidyAccessPolicy(subsidyAccessPolicyId);
+ const {
+ subsidyUuid, assignmentConfiguration, isSubsidyActive, isAssignable, catalogUuid, aggregates,
+ } = subsidyAccessPolicy;
+
+ const sharedTrackEventMetadata = {
+ learnerState,
+ subsidyUuid,
+ assignmentConfiguration,
+ isSubsidyActive,
+ isAssignable,
+ catalogUuid,
+ aggregates,
+ };
+
+ const sendGenericTrackEvent = (eventName, eventMetadata = {}) => {
+ sendEnterpriseTrackEvent(
+ enterpriseId,
+ eventName,
+ {
+ ...sharedTrackEventMetadata,
+ ...eventMetadata,
+ },
+ );
+ };
+
+ const sendErrorStateTrackEvent = (eventName, eventMetadata = {}) => {
+ const errorReasonMetadata = {
+ erroredAction: {
+ errorReason: errorReason?.errorReason || null,
+ actionType: errorReason?.actionType || null,
+ },
+ };
+ const errorStateMetadata = {
+ ...sharedTrackEventMetadata,
+ ...errorReasonMetadata,
+ ...eventMetadata,
+ };
+ sendEnterpriseTrackEvent(
+ enterpriseId,
+ eventName,
+ errorStateMetadata,
+ );
+ };
+
// Learner state is not available for this assignment, so don't display anything.
if (!learnerState) {
return null;
@@ -24,43 +73,42 @@ const AssignmentStatusTableCell = ({ row }) => {
// Display the appropriate status chip based on the learner state.
if (learnerState === 'notifying') {
return (
-
+
);
}
if (learnerState === 'waiting') {
return (
-
+
);
}
if (learnerState === 'failed') {
// If learnerState is failed but no top-level error reason is defined, return a failed system chip.
if (!errorReason) {
- return ;
+ return ;
}
// Determine which failure chip to display based on the top level errorReason. In most cases, the actual errorReason
// code is ignored, in which case we key off the actionType.
if (errorReason.actionType === 'notified') {
if (errorReason.errorReason === 'email_error') {
return (
-
+
);
}
- // non-email errors on failed notifications should NOT use the FailedBadEmail chip.
- return ;
+ return ;
}
if (errorReason.actionType === 'cancelled') {
- return ;
+ return ;
}
if (errorReason.actionType === 'reminded') {
- return ;
+ return ;
}
if (errorReason.actionType === 'redeemed') {
- return ;
+ return ;
}
// In all other unexpected cases, return a failed system chip.
- return ;
+ return ;
}
// Note: The given `learnerState` not officially supported with a `ModalPopup`, but display it anyway.
@@ -68,6 +116,7 @@ const AssignmentStatusTableCell = ({ row }) => {
};
AssignmentStatusTableCell.propTypes = {
+ enterpriseId: PropTypes.string.isRequired,
row: PropTypes.shape({
original: PropTypes.shape({
learnerEmail: PropTypes.string,
@@ -84,4 +133,8 @@ AssignmentStatusTableCell.propTypes = {
}).isRequired,
};
-export default AssignmentStatusTableCell;
+const mapStateToProps = state => ({
+ enterpriseId: state.portalConfiguration.enterpriseId,
+});
+
+export default connect(mapStateToProps)(AssignmentStatusTableCell);
diff --git a/src/components/learner-credit-management/AssignmentTableCancel.jsx b/src/components/learner-credit-management/AssignmentTableCancel.jsx
index db155be926..eac61c8f31 100644
--- a/src/components/learner-credit-management/AssignmentTableCancel.jsx
+++ b/src/components/learner-credit-management/AssignmentTableCancel.jsx
@@ -2,8 +2,12 @@ import React from 'react';
import PropTypes from 'prop-types';
import { Button } from '@edx/paragon';
import { DoNotDisturbOn } from '@edx/paragon/icons';
+import { sendEnterpriseTrackEvent } from '@edx/frontend-enterprise-utils';
+import { connect } from 'react-redux';
import CancelAssignmentModal from './CancelAssignmentModal';
import useCancelContentAssignments from './data/hooks/useCancelContentAssignments';
+import { transformSelectedRows, useBudgetId, useSubsidyAccessPolicy } from './data';
+import EVENT_NAMES from '../../eventTracking';
import { getActiveTableColumnFilters } from '../../utils';
const calculateTotalToCancel = ({
@@ -17,9 +21,23 @@ const calculateTotalToCancel = ({
return assignmentUuids.length;
};
-const AssignmentTableCancelAction = ({ selectedFlatRows, isEntireTableSelected, tableInstance }) => {
- const assignmentUuids = selectedFlatRows.map(row => row.id);
- const assignmentConfigurationUuid = selectedFlatRows[0].original.assignmentConfiguration;
+const AssignmentTableCancelAction = ({
+ selectedFlatRows, isEntireTableSelected, learnerStateCounts, tableInstance, enterpriseId,
+}) => {
+ const { subsidyAccessPolicyId } = useBudgetId();
+ const { data: subsidyAccessPolicy } = useSubsidyAccessPolicy(subsidyAccessPolicyId);
+ const {
+ subsidyUuid, assignmentConfiguration, isSubsidyActive, isAssignable, catalogUuid, aggregates,
+ } = subsidyAccessPolicy;
+
+ const {
+ uniqueLearnerState,
+ uniqueAssignmentState,
+ uniqueContentKeys,
+ totalContentQuantity,
+ assignmentUuids,
+ totalSelectedRows,
+ } = transformSelectedRows(selectedFlatRows);
const activeFilters = getActiveTableColumnFilters(tableInstance.columns);
@@ -32,7 +50,66 @@ const AssignmentTableCancelAction = ({ selectedFlatRows, isEntireTableSelected,
close,
isOpen,
open,
- } = useCancelContentAssignments(assignmentConfigurationUuid, assignmentUuids, shouldCancelAll);
+ } = useCancelContentAssignments(assignmentConfiguration.uuid, assignmentUuids, shouldCancelAll);
+
+ const {
+ BUDGET_DETAILS_ASSIGNED_DATATABLE_OPEN_BULK_CANCEL_MODAL,
+ BUDGET_DETAILS_ASSIGNED_DATATABLE_CLOSE_BULK_CANCEL_MODAL,
+ BUDGET_DETAILS_ASSIGNED_DATATABLE_BULK_CANCEL,
+ } = EVENT_NAMES.LEARNER_CREDIT_MANAGEMENT;
+
+ const trackEvent = (eventName) => {
+ // constructs a learner state object for the select all state to match format of select all on page metadata
+ const learnerStateObject = {};
+ learnerStateCounts.forEach((learnerState) => {
+ learnerStateObject[learnerState.learnerState] = learnerState.count;
+ });
+
+ const selectedRowsMetadata = isEntireTableSelected
+ ? { uniqueLearnerState: learnerStateObject, totalSelectedRows: tableInstance.itemCount }
+ : {
+ uniqueLearnerState, uniqueAssignmentState, uniqueContentKeys, totalContentQuantity, totalSelectedRows,
+ };
+
+ const trackEventMetadata = {
+ ...selectedRowsMetadata,
+ isAssignable,
+ isSubsidyActive,
+ subsidyUuid,
+ catalogUuid,
+ isEntireTableSelected,
+ assignmentUuids,
+ aggregates,
+ assignmentConfiguration,
+ isOpen: !isOpen,
+ };
+
+ sendEnterpriseTrackEvent(
+ enterpriseId,
+ eventName,
+ trackEventMetadata,
+ );
+ };
+
+ const openModal = () => {
+ open();
+ trackEvent(
+ BUDGET_DETAILS_ASSIGNED_DATATABLE_OPEN_BULK_CANCEL_MODAL,
+ );
+ };
+
+ const closeModal = () => {
+ close();
+ trackEvent(
+ BUDGET_DETAILS_ASSIGNED_DATATABLE_CLOSE_BULK_CANCEL_MODAL,
+ );
+ };
+
+ const cancellationTrackEvent = () => {
+ trackEvent(
+ BUDGET_DETAILS_ASSIGNED_DATATABLE_BULK_CANCEL,
+ );
+ };
const tableItemCount = tableInstance.itemCount;
const totalToCancel = calculateTotalToCancel({
@@ -43,14 +120,15 @@ const AssignmentTableCancelAction = ({ selectedFlatRows, isEntireTableSelected,
return (
<>
-
-
+ sendEnterpriseTrackEvent(
+ enterpriseId,
+ EVENT_NAMES.LEARNER_CREDIT_MANAGEMENT.BUDGET_OVERVIEW_CONTACT_US,
+ trackEventMetadata,
+ )}
+ target="_blank"
+ >
Contact support
@@ -73,6 +105,11 @@ const BudgetActions = ({ budgetId, isAssignable }) => {
pathname: generatePath(routeMatch.path, { budgetId, activeTabKey: 'catalog' }),
state: { budgetActivityScrollToKey: 'catalog' },
}}
+ onClick={() => sendEnterpriseTrackEvent(
+ enterpriseId,
+ EVENT_NAMES.LEARNER_CREDIT_MANAGEMENT.BUDGET_OVERVIEW_NEW_ASSIGNMENT,
+ trackEventMetadata,
+ )}
>
New course assignment
@@ -84,6 +121,7 @@ const BudgetActions = ({ budgetId, isAssignable }) => {
BudgetActions.propTypes = {
budgetId: PropTypes.string.isRequired,
isAssignable: PropTypes.bool.isRequired,
+ enterpriseId: PropTypes.string.isRequired,
};
const BudgetDetailPageOverviewAvailability = ({
@@ -91,6 +129,7 @@ const BudgetDetailPageOverviewAvailability = ({
isAssignable,
budgetTotalSummary: { available, utilized, limit },
enterpriseFeatures,
+ enterpriseId,
}) => (
@@ -101,6 +140,7 @@ const BudgetDetailPageOverviewAvailability = ({
@@ -120,9 +160,11 @@ BudgetDetailPageOverviewAvailability.propTypes = {
enterpriseFeatures: PropTypes.shape({
topDownAssignmentRealTimeLcm: PropTypes.bool,
}).isRequired,
+ enterpriseId: PropTypes.string.isRequired,
};
const mapStateToProps = state => ({
+ enterpriseId: state.portalConfiguration.enterpriseId,
enterpriseFeatures: state.portalConfiguration.enterpriseFeatures,
});
diff --git a/src/components/learner-credit-management/BudgetDetailPageOverviewUtilization.jsx b/src/components/learner-credit-management/BudgetDetailPageOverviewUtilization.jsx
index 2276670b81..f4865abd7d 100644
--- a/src/components/learner-credit-management/BudgetDetailPageOverviewUtilization.jsx
+++ b/src/components/learner-credit-management/BudgetDetailPageOverviewUtilization.jsx
@@ -5,11 +5,13 @@ import {
Stack, Collapsible, Row, Col, Button,
} from '@edx/paragon';
import { ArrowDownward } from '@edx/paragon/icons';
+import { sendEnterpriseTrackEvent } from '@edx/frontend-enterprise-utils';
import {
generatePath, useRouteMatch, Link,
} from 'react-router-dom';
import { formatPrice } from './data';
+import EVENT_NAMES from '../../eventTracking';
const BudgetDetailPageOverviewUtilization = ({
budgetId,
@@ -17,10 +19,15 @@ const BudgetDetailPageOverviewUtilization = ({
budgetAggregates,
isAssignable,
enterpriseFeatures,
+ enterpriseId,
}) => {
const routeMatch = useRouteMatch();
-
const { amountAllocatedUsd, amountRedeemedUsd } = budgetAggregates;
+ const {
+ BUDGET_OVERVIEW_UTILIZATION_VIEW_ASSIGNED_TABLE,
+ BUDGET_OVERVIEW_UTILIZATION_VIEW_SPENT_TABLE,
+ BUDGET_OVERVIEW_UTILIZATION_DROPDOWN_TOGGLE,
+ } = EVENT_NAMES.LEARNER_CREDIT_MANAGEMENT;
if (!budgetId || !enterpriseFeatures.topDownAssignmentRealTimeLcm || utilized <= 0 || !isAssignable) {
return null;
@@ -32,6 +39,9 @@ const BudgetDetailPageOverviewUtilization = ({
}
const linkText = (type === 'assigned') ? 'View assigned activity' : 'View spent activity';
+ const eventNameType = (type === 'assigned')
+ ? BUDGET_OVERVIEW_UTILIZATION_VIEW_ASSIGNED_TABLE
+ : BUDGET_OVERVIEW_UTILIZATION_VIEW_SPENT_TABLE;
return (
sendEnterpriseTrackEvent(
+ enterpriseId,
+ eventNameType,
+ )}
>
{linkText}
@@ -55,6 +69,13 @@ const BudgetDetailPageOverviewUtilization = ({
className="mt-4 budget-utilization-container"
styling="basic"
title={Utilization details
}
+ onToggle={(open) => sendEnterpriseTrackEvent(
+ enterpriseId,
+ BUDGET_OVERVIEW_UTILIZATION_DROPDOWN_TOGGLE,
+ {
+ isOpen: open,
+ },
+ )}
>
@@ -121,10 +142,12 @@ BudgetDetailPageOverviewUtilization.propTypes = {
enterpriseFeatures: PropTypes.shape({
topDownAssignmentRealTimeLcm: PropTypes.bool,
}).isRequired,
+ enterpriseId: PropTypes.string.isRequired,
};
const mapStateToProps = state => ({
enterpriseFeatures: state.portalConfiguration.enterpriseFeatures,
+ enterpriseId: state.portalConfiguration.enterpriseId,
});
export default connect(mapStateToProps)(BudgetDetailPageOverviewUtilization);
diff --git a/src/components/learner-credit-management/CancelAssignmentModal.jsx b/src/components/learner-credit-management/CancelAssignmentModal.jsx
index cce7b896b1..13657a184b 100644
--- a/src/components/learner-credit-management/CancelAssignmentModal.jsx
+++ b/src/components/learner-credit-management/CancelAssignmentModal.jsx
@@ -12,6 +12,7 @@ const CancelAssignmentModal = ({
close,
isOpen,
uuidCount,
+ trackEvent,
}) => {
const {
successfulCancellationToast: { displayToastForAssignmentCancellation },
@@ -19,6 +20,7 @@ const CancelAssignmentModal = ({
const handleOnClick = async () => {
await cancelContentAssignments();
+ trackEvent();
displayToastForAssignmentCancellation(uuidCount);
};
@@ -69,6 +71,7 @@ CancelAssignmentModal.propTypes = {
close: PropTypes.func.isRequired,
isOpen: PropTypes.bool.isRequired,
uuidCount: PropTypes.number,
+ trackEvent: PropTypes.func.isRequired,
};
export default CancelAssignmentModal;
diff --git a/src/components/learner-credit-management/PendingAssignmentCancelButton.jsx b/src/components/learner-credit-management/PendingAssignmentCancelButton.jsx
index 5b2144b149..74ebab3da9 100644
--- a/src/components/learner-credit-management/PendingAssignmentCancelButton.jsx
+++ b/src/components/learner-credit-management/PendingAssignmentCancelButton.jsx
@@ -4,25 +4,96 @@ import {
Icon, IconButtonWithTooltip,
} from '@edx/paragon';
import { DoNotDisturbOn } from '@edx/paragon/icons';
+import { connect } from 'react-redux';
+import { sendEnterpriseTrackEvent } from '@edx/frontend-enterprise-utils';
import useCancelContentAssignments from './data/hooks/useCancelContentAssignments';
import CancelAssignmentModal from './CancelAssignmentModal';
+import EVENT_NAMES from '../../eventTracking';
+import { useBudgetId, useSubsidyAccessPolicy } from './data';
+
+const PendingAssignmentCancelButton = ({ row, enterpriseId }) => {
+ const { subsidyAccessPolicyId } = useBudgetId();
+ const { data: subsidyAccessPolicy } = useSubsidyAccessPolicy(subsidyAccessPolicyId);
+ const {
+ subsidyUuid, assignmentConfiguration, isSubsidyActive, isAssignable, catalogUuid, aggregates,
+ } = subsidyAccessPolicy;
-const PendingAssignmentCancelButton = ({ row }) => {
const emailAltText = row.original.learnerEmail ? `for ${row.original.learnerEmail}` : '';
+ const {
+ contentKey,
+ contentQuantity,
+ learnerState,
+ state,
+ uuid,
+ } = row.original;
+
const {
cancelButtonState,
cancelContentAssignments,
close,
isOpen,
open,
- } = useCancelContentAssignments(row.original.assignmentConfiguration, [row.original.uuid]);
+ } = useCancelContentAssignments(assignmentConfiguration, [uuid]);
+
+ const sharedTrackEventMetadata = {
+ subsidyUuid,
+ isSubsidyActive,
+ isAssignable,
+ catalogUuid,
+ assignmentConfiguration,
+ contentKey,
+ contentQuantity,
+ learnerState,
+ aggregates,
+ assignmentState: state,
+ isOpen: !isOpen,
+ };
+
+ const {
+ BUDGET_DETAILS_ASSIGNED_DATATABLE_OPEN_CANCEL_MODAL,
+ BUDGET_DETAILS_ASSIGNED_DATATABLE_CLOSE_CANCEL_MODAL,
+ BUDGET_DETAILS_ASSIGNED_DATATABLE_CANCEL,
+ } = EVENT_NAMES.LEARNER_CREDIT_MANAGEMENT;
+
+ const trackEvent = (eventName, eventMetadata = {}) => {
+ const trackEventMetadata = {
+ ...sharedTrackEventMetadata,
+ ...eventMetadata,
+ };
+ sendEnterpriseTrackEvent(
+ enterpriseId,
+ eventName,
+ trackEventMetadata,
+ );
+ };
+
+ const openModal = () => {
+ open();
+ trackEvent(
+ BUDGET_DETAILS_ASSIGNED_DATATABLE_OPEN_CANCEL_MODAL,
+ );
+ };
+
+ const closeModal = () => {
+ close();
+ trackEvent(
+ BUDGET_DETAILS_ASSIGNED_DATATABLE_CLOSE_CANCEL_MODAL,
+ );
+ };
+
+ const cancellationTrackEvent = () => {
+ trackEvent(
+ BUDGET_DETAILS_ASSIGNED_DATATABLE_CANCEL,
+ );
+ };
+
return (
<>
{
/>
>
);
@@ -41,11 +113,20 @@ const PendingAssignmentCancelButton = ({ row }) => {
PendingAssignmentCancelButton.propTypes = {
row: PropTypes.shape({
original: PropTypes.shape({
+ contentKey: PropTypes.string.isRequired,
+ contentQuantity: PropTypes.number.isRequired,
+ learnerState: PropTypes.string.isRequired,
+ state: PropTypes.string.isRequired,
assignmentConfiguration: PropTypes.string.isRequired,
learnerEmail: PropTypes.string,
uuid: PropTypes.string.isRequired,
}).isRequired,
}).isRequired,
+ enterpriseId: PropTypes.string.isRequired,
};
-export default PendingAssignmentCancelButton;
+const mapStateToProps = state => ({
+ enterpriseId: state.portalConfiguration.enterpriseId,
+});
+
+export default connect(mapStateToProps)(PendingAssignmentCancelButton);
diff --git a/src/components/learner-credit-management/PendingAssignmentRemindButton.jsx b/src/components/learner-credit-management/PendingAssignmentRemindButton.jsx
index 07f38883cf..a88843a2fd 100644
--- a/src/components/learner-credit-management/PendingAssignmentRemindButton.jsx
+++ b/src/components/learner-credit-management/PendingAssignmentRemindButton.jsx
@@ -3,18 +3,88 @@ import PropTypes from 'prop-types';
import { Icon, IconButtonWithTooltip } from '@edx/paragon';
import { Mail } from '@edx/paragon/icons';
+import { sendEnterpriseTrackEvent } from '@edx/frontend-enterprise-utils';
+import { connect } from 'react-redux';
import RemindAssignmentModal from './RemindAssignmentModal';
import useRemindContentAssignments from './data/hooks/useRemindContentAssignments';
+import EVENT_NAMES from '../../eventTracking';
+import { useBudgetId, useSubsidyAccessPolicy } from './data';
+
+const PendingAssignmentRemindButton = ({ row, enterpriseId }) => {
+ const { subsidyAccessPolicyId } = useBudgetId();
+ const { data: subsidyAccessPolicy } = useSubsidyAccessPolicy(subsidyAccessPolicyId);
+ const {
+ subsidyUuid, assignmentConfiguration, isSubsidyActive, isAssignable, catalogUuid, aggregates,
+ } = subsidyAccessPolicy;
-const PendingAssignmentRemindButton = ({ row }) => {
const emailAltText = row.original.learnerEmail ? `for ${row.original.learnerEmail}` : '';
+ const {
+ contentKey,
+ contentQuantity,
+ learnerState,
+ state,
+ uuid,
+ } = row.original;
+
const {
remindButtonState,
remindContentAssignments,
close,
isOpen,
open,
- } = useRemindContentAssignments(row.original.assignmentConfiguration, [row.original.uuid]);
+ } = useRemindContentAssignments(assignmentConfiguration, [uuid]);
+
+ const sharedTrackEventMetadata = {
+ subsidyUuid,
+ isSubsidyActive,
+ isAssignable,
+ catalogUuid,
+ assignmentConfiguration,
+ contentKey,
+ contentQuantity,
+ learnerState,
+ aggregates,
+ assignmentState: state,
+ isOpen: !isOpen,
+ };
+
+ const {
+ BUDGET_DETAILS_ASSIGNED_DATATABLE_OPEN_REMIND_MODAL,
+ BUDGET_DETAILS_ASSIGNED_DATATABLE_CLOSE_REMIND_MODAL,
+ BUDGET_DETAILS_ASSIGNED_DATATABLE_REMIND,
+ } = EVENT_NAMES.LEARNER_CREDIT_MANAGEMENT;
+
+ const trackEvent = (eventName, eventMetadata = {}) => {
+ const trackEventMetadata = {
+ ...sharedTrackEventMetadata,
+ ...eventMetadata,
+ };
+ sendEnterpriseTrackEvent(
+ enterpriseId,
+ eventName,
+ trackEventMetadata,
+ );
+ };
+
+ const openModal = () => {
+ open();
+ trackEvent(
+ BUDGET_DETAILS_ASSIGNED_DATATABLE_OPEN_REMIND_MODAL,
+ );
+ };
+
+ const closeModal = () => {
+ close();
+ trackEvent(
+ BUDGET_DETAILS_ASSIGNED_DATATABLE_CLOSE_REMIND_MODAL,
+ );
+ };
+
+ const reminderTrackEvent = () => {
+ trackEvent(
+ BUDGET_DETAILS_ASSIGNED_DATATABLE_REMIND,
+ );
+ };
return (
<>
@@ -22,16 +92,17 @@ const PendingAssignmentRemindButton = ({ row }) => {
alt={`Remind learner ${emailAltText}`}
data-testid={`remind-assignment-${row.original.uuid}`}
iconAs={Icon}
- onClick={open}
+ onClick={openModal}
src={Mail}
tooltipContent="Remind learner"
tooltipPlacement="top"
/>
>
);
@@ -40,11 +111,20 @@ const PendingAssignmentRemindButton = ({ row }) => {
PendingAssignmentRemindButton.propTypes = {
row: PropTypes.shape({
original: PropTypes.shape({
+ contentKey: PropTypes.string.isRequired,
+ contentQuantity: PropTypes.number.isRequired,
+ learnerState: PropTypes.string.isRequired,
+ state: PropTypes.string.isRequired,
assignmentConfiguration: PropTypes.string.isRequired,
learnerEmail: PropTypes.string,
uuid: PropTypes.string.isRequired,
}).isRequired,
}).isRequired,
+ enterpriseId: PropTypes.string.isRequired,
};
-export default PendingAssignmentRemindButton;
+const mapStateToProps = state => ({
+ enterpriseId: state.portalConfiguration.enterpriseId,
+});
+
+export default connect(mapStateToProps)(PendingAssignmentRemindButton);
diff --git a/src/components/learner-credit-management/RemindAssignmentModal.jsx b/src/components/learner-credit-management/RemindAssignmentModal.jsx
index 013535e2b9..546bee7093 100644
--- a/src/components/learner-credit-management/RemindAssignmentModal.jsx
+++ b/src/components/learner-credit-management/RemindAssignmentModal.jsx
@@ -7,7 +7,7 @@ import { Mail } from '@edx/paragon/icons';
import { BudgetDetailPageContext } from './BudgetDetailPageWrapper';
const RemindAssignmentModal = ({
- remindButtonState, remindContentAssignments, close, isOpen, uuidCount,
+ remindButtonState, remindContentAssignments, close, isOpen, uuidCount, trackEvent,
}) => {
const {
successfulReminderToast: { displayToastForAssignmentReminder },
@@ -15,6 +15,7 @@ const RemindAssignmentModal = ({
const handleOnClick = async () => {
await remindContentAssignments();
+ trackEvent();
displayToastForAssignmentReminder(uuidCount);
};
@@ -66,6 +67,7 @@ RemindAssignmentModal.propTypes = {
close: PropTypes.func.isRequired,
isOpen: PropTypes.bool.isRequired,
uuidCount: PropTypes.number,
+ trackEvent: PropTypes.func.isRequired,
};
export default RemindAssignmentModal;
diff --git a/src/components/learner-credit-management/assignments-status-chips/FailedBadEmail.jsx b/src/components/learner-credit-management/assignments-status-chips/FailedBadEmail.jsx
index b6664a8768..df5fb496c3 100644
--- a/src/components/learner-credit-management/assignments-status-chips/FailedBadEmail.jsx
+++ b/src/components/learner-credit-management/assignments-status-chips/FailedBadEmail.jsx
@@ -1,30 +1,46 @@
import React, { useState } from 'react';
import PropTypes from 'prop-types';
-import { Chip, Hyperlink, useToggle } from '@edx/paragon';
+import { Chip, Hyperlink } from '@edx/paragon';
import { Error } from '@edx/paragon/icons';
import { getConfig } from '@edx/frontend-platform/config';
import BaseModalPopup from './BaseModalPopup';
+import EVENT_NAMES from '../../../eventTracking';
+import { useAssignmentStatusChip } from '../data';
-const FailedBadEmail = ({ learnerEmail }) => {
- const [isOpen, open, close] = useToggle(false);
+const FailedBadEmail = ({ learnerEmail, trackEvent }) => {
const [target, setTarget] = useState(null);
+ const {
+ BUDGET_DETAILS_ASSIGNED_DATATABLE_CHIP_FAILED_EMAIL,
+ BUDGET_DETAILS_ASSIGNED_DATATABLE_CHIP_FAILED_EMAIL_HELP_CENTER,
+ } = EVENT_NAMES.LEARNER_CREDIT_MANAGEMENT;
+
+ const {
+ openChipModal,
+ closeChipModal,
+ isChipModalOpen,
+ helpCenterTrackEvent,
+ } = useAssignmentStatusChip({
+ chipInteractionEventName: BUDGET_DETAILS_ASSIGNED_DATATABLE_CHIP_FAILED_EMAIL,
+ chipHelpCenterEventName: BUDGET_DETAILS_ASSIGNED_DATATABLE_CHIP_FAILED_EMAIL_HELP_CENTER,
+ trackEvent,
+ });
return (
<>
Failed: Bad email
Failed: Bad email
@@ -41,7 +57,11 @@ const FailedBadEmail = ({ learnerEmail }) => {
Get more troubleshooting help at{' '}
-
+
Help Center: Course Assignments
.
@@ -55,6 +75,7 @@ const FailedBadEmail = ({ learnerEmail }) => {
FailedBadEmail.propTypes = {
learnerEmail: PropTypes.string,
+ trackEvent: PropTypes.func.isRequired,
};
FailedBadEmail.defaultProps = {
diff --git a/src/components/learner-credit-management/assignments-status-chips/FailedCancellation.jsx b/src/components/learner-credit-management/assignments-status-chips/FailedCancellation.jsx
index f102592f4f..18bc683c56 100644
--- a/src/components/learner-credit-management/assignments-status-chips/FailedCancellation.jsx
+++ b/src/components/learner-credit-management/assignments-status-chips/FailedCancellation.jsx
@@ -1,29 +1,46 @@
import React, { useState } from 'react';
-import { Chip, useToggle, Hyperlink } from '@edx/paragon';
+import PropTypes from 'prop-types';
+import { Chip, Hyperlink } from '@edx/paragon';
import { Error } from '@edx/paragon/icons';
import { getConfig } from '@edx/frontend-platform/config';
import BaseModalPopup from './BaseModalPopup';
+import EVENT_NAMES from '../../../eventTracking';
+import { useAssignmentStatusChip } from '../data';
-const FailedCancellation = () => {
- const [isOpen, open, close] = useToggle(false);
+const FailedCancellation = ({ trackEvent }) => {
const [target, setTarget] = useState(null);
+ const {
+ BUDGET_DETAILS_ASSIGNED_DATATABLE_CHIP_FAILED_CANCELLATION,
+ BUDGET_DETAILS_ASSIGNED_DATATABLE_CHIP_FAILED_CANCELLATION_HELP_CENTER,
+ } = EVENT_NAMES.LEARNER_CREDIT_MANAGEMENT;
+
+ const {
+ openChipModal,
+ closeChipModal,
+ isChipModalOpen,
+ helpCenterTrackEvent,
+ } = useAssignmentStatusChip({
+ chipInteractionEventName: BUDGET_DETAILS_ASSIGNED_DATATABLE_CHIP_FAILED_CANCELLATION,
+ chipHelpCenterEventName: BUDGET_DETAILS_ASSIGNED_DATATABLE_CHIP_FAILED_CANCELLATION_HELP_CENTER,
+ trackEvent,
+ });
return (
<>
Failed: Cancellation
Failed: Cancellation
@@ -41,7 +58,11 @@ const FailedCancellation = () => {
Get more troubleshooting help at{' '}
-
+
Help Center: Course Assignments
@@ -53,4 +74,8 @@ const FailedCancellation = () => {
);
};
+FailedCancellation.propTypes = {
+ trackEvent: PropTypes.func.isRequired,
+};
+
export default FailedCancellation;
diff --git a/src/components/learner-credit-management/assignments-status-chips/FailedRedemption.jsx b/src/components/learner-credit-management/assignments-status-chips/FailedRedemption.jsx
index 6e12f9303f..ef263065c6 100644
--- a/src/components/learner-credit-management/assignments-status-chips/FailedRedemption.jsx
+++ b/src/components/learner-credit-management/assignments-status-chips/FailedRedemption.jsx
@@ -1,29 +1,48 @@
import React, { useState } from 'react';
-import { Chip, Hyperlink, useToggle } from '@edx/paragon';
+import PropTypes from 'prop-types';
+
+import { Chip, Hyperlink } from '@edx/paragon';
import { Error } from '@edx/paragon/icons';
import { getConfig } from '@edx/frontend-platform/config';
import BaseModalPopup from './BaseModalPopup';
+import EVENT_NAMES from '../../../eventTracking';
+import { useAssignmentStatusChip } from '../data';
-const FailedRedemption = () => {
- const [isOpen, open, close] = useToggle(false);
+const FailedRedemption = ({ trackEvent }) => {
const [target, setTarget] = useState(null);
+ const {
+ BUDGET_DETAILS_ASSIGNED_DATATABLE_CHIP_FAILED_REDEMPTION,
+ BUDGET_DETAILS_ASSIGNED_DATATABLE_CHIP_FAILED_REDEMPTION_HELP_CENTER,
+ } = EVENT_NAMES.LEARNER_CREDIT_MANAGEMENT;
+
+ const {
+ openChipModal,
+ closeChipModal,
+ isChipModalOpen,
+ helpCenterTrackEvent,
+ } = useAssignmentStatusChip({
+ chipInteractionEventName: BUDGET_DETAILS_ASSIGNED_DATATABLE_CHIP_FAILED_REDEMPTION,
+ chipHelpCenterEventName: BUDGET_DETAILS_ASSIGNED_DATATABLE_CHIP_FAILED_REDEMPTION_HELP_CENTER,
+ trackEvent,
+ });
+
return (
<>
Failed: Redemption
Failed: Redemption
@@ -44,7 +63,11 @@ const FailedRedemption = () => {
Get more troubleshooting help at{' '}
-
+
Help Center: Course Assignments
.
@@ -56,4 +79,8 @@ const FailedRedemption = () => {
);
};
+FailedRedemption.propTypes = {
+ trackEvent: PropTypes.func.isRequired,
+};
+
export default FailedRedemption;
diff --git a/src/components/learner-credit-management/assignments-status-chips/FailedReminder.jsx b/src/components/learner-credit-management/assignments-status-chips/FailedReminder.jsx
index 033ae7bca4..03519222c9 100644
--- a/src/components/learner-credit-management/assignments-status-chips/FailedReminder.jsx
+++ b/src/components/learner-credit-management/assignments-status-chips/FailedReminder.jsx
@@ -1,27 +1,45 @@
import React, { useState } from 'react';
-import { Chip, useToggle, Hyperlink } from '@edx/paragon';
+import PropTypes from 'prop-types';
+import { Chip, Hyperlink } from '@edx/paragon';
import { Error } from '@edx/paragon/icons';
+import { getConfig } from '@edx/frontend-platform/config';
import BaseModalPopup from './BaseModalPopup';
+import EVENT_NAMES from '../../../eventTracking';
+import { useAssignmentStatusChip } from '../data';
-const FailedReminder = () => {
- const [isOpen, open, close] = useToggle(false);
+const FailedReminder = ({ trackEvent }) => {
const [target, setTarget] = useState(null);
+ const {
+ BUDGET_DETAILS_ASSIGNED_DATATABLE_CHIP_FAILED_REMINDER,
+ BUDGET_DETAILS_ASSIGNED_DATATABLE_CHIP_FAILED_REMINDER_HELP_CENTER,
+ } = EVENT_NAMES.LEARNER_CREDIT_MANAGEMENT;
+
+ const {
+ openChipModal,
+ closeChipModal,
+ isChipModalOpen,
+ helpCenterTrackEvent,
+ } = useAssignmentStatusChip({
+ chipInteractionEventName: BUDGET_DETAILS_ASSIGNED_DATATABLE_CHIP_FAILED_REMINDER,
+ chipHelpCenterEventName: BUDGET_DETAILS_ASSIGNED_DATATABLE_CHIP_FAILED_REMINDER_HELP_CENTER,
+ trackEvent,
+ });
return (
<>
Failed: Reminder
Failed: Reminder
@@ -39,7 +57,11 @@ const FailedReminder = () => {
Get more troubleshooting help at{' '}
-
+
Help Center: Course Assignments
.
@@ -51,4 +73,8 @@ const FailedReminder = () => {
);
};
+FailedReminder.propTypes = {
+ trackEvent: PropTypes.func.isRequired,
+};
+
export default FailedReminder;
diff --git a/src/components/learner-credit-management/assignments-status-chips/FailedSystem.jsx b/src/components/learner-credit-management/assignments-status-chips/FailedSystem.jsx
index 43ae45e4b0..c5a1acb026 100644
--- a/src/components/learner-credit-management/assignments-status-chips/FailedSystem.jsx
+++ b/src/components/learner-credit-management/assignments-status-chips/FailedSystem.jsx
@@ -1,29 +1,47 @@
import React, { useState } from 'react';
-import { Chip, Hyperlink, useToggle } from '@edx/paragon';
+import PropTypes from 'prop-types';
+
+import { Chip, Hyperlink } from '@edx/paragon';
import { Error } from '@edx/paragon/icons';
import { getConfig } from '@edx/frontend-platform/config';
import BaseModalPopup from './BaseModalPopup';
+import EVENT_NAMES from '../../../eventTracking';
+import { useAssignmentStatusChip } from '../data';
-const FailedSystem = () => {
- const [isOpen, open, close] = useToggle(false);
+const FailedSystem = ({ trackEvent }) => {
const [target, setTarget] = useState(null);
+ const {
+ BUDGET_DETAILS_ASSIGNED_DATATABLE_CHIP_FAILED_SYSTEM,
+ BUDGET_DETAILS_ASSIGNED_DATATABLE_CHIP_FAILED_SYSTEM_HELP_CENTER,
+ } = EVENT_NAMES.LEARNER_CREDIT_MANAGEMENT;
+
+ const {
+ openChipModal,
+ closeChipModal,
+ isChipModalOpen,
+ helpCenterTrackEvent,
+ } = useAssignmentStatusChip({
+ chipInteractionEventName: BUDGET_DETAILS_ASSIGNED_DATATABLE_CHIP_FAILED_SYSTEM,
+ chipHelpCenterEventName: BUDGET_DETAILS_ASSIGNED_DATATABLE_CHIP_FAILED_SYSTEM_HELP_CENTER,
+ trackEvent,
+ });
return (
<>
Failed: System
Failed: System
@@ -38,7 +56,11 @@ const FailedSystem = () => {
Get more troubleshooting help at{' '}
-
+
Help Center: Course Assignments
.
@@ -50,4 +72,8 @@ const FailedSystem = () => {
);
};
+FailedSystem.propTypes = {
+ trackEvent: PropTypes.func.isRequired,
+};
+
export default FailedSystem;
diff --git a/src/components/learner-credit-management/assignments-status-chips/NotifyingLearner.jsx b/src/components/learner-credit-management/assignments-status-chips/NotifyingLearner.jsx
index 2380fda362..94ac11d39f 100644
--- a/src/components/learner-credit-management/assignments-status-chips/NotifyingLearner.jsx
+++ b/src/components/learner-credit-management/assignments-status-chips/NotifyingLearner.jsx
@@ -1,28 +1,39 @@
import React, { useState } from 'react';
import PropTypes from 'prop-types';
-import { Chip, useToggle } from '@edx/paragon';
+import { Chip } from '@edx/paragon';
import { Send } from '@edx/paragon/icons';
import BaseModalPopup from './BaseModalPopup';
+import EVENT_NAMES from '../../../eventTracking';
+import { useAssignmentStatusChip } from '../data';
-const NotifyingLearner = ({ learnerEmail }) => {
- const [isOpen, open, close] = useToggle(false);
+const NotifyingLearner = ({ learnerEmail, trackEvent }) => {
const [target, setTarget] = useState(null);
+ const { BUDGET_DETAILS_ASSIGNED_DATATABLE_CHIP_NOTIFY_LEARNER } = EVENT_NAMES.LEARNER_CREDIT_MANAGEMENT;
+
+ const {
+ openChipModal,
+ closeChipModal,
+ isChipModalOpen,
+ } = useAssignmentStatusChip({
+ chipInteractionEventName: BUDGET_DETAILS_ASSIGNED_DATATABLE_CHIP_NOTIFY_LEARNER,
+ trackEvent,
+ });
return (
<>
Notifying learner
Notifying {learnerEmail ?? 'learner'}
@@ -40,6 +51,7 @@ const NotifyingLearner = ({ learnerEmail }) => {
NotifyingLearner.propTypes = {
learnerEmail: PropTypes.string,
+ trackEvent: PropTypes.func.isRequired,
};
export default NotifyingLearner;
diff --git a/src/components/learner-credit-management/assignments-status-chips/WaitingForLearner.jsx b/src/components/learner-credit-management/assignments-status-chips/WaitingForLearner.jsx
index f1176b3317..819f918c53 100644
--- a/src/components/learner-credit-management/assignments-status-chips/WaitingForLearner.jsx
+++ b/src/components/learner-credit-management/assignments-status-chips/WaitingForLearner.jsx
@@ -1,31 +1,46 @@
import React, { useState } from 'react';
import PropTypes from 'prop-types';
-import { Chip, Hyperlink, useToggle } from '@edx/paragon';
+import { Chip, Hyperlink } from '@edx/paragon';
import { Timelapse } from '@edx/paragon/icons';
import { getConfig } from '@edx/frontend-platform/config';
import BaseModalPopup from './BaseModalPopup';
-import { ASSIGNMENT_ENROLLMENT_DEADLINE } from '../data';
+import { ASSIGNMENT_ENROLLMENT_DEADLINE, useAssignmentStatusChip } from '../data';
+import EVENT_NAMES from '../../../eventTracking';
-const WaitingForLearner = ({ learnerEmail }) => {
- const [isOpen, open, close] = useToggle(false);
+const WaitingForLearner = ({ learnerEmail, trackEvent }) => {
const [target, setTarget] = useState(null);
+ const {
+ BUDGET_DETAILS_ASSIGNED_DATATABLE_CHIP_WAITING_FOR_LEARNER,
+ BUDGET_DETAILS_ASSIGNED_DATATABLE_CHIP_WAITING_FOR_LEARNER_HELP_CENTER,
+ } = EVENT_NAMES.LEARNER_CREDIT_MANAGEMENT;
+
+ const {
+ openChipModal,
+ closeChipModal,
+ isChipModalOpen,
+ helpCenterTrackEvent,
+ } = useAssignmentStatusChip({
+ chipInteractionEventName: BUDGET_DETAILS_ASSIGNED_DATATABLE_CHIP_WAITING_FOR_LEARNER,
+ chipHelpCenterEventName: BUDGET_DETAILS_ASSIGNED_DATATABLE_CHIP_WAITING_FOR_LEARNER_HELP_CENTER,
+ trackEvent,
+ });
return (
<>
Waiting for learner
Waiting for {learnerEmail ?? 'learner'}
@@ -40,7 +55,11 @@ const WaitingForLearner = ({ learnerEmail }) => {
Need help?
Learn more about learner enrollment in assigned courses at{' '}
-
+
Help Center: Course Assignments
.
@@ -53,6 +72,7 @@ const WaitingForLearner = ({ learnerEmail }) => {
WaitingForLearner.propTypes = {
learnerEmail: PropTypes.string,
+ trackEvent: PropTypes.func.isRequired,
};
export default WaitingForLearner;
diff --git a/src/components/learner-credit-management/cards/NewAssignmentModalButton.jsx b/src/components/learner-credit-management/cards/NewAssignmentModalButton.jsx
index 790fc68d3f..4024990c9f 100644
--- a/src/components/learner-credit-management/cards/NewAssignmentModalButton.jsx
+++ b/src/components/learner-credit-management/cards/NewAssignmentModalButton.jsx
@@ -47,7 +47,7 @@ const NewAssignmentModalButton = ({ enterpriseId, course, children }) => {
} = useContext(BudgetDetailPageContext);
const { data: subsidyAccessPolicy } = useSubsidyAccessPolicy(subsidyAccessPolicyId);
const {
- subsidyUuid, assignmentConfiguration, isSubsidyActive, isAssignable, catalogUuid,
+ subsidyUuid, assignmentConfiguration, isSubsidyActive, isAssignable, catalogUuid, aggregates,
} = subsidyAccessPolicy;
const sharedEnterpriseTrackEventMetadata = {
subsidyAccessPolicyId,
@@ -55,10 +55,11 @@ const NewAssignmentModalButton = ({ enterpriseId, course, children }) => {
subsidyUuid,
isSubsidyActive,
isAssignable,
+ aggregates,
contentPriceCents: course.normalizedMetadata.contentPrice * 100,
contentKey: course.key,
courseUuid: course.uuid,
- assignmentConfigurationUuid: assignmentConfiguration.uuid,
+ assignmentConfiguration,
};
const { mutate } = useAllocateContentAssignments();
@@ -94,11 +95,13 @@ const NewAssignmentModalButton = ({ enterpriseId, course, children }) => {
const onSuccessEnterpriseTrackEvents = ({
totalLearnersAllocated,
totalLearnersAlreadyAllocated,
+ response,
}) => {
const trackEventMetadata = {
...sharedEnterpriseTrackEventMetadata,
totalLearnersAllocated,
totalLearnersAlreadyAllocated,
+ response,
};
sendEnterpriseTrackEvent(
enterpriseId,
@@ -120,7 +123,7 @@ const NewAssignmentModalButton = ({ enterpriseId, course, children }) => {
setAssignButtonState('pending');
setCreateAssignmentsErrorReason(null);
mutate(mutationArgs, {
- onSuccess: ({ created, noChange, updated }) => {
+ onSuccess: (res) => {
setAssignButtonState('complete');
// Ensure the budget and budgets queries are invalidated so that the relevant
// queries become stale and refetches new updated data from the API.
@@ -131,11 +134,12 @@ const NewAssignmentModalButton = ({ enterpriseId, course, children }) => {
queryKey: learnerCreditManagementQueryKeys.budgets(enterpriseId),
});
handleCloseAssignmentModal();
- const totalLearnersAllocated = created.length + updated.length;
- const totalLearnersAlreadyAllocated = noChange.length;
+ const totalLearnersAllocated = res.created.length + res.updated.length;
+ const totalLearnersAlreadyAllocated = res.noChange.length;
onSuccessEnterpriseTrackEvents({
totalLearnersAllocated,
totalLearnersAlreadyAllocated,
+ res,
});
displayToastForAssignmentAllocation({
totalLearnersAllocated,
@@ -167,6 +171,7 @@ const NewAssignmentModalButton = ({ enterpriseId, course, children }) => {
totalAllocatedLearners: learnerEmails.length,
errorStatus: httpErrorStatus,
errorReason,
+ response: err,
},
);
},
@@ -185,7 +190,10 @@ const NewAssignmentModalButton = ({ enterpriseId, course, children }) => {
sendEnterpriseTrackEvent(
enterpriseId,
EVENT_NAMES.LEARNER_CREDIT_MANAGEMENT.ASSIGNMENT_MODAL_EXIT,
- { assignButtonState },
+ {
+ ...sharedEnterpriseTrackEventMetadata,
+ assignButtonState,
+ },
);
}}
footerNode={(
@@ -196,6 +204,10 @@ const NewAssignmentModalButton = ({ enterpriseId, course, children }) => {
onClick={() => sendEnterpriseTrackEvent(
enterpriseId,
EVENT_NAMES.LEARNER_CREDIT_MANAGEMENT.ASSIGNMENT_MODAL_HELP_CENTER,
+ {
+ ...sharedEnterpriseTrackEventMetadata,
+ assignButtonState,
+ },
)}
destination={getConfig().ENTERPRISE_SUPPORT_LEARNER_CREDIT_URL}
showLaunchIcon
@@ -211,7 +223,10 @@ const NewAssignmentModalButton = ({ enterpriseId, course, children }) => {
sendEnterpriseTrackEvent(
enterpriseId,
EVENT_NAMES.LEARNER_CREDIT_MANAGEMENT.ASSIGNMENT_MODAL_CANCEL,
- { assignButtonState },
+ {
+ ...sharedEnterpriseTrackEventMetadata,
+ assignButtonState,
+ },
);
}}
>
diff --git a/src/components/learner-credit-management/cards/tests/CourseCard.test.jsx b/src/components/learner-credit-management/cards/tests/CourseCard.test.jsx
index 517f14fbd5..f5fed6a990 100644
--- a/src/components/learner-credit-management/cards/tests/CourseCard.test.jsx
+++ b/src/components/learner-credit-management/cards/tests/CourseCard.test.jsx
@@ -9,7 +9,6 @@ import { QueryClientProvider, useQueryClient } from '@tanstack/react-query';
import { AppContext } from '@edx/frontend-platform/react';
import { IntlProvider } from '@edx/frontend-platform/i18n';
import { renderWithRouter, sendEnterpriseTrackEvent } from '@edx/frontend-enterprise-utils';
-
import CourseCard from '../CourseCard';
import {
formatPrice,
diff --git a/src/components/learner-credit-management/data/hooks/index.js b/src/components/learner-credit-management/data/hooks/index.js
index 63681a4030..396153dad6 100644
--- a/src/components/learner-credit-management/data/hooks/index.js
+++ b/src/components/learner-credit-management/data/hooks/index.js
@@ -12,3 +12,4 @@ export { default as useSuccessfulAssignmentToastContextValue } from './useSucces
export { default as useSuccessfulCancellationToastContextValue } from './useSuccessfulCancellationToastContextValue';
export { default as useSuccessfulReminderToastContextValue } from './useSuccessfulReminderToastContextValue';
export { default as useEnterpriseOffer } from './useEnterpriseOffer';
+export { default as useAssignmentStatusChip } from './useAssignmentStatusChip';
diff --git a/src/components/learner-credit-management/data/hooks/useAssignmentStatusChip.jsx b/src/components/learner-credit-management/data/hooks/useAssignmentStatusChip.jsx
new file mode 100644
index 0000000000..f74dc90a2a
--- /dev/null
+++ b/src/components/learner-credit-management/data/hooks/useAssignmentStatusChip.jsx
@@ -0,0 +1,39 @@
+import { useToggle } from '@edx/paragon';
+
+/**
+ *
+ * @param chipInteractionEventName {String} - The event name that will be read in Segment of a chip opening and closing
+ * @param chipHelpCenterEventName {String} - The event name that will be read in Segment for a help center link
+ * interaction
+ * @param trackEvent {Function} - The track event functioning that will be sending a track event
+ * @returns {{
+ * closeChipModal: closeChipModal,
+ * openChipModal: openChipModal,
+ * helpCenterTrackEvent: helpCenterTrackEvent,
+ * isChipModalOpen: *
+ * }}
+ */
+export default function useAssignmentStatusChip({ chipInteractionEventName, chipHelpCenterEventName, trackEvent }) {
+ const [isChipModalOpen, open, close] = useToggle(false);
+ const openChipModal = () => {
+ open();
+ // Note: could hardcode true given this function *always* opens
+ trackEvent(chipInteractionEventName, { isOpen: true });
+ };
+ const closeChipModal = () => {
+ close();
+ // Note: could hardcode false given this function *always* closes
+ trackEvent(chipInteractionEventName, { isOpen: false });
+ };
+
+ const helpCenterTrackEvent = () => {
+ trackEvent(chipHelpCenterEventName);
+ };
+
+ return {
+ isChipModalOpen,
+ openChipModal,
+ closeChipModal,
+ helpCenterTrackEvent,
+ };
+}
diff --git a/src/components/learner-credit-management/data/utils.js b/src/components/learner-credit-management/data/utils.js
index 51746f3eca..2216a01580 100644
--- a/src/components/learner-credit-management/data/utils.js
+++ b/src/components/learner-credit-management/data/utils.js
@@ -337,3 +337,60 @@ export async function retrieveBudgetDetailActivityOverview({
}
return result;
}
+
+/**
+ * Takes the raw selected flat rows data from the 'Assigned' datatable and returns metadata that is used for tracking
+ * bulk enrollment of reminders and bulk enrollment of cancellations.
+ * @param {Array} selectedFlatRows An array of selectedFlatRows from the activity 'Assigned' table
+ * @returns {{
+ * uniqueLearnerState: [String],
+ * totalContentQuantity: Number,
+ * assignmentConfigurationUuid: String,
+ * assignmentUuids: [String]
+ * uniqueContentKeys: [String],
+ * uniqueAssignmentState: [String],
+ * totalSelectedRows: Number,
+ * }}
+ */
+export const transformSelectedRows = (selectedFlatRows) => {
+ const assignmentUuids = selectedFlatRows.map(item => item.id);
+ const totalSelectedRows = selectedFlatRows.length;
+
+ // Count of unique content keys, where the key is the course,
+ // and value is count of the course.
+ const flatMappedContentKeys = selectedFlatRows.map(item => item?.original?.contentKey);
+ const uniqueContentKeys = {};
+ flatMappedContentKeys.forEach((courseKey) => {
+ uniqueContentKeys[courseKey] = (uniqueContentKeys[courseKey] || 0) + 1;
+ });
+
+ // Count of unique learner states, where the key is the learnerState,
+ // and value is count of the learnerState.
+ const flatMappedLearnerState = selectedFlatRows.map(item => item?.original?.learnerState);
+ const uniqueLearnerState = {};
+ flatMappedLearnerState.forEach((learnerState) => {
+ uniqueLearnerState[learnerState] = (uniqueLearnerState[learnerState] || 0) + 1;
+ });
+
+ // Count of unique assignment states, where the key is the assignment state,
+ // and value is count of the assignment state.
+ const flatMappedAssignmentState = selectedFlatRows.map(item => item?.original?.state);
+ const uniqueAssignmentState = {};
+ flatMappedAssignmentState.forEach((state) => {
+ uniqueAssignmentState[state] = (uniqueAssignmentState[state] || 0) + 1;
+ });
+
+ // Total value of all the selected rows accumulated from the contentQuantity
+ const totalContentQuantity = selectedFlatRows.map(
+ item => item.original.contentQuantity,
+ ).reduce((prev, next) => prev + next, 0);
+
+ return {
+ uniqueAssignmentState,
+ uniqueLearnerState,
+ uniqueContentKeys,
+ totalContentQuantity,
+ assignmentUuids,
+ totalSelectedRows,
+ };
+};
diff --git a/src/components/learner-credit-management/tests/BudgetDetailPage.test.jsx b/src/components/learner-credit-management/tests/BudgetDetailPage.test.jsx
index 9052c8d93c..b3ade09d7b 100644
--- a/src/components/learner-credit-management/tests/BudgetDetailPage.test.jsx
+++ b/src/components/learner-credit-management/tests/BudgetDetailPage.test.jsx
@@ -1175,10 +1175,25 @@ describe('', () => {
expect(statusChip).toBeInTheDocument();
userEvent.click(statusChip);
+ expect(sendEnterpriseTrackEvent).toHaveBeenCalledTimes(1);
+
// Modal popup is visible with expected text
const modalPopupContents = within(screen.getByTestId('assignment-status-modalpopup-contents'));
expect(modalPopupContents.getByText(expectedModalPopupHeading)).toBeInTheDocument();
expect(modalPopupContents.getByText(expectedModalPopupContent, { exact: false })).toBeInTheDocument();
+
+ // Help Center link clicked and modal closed
+ if (screen.queryByText('Help Center: Course Assignments')) {
+ const helpCenterLink = screen.getByText('Help Center: Course Assignments');
+ userEvent.click(helpCenterLink);
+ expect(sendEnterpriseTrackEvent).toHaveBeenCalledTimes(2);
+ // Click chip to close modal
+ userEvent.click(statusChip);
+ expect(sendEnterpriseTrackEvent).toHaveBeenCalled();
+ } else {
+ userEvent.click(statusChip);
+ expect(sendEnterpriseTrackEvent).toHaveBeenCalledTimes(2);
+ }
});
it.each([
@@ -1332,6 +1347,8 @@ describe('', () => {
userEvent.click(catalogTab);
});
+ expect(sendEnterpriseTrackEvent).toHaveBeenCalledTimes(1);
+
await waitFor(() => {
expect(screen.getByTestId('budget-detail-catalog-tab-contents')).toBeInTheDocument();
});
@@ -1516,6 +1533,7 @@ describe('', () => {
const cancelBulkActionButton = screen.getByText('Cancel (2)');
expect(cancelBulkActionButton).toBeInTheDocument();
userEvent.click(cancelBulkActionButton);
+ expect(sendEnterpriseTrackEvent).toHaveBeenCalledTimes(1);
const modalDialog = screen.getByRole('dialog');
expect(modalDialog).toBeInTheDocument();
const cancelDialogButton = getButtonElement('Cancel assignments (2)');
@@ -1528,6 +1546,7 @@ describe('', () => {
await waitFor(
() => expect(screen.getByText('Assignments canceled (2)')).toBeInTheDocument(),
);
+ expect(sendEnterpriseTrackEvent).toHaveBeenCalledTimes(2);
});
it('reminds assignments in bulk', async () => {
@@ -1608,6 +1627,7 @@ describe('', () => {
expect(modalDialog).toBeInTheDocument();
const remindDialogButton = getButtonElement('Send reminders (2)');
userEvent.click(remindDialogButton);
+ expect(sendEnterpriseTrackEvent).toHaveBeenCalledTimes(1);
await waitFor(
() => expect(
EnterpriseAccessApiService.remindAllContentAssignments,
@@ -1616,6 +1636,7 @@ describe('', () => {
await waitFor(
() => expect(screen.getByText('Reminders sent (2)')).toBeInTheDocument(),
);
+ expect(sendEnterpriseTrackEvent).toHaveBeenCalledTimes(2);
});
it('cancels a single assignment', async () => {
@@ -1666,6 +1687,7 @@ describe('', () => {
const cancelIconButton = screen.getByTestId('cancel-assignment-test-uuid');
expect(cancelIconButton).toBeInTheDocument();
userEvent.click(cancelIconButton);
+ expect(sendEnterpriseTrackEvent).toHaveBeenCalledTimes(1);
const modalDialog = screen.getByRole('dialog');
expect(modalDialog).toBeInTheDocument();
const cancelDialogButton = getButtonElement('Cancel assignment');
@@ -1673,6 +1695,7 @@ describe('', () => {
await waitFor(
() => expect(screen.getByText('Assignment canceled')).toBeInTheDocument(),
);
+ expect(sendEnterpriseTrackEvent).toHaveBeenCalledTimes(2);
});
it('reminds a single assignment', async () => {
EnterpriseAccessApiService.remindContentAssignments.mockResolvedValueOnce({ status: 200 });
@@ -1722,6 +1745,7 @@ describe('', () => {
const remindIconButton = screen.getByTestId('remind-assignment-test-uuid');
expect(remindIconButton).toBeInTheDocument();
userEvent.click(remindIconButton);
+ expect(sendEnterpriseTrackEvent).toHaveBeenCalledTimes(1);
const modalDialog = screen.getByRole('dialog');
expect(modalDialog).toBeInTheDocument();
const remindDialogButton = getButtonElement('Send reminder');
@@ -1729,5 +1753,6 @@ describe('', () => {
await waitFor(
() => expect(screen.getByText('Reminder sent')).toBeInTheDocument(),
);
+ expect(sendEnterpriseTrackEvent).toHaveBeenCalledTimes(2);
});
});
diff --git a/src/components/learner-credit-management/tests/BudgetDetailPageWrapper.test.jsx b/src/components/learner-credit-management/tests/BudgetDetailPageWrapper.test.jsx
index 96c7ed5fc2..7ce956ab94 100644
--- a/src/components/learner-credit-management/tests/BudgetDetailPageWrapper.test.jsx
+++ b/src/components/learner-credit-management/tests/BudgetDetailPageWrapper.test.jsx
@@ -7,9 +7,12 @@ import { Provider } from 'react-redux';
import thunk from 'redux-thunk';
import { IntlProvider } from '@edx/frontend-platform/i18n';
import '@testing-library/jest-dom/extend-expect';
+import { QueryClientProvider } from '@tanstack/react-query';
+import { renderWithRouter, sendEnterpriseTrackEvent } from '@edx/frontend-enterprise-utils';
import BudgetDetailPageWrapper, { BudgetDetailPageContext } from '../BudgetDetailPageWrapper';
-import { getButtonElement } from '../../test/testUtils';
+import { getButtonElement, queryClient } from '../../test/testUtils';
+import BudgetDetailPageBreadcrumbs from '../BudgetDetailPageBreadcrumbs';
const mockStore = configureMockStore([thunk]);
const getMockStore = store => mockStore(store);
@@ -26,19 +29,27 @@ const defaultStoreState = {
},
};
+jest.mock('@edx/frontend-enterprise-utils', () => ({
+ ...jest.requireActual('@edx/frontend-enterprise-utils'),
+ sendEnterpriseTrackEvent: jest.fn(),
+}));
+
const MockBudgetDetailPageWrapper = ({
initialStoreState = defaultStoreState,
children,
}) => {
const store = getMockStore(initialStoreState);
return (
-
-
-
- {children}
-
-
-
+
+
+
+
+ {children}
+
+
+
+
+
);
};
@@ -263,4 +274,16 @@ describe('', () => {
expect(screen.queryByText(expectedToastMessage)).not.toBeInTheDocument();
});
});
+ it('calls segment event on breadcrumb click', () => {
+ const mockBudgetDisplayName = 'Test Budget';
+ renderWithRouter(
+
+
+ ,
+ );
+ const previousBreadcrumb = screen.getByText('Budgets');
+
+ userEvent.click(previousBreadcrumb);
+ expect(sendEnterpriseTrackEvent).toHaveBeenCalledTimes(1);
+ });
});
diff --git a/src/components/settings/SettingsSSOTab/steps/NewSSOConfigConfigureStep.tsx b/src/components/settings/SettingsSSOTab/steps/NewSSOConfigConfigureStep.tsx
index 31070334ca..cd2a6e1efe 100644
--- a/src/components/settings/SettingsSSOTab/steps/NewSSOConfigConfigureStep.tsx
+++ b/src/components/settings/SettingsSSOTab/steps/NewSSOConfigConfigureStep.tsx
@@ -103,6 +103,14 @@ const SSOConfigConfigureStep = () => {
fieldInstructions="URN of SAML attribute containing the user's email address[es]."
/>
+
+
+
>
);
const renderSAPFields = () => (
diff --git a/src/eventTracking.js b/src/eventTracking.js
index ed4244786f..d77196db3e 100644
--- a/src/eventTracking.js
+++ b/src/eventTracking.js
@@ -103,14 +103,54 @@ export const SUBSCRIPTION_EVENTS = {
};
export const LEARNER_CREDIT_MANAGEMENT_EVENTS = {
+ BREADCRUMB_FROM_BUDGET_DETAIL_TO_BUDGETS: `${LEARNER_CREDIT_MANAGEMENT_PREFIX}.budget_detail.breadcrumb_budget_detail_to_budgets.clicked`,
TAB_CHANGED: `${LEARNER_CREDIT_MANAGEMENT_PREFIX}.budget_detail.tab.changed`,
+ // Budget Overview
+ BUDGET_OVERVIEW_CONTACT_US: `${LEARNER_CREDIT_MANAGEMENT_PREFIX}.budget_detail.contact_us.clicked`,
+ BUDGET_OVERVIEW_NEW_ASSIGNMENT: `${LEARNER_CREDIT_MANAGEMENT_PREFIX}.budget_detail.new_assignment.clicked`,
+ BUDGET_OVERVIEW_UTILIZATION_DROPDOWN_TOGGLE: `${LEARNER_CREDIT_MANAGEMENT_PREFIX}.budget_detail.utilization_dropdown.toggled`,
+ BUDGET_OVERVIEW_UTILIZATION_VIEW_SPENT_TABLE: `${LEARNER_CREDIT_MANAGEMENT_PREFIX}.budget_detail.view_spent_activity.clicked`,
+ BUDGET_OVERVIEW_UTILIZATION_VIEW_ASSIGNED_TABLE: `${LEARNER_CREDIT_MANAGEMENT_PREFIX}.budget_detail.view_assigned_activity.clicked`,
// Activity tab
+ // Activity tab assigned datatable
BUDGET_DETAILS_ASSIGNED_DATATABLE_SORT_BY_OR_FILTER: `${BUDGET_DETAIL_ACTIVITY_TAB_PREFIX}.assigned_table.changed`,
BUDGET_DETAILS_ASSIGNED_DATATABLE_VIEW_COURSE: `${BUDGET_DETAIL_ACTIVITY_TAB_PREFIX}.assigned_table_view_course.clicked`,
BUDGET_DETAILS_ASSIGNED_DATATABLE_ACTIONS_REFRESH: `${BUDGET_DETAIL_ACTIVITY_TAB_PREFIX}.assigned_table_refresh.clicked`,
+ // Activity tab assigned table remind
+ BUDGET_DETAILS_ASSIGNED_DATATABLE_OPEN_REMIND_MODAL: `${BUDGET_DETAIL_ACTIVITY_TAB_PREFIX}.assigned_table_remind_modal_open.clicked`,
+ BUDGET_DETAILS_ASSIGNED_DATATABLE_CLOSE_REMIND_MODAL: `${BUDGET_DETAIL_ACTIVITY_TAB_PREFIX}.assigned_table_remind_modal_close.clicked`,
+ BUDGET_DETAILS_ASSIGNED_DATATABLE_REMIND: `${BUDGET_DETAIL_ACTIVITY_TAB_PREFIX}.assigned_table_remind.clicked`,
+ // Activity tab assigned table bulk remind
+ BUDGET_DETAILS_ASSIGNED_DATATABLE_OPEN_BULK_REMIND_MODAL: `${BUDGET_DETAIL_ACTIVITY_TAB_PREFIX}.assigned_table_bulk_remind_modal_open.clicked`,
+ BUDGET_DETAILS_ASSIGNED_DATATABLE_CLOSE_BULK_REMIND_MODAL: `${BUDGET_DETAIL_ACTIVITY_TAB_PREFIX}.assigned_table_bulk_remind_modal_close.clicked`,
+ BUDGET_DETAILS_ASSIGNED_DATATABLE_BULK_REMIND: `${BUDGET_DETAIL_ACTIVITY_TAB_PREFIX}.assigned_table_bulk_remind.clicked`,
+ // Activity tab assigned table cancel
+ BUDGET_DETAILS_ASSIGNED_DATATABLE_OPEN_CANCEL_MODAL: `${BUDGET_DETAIL_ACTIVITY_TAB_PREFIX}.assigned_table_cancel_modal_open.clicked`,
+ BUDGET_DETAILS_ASSIGNED_DATATABLE_CLOSE_CANCEL_MODAL: `${BUDGET_DETAIL_ACTIVITY_TAB_PREFIX}.assigned_table_cancel_modal_close.clicked`,
+ BUDGET_DETAILS_ASSIGNED_DATATABLE_CANCEL: `${BUDGET_DETAIL_ACTIVITY_TAB_PREFIX}.assigned_table_cancel.clicked`,
+ // Activity tab assigned table bulk cancel
+ BUDGET_DETAILS_ASSIGNED_DATATABLE_OPEN_BULK_CANCEL_MODAL: `${BUDGET_DETAIL_ACTIVITY_TAB_PREFIX}.assigned_table_bulk_cancel_modal_open.clicked`,
+ BUDGET_DETAILS_ASSIGNED_DATATABLE_CLOSE_BULK_CANCEL_MODAL: `${BUDGET_DETAIL_ACTIVITY_TAB_PREFIX}.assigned_table_bulk_cancel_modal_close.clicked`,
+ BUDGET_DETAILS_ASSIGNED_DATATABLE_BULK_CANCEL: `${BUDGET_DETAIL_ACTIVITY_TAB_PREFIX}.assigned_table_bulk_cancel.clicked`,
+ // Activity tab spent datatable
BUDGET_DETAILS_SPENT_DATATABLE_SORT_BY_OR_FILTER: `${BUDGET_DETAIL_ACTIVITY_TAB_PREFIX}.spent_table.changed`,
BUDGET_DETAILS_SPENT_DATATABLE_VIEW_COURSE: `${BUDGET_DETAIL_ACTIVITY_TAB_PREFIX}.spent_table_view_course.clicked`,
EMPTY_STATE_CTA: `${BUDGET_DETAIL_ACTIVITY_TAB_PREFIX}.empty_state_cta_to_catalog.clicked`,
+ // Activity tab chips
+ BUDGET_DETAILS_ASSIGNED_DATATABLE_CHIP_NOTIFY_LEARNER: `${BUDGET_DETAIL_ACTIVITY_TAB_PREFIX}.assigned_table_chip_notify_learner.clicked`,
+ BUDGET_DETAILS_ASSIGNED_DATATABLE_CHIP_WAITING_FOR_LEARNER: `${BUDGET_DETAIL_ACTIVITY_TAB_PREFIX}.assigned_table_chip_waiting_for_learner.clicked`,
+ BUDGET_DETAILS_ASSIGNED_DATATABLE_CHIP_FAILED_CANCELLATION: `${BUDGET_DETAIL_ACTIVITY_TAB_PREFIX}.assigned_table_chip_failed_cancellation.clicked`,
+ BUDGET_DETAILS_ASSIGNED_DATATABLE_CHIP_FAILED_SYSTEM: `${BUDGET_DETAIL_ACTIVITY_TAB_PREFIX}.assigned_table_chip_failed_system.clicked`,
+ BUDGET_DETAILS_ASSIGNED_DATATABLE_CHIP_FAILED_EMAIL: `${BUDGET_DETAIL_ACTIVITY_TAB_PREFIX}.assigned_table_chip_failed_bad_email.clicked`,
+ BUDGET_DETAILS_ASSIGNED_DATATABLE_CHIP_FAILED_REMINDER: `${BUDGET_DETAIL_ACTIVITY_TAB_PREFIX}.assigned_table_chip_failed_reminder.clicked`,
+ BUDGET_DETAILS_ASSIGNED_DATATABLE_CHIP_FAILED_REDEMPTION: `${BUDGET_DETAIL_ACTIVITY_TAB_PREFIX}.assigned_table_chip_failed_redemption.clicked`,
+ // Activity tab chips help center links
+ BUDGET_DETAILS_ASSIGNED_DATATABLE_CHIP_WAITING_FOR_LEARNER_HELP_CENTER: `${BUDGET_DETAIL_ACTIVITY_TAB_PREFIX}.assigned_table_chip_waiting_for_learner_help_center.clicked`,
+ BUDGET_DETAILS_ASSIGNED_DATATABLE_CHIP_FAILED_CANCELLATION_HELP_CENTER: `${BUDGET_DETAIL_ACTIVITY_TAB_PREFIX}.assigned_table_chip_failed_cancellation_help_center.clicked`,
+ BUDGET_DETAILS_ASSIGNED_DATATABLE_CHIP_FAILED_SYSTEM_HELP_CENTER: `${BUDGET_DETAIL_ACTIVITY_TAB_PREFIX}.assigned_table_chip_failed_system_help_center.clicked`,
+ BUDGET_DETAILS_ASSIGNED_DATATABLE_CHIP_FAILED_EMAIL_HELP_CENTER: `${BUDGET_DETAIL_ACTIVITY_TAB_PREFIX}.assigned_table_chip_failed_bad_email_help_center.clicked`,
+ BUDGET_DETAILS_ASSIGNED_DATATABLE_CHIP_FAILED_REMINDER_HELP_CENTER: `${BUDGET_DETAIL_ACTIVITY_TAB_PREFIX}.assigned_table_chip_failed_reminder_help_center.clicked`,
+ BUDGET_DETAILS_ASSIGNED_DATATABLE_CHIP_FAILED_REDEMPTION_HELP_CENTER: `${BUDGET_DETAIL_ACTIVITY_TAB_PREFIX}.assigned_table_chip_failed_redemption_help_center.clicked`,
// Catalog tab
// Catalog tab search
VIEW_COURSE: `${BUDGET_DETAIL_SEARCH_PREFIX}.view_course.clicked`,