Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18,776 changes: 9,491 additions & 9,285 deletions src/components/Admin/__snapshots__/Admin.test.jsx.snap

Large diffs are not rendered by default.

82 changes: 44 additions & 38 deletions src/components/Admin/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import DownloadCsvButton from '../../containers/DownloadCsvButton';
import AdminCards from '../../containers/AdminCards';
import AdminSearchForm from './AdminSearchForm';
import EnterpriseAppSkeleton from '../EnterpriseApp/EnterpriseAppSkeleton';
import { TRACK_LEARNER_PROGRESS_TARGETS } from '../ProductTours/AdminOnboardingTours/constants';

import EnterpriseDataApiService from '../../data/services/EnterpriseDataApiService';
import { formatTimestamp } from '../../utils';
Expand Down Expand Up @@ -451,36 +452,37 @@ class Admin extends React.Component {
<Helmet title="Learner Progress Report" />
<Hero title="Learner Progress Report" />
<div className="container-fluid">
<div className="row mt-4">
<div className="col">
<BudgetExpiryAlertAndModal />
<h2>
<FormattedMessage
id="admin.portal.lpr.overview.heading"
defaultMessage="Overview"
description="Heading for the overview section of the learner progress report page"
/>
</h2>
<div id={TRACK_LEARNER_PROGRESS_TARGETS.LPR_OVERVIEW}>
<div className="row mt-4">
<div className="col">
<BudgetExpiryAlertAndModal />
<h2>
<FormattedMessage
id="admin.portal.lpr.overview.heading"
defaultMessage="Overview"
description="Heading for the overview section of the learner progress report page"
/>
</h2>
</div>
</div>
</div>
<div className="row mt-4">
<div className="col">
{insightsLoading ? <AIAnalyticsSummarySkeleton /> : (
hasCompleteInsights && <AIAnalyticsSummary enterpriseId={enterpriseId} />
<div className="row mt-4">
<div id={TRACK_LEARNER_PROGRESS_TARGETS.AI_SUMMARY} className="col">
{insightsLoading ? <AIAnalyticsSummarySkeleton /> : (
hasCompleteInsights && <AIAnalyticsSummary enterpriseId={enterpriseId} />
)}
</div>
</div>
<div className="row mt-3">
{(error || loading) ? (
<div className="col">
{error && this.renderErrorMessage()}
{loading && <AdminCardsSkeleton />}
</div>
) : (
<AdminCards />
)}
</div>
</div>
<div className="row mt-3">
{(error || loading) ? (
<div className="col">
{error && this.renderErrorMessage()}
{loading && <AdminCardsSkeleton />}
</div>
) : (
<AdminCards />
)}
</div>

<div className="row">
<div className="col mb-4.5">
<SubscriptionData enterpriseId={enterpriseId}>
Expand All @@ -490,7 +492,7 @@ class Admin extends React.Component {
</div>

<div className="row mt-4" id="learner-progress-report">
<div className="col">
<div className="col" id={TRACK_LEARNER_PROGRESS_TARGETS.PROGRESS_REPORT}>
<div className="row">
<div className="col-12 col-md-3 col-xl-2 mb-2 mb-md-0">
<h2 className="table-title" ref={this.fullReportRef}>{tableMetadata.title}</h2>
Expand Down Expand Up @@ -542,26 +544,29 @@ class Admin extends React.Component {
defaultMessage: 'Learner Progress Report',
description: 'Title for the learner progress report tab in admin portal.',
})}
id={TRACK_LEARNER_PROGRESS_TARGETS.FULL_PROGRESS_REPORT}
>
<div className="row">
<div className="col">
{!error && !loading && !this.hasEmptyData() && (
<>
<div className="row pb-3 mt-2">
<div className="col-12 col-md-12 col-xl-12">
<div className="col-12 col-md-12 col-xl-12" id={TRACK_LEARNER_PROGRESS_TARGETS.CSV_DOWNLOAD}>
{this.renderDownloadButton()}
</div>
</div>
{this.displaySearchBar() && (
<AdminSearchForm
searchParams={searchParams}
searchEnrollmentsList={() => this.props.searchEnrollmentsList()}
tableData={this.getTableData() ? this.getTableData().results : []}
budgets={budgets}
groups={groups}
enterpriseId={enterpriseId}
/>
)}
<span id={TRACK_LEARNER_PROGRESS_TARGETS.FILTER}>
{this.displaySearchBar() && (
<AdminSearchForm
searchParams={searchParams}
searchEnrollmentsList={() => this.props.searchEnrollmentsList()}
tableData={this.getTableData() ? this.getTableData().results : []}
budgets={budgets}
groups={groups}
enterpriseId={enterpriseId}
/>
)}
</span>
</>
)}
{csvErrorMessage && this.renderCsvErrorMessage(csvErrorMessage)}
Expand All @@ -578,6 +583,7 @@ class Admin extends React.Component {
defaultMessage: 'Module Activity (Executive Education)',
description: 'Title for the module activity tab in admin portal.',
})}
id={TRACK_LEARNER_PROGRESS_TARGETS.MODULE_ACTIVITY}
>
<div className="mt-3">
<ModuleActivityReport enterpriseId={enterpriseId} />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React, { FC } from 'react';
import React, { FC, useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { ProductTour } from '@openedx/paragon';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
Expand All @@ -9,29 +10,44 @@ import '../_ProductTours.scss';
interface AdminOnboardingToursProps {
isOpen: boolean;
onClose: () => void;
enterpriseSlug: string;
targetSelector: string;
adminUuid: string,
setTarget: Function,
enterpriseSlug: string;
}

interface RootState {
portalConfiguration: {
enterpriseSlug: string;
};
enterpriseCustomerAdmin: {
uuid: string;
}
}

const AdminOnboardingTours: FC<AdminOnboardingToursProps> = ({
isOpen,
onClose,
enterpriseSlug,
targetSelector,
adminUuid,
setTarget,
enterpriseSlug,
}) => {
const learnerProgressStep = useLearnerProgressTour({ enterpriseSlug });
const [currentStep, setCurrentStep] = useState(0);
const learnerProgressSteps = useLearnerProgressTour({ enterpriseSlug, adminUuid });

useEffect(() => {
if (learnerProgressSteps[currentStep]) {
const nextTarget = learnerProgressSteps[currentStep].target.replace('#', '');
setTarget(nextTarget);
}
}, [currentStep, learnerProgressSteps, setTarget]);

const tours = [
{
tourId: 'admin-onboarding-tour',
enabled: isOpen,
startingIndex: 0,
startingIndex: currentStep,
advanceButtonText: (
<FormattedMessage
id="adminPortal.productTours.adminOnboarding.next"
Expand All @@ -49,32 +65,44 @@ const AdminOnboardingTours: FC<AdminOnboardingToursProps> = ({
endButtonText: (
<FormattedMessage
id="adminPortal.productTours.adminOnboarding.end"
defaultMessage="Complete"
defaultMessage="Keep going"
description="Text for the end button"
/>
),
onDismiss: onClose,
onEnd: onClose,
onEscape: onClose,
checkpoints: [learnerProgressStep],
checkpoints: learnerProgressSteps.map((step, index) => ({
...step,
onAdvance: () => {
setCurrentStep(index + 1);
step.onAdvance();
},
})),
},
];

if (!isOpen) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[inform] It's OK to render the ProductTour component itself because each tour itself has the enabled: boolean flag, which is also set to isOpen. Then, CheckpointOverlay appears to return null as well if there's no rect, so rendering both CheckpointOverlay and ProductTour here irrespective of isOpen should be a no-op.

return null;
}

return (
<>
<CheckpointOverlay target={`#${targetSelector}`} />
<CheckpointOverlay target={targetSelector} />
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[inform] Ensuring we always include # when defining selectors, since the selector is intended to semantically include the #.

<ProductTour
tours={tours}
/>
</>
);
};

AdminOnboardingTours.propTypes = {
isOpen: PropTypes.bool.isRequired,
onClose: PropTypes.func.isRequired,
targetSelector: PropTypes.string.isRequired,
setTarget: PropTypes.func.isRequired,
adminUuid: PropTypes.string.isRequired,
enterpriseSlug: PropTypes.string.isRequired,
};

const mapStateToProps = (state: RootState) => ({
adminUuid: state.enterpriseCustomerAdmin.uuid,
enterpriseSlug: state.portalConfiguration.enterpriseSlug,
});

Expand Down
20 changes: 18 additions & 2 deletions src/components/ProductTours/AdminOnboardingTours/constants.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,24 @@
// Admin Tour Targets
// ADMIN TOUR TARGETS

// Track learner progress - LPR flow
const LEARNER_PROGRESS_SIDEBAR = 'learner-progress-sidebar';
const LPR_OVERVIEW = 'lpr-overview';
const AI_SUMMARY = 'ai-summary';
const PROGRESS_REPORT = 'progress-report';
const FULL_PROGRESS_REPORT = 'full-progress-report';
const FILTER = 'filter';
const CSV_DOWNLOAD = 'csv-download';
const MODULE_ACTIVITY = 'module-activity';

export const ADMIN_TOUR_TARGETS = {
export const TRACK_LEARNER_PROGRESS_TARGETS = {
LEARNER_PROGRESS_SIDEBAR,
LPR_OVERVIEW,
AI_SUMMARY,
FULL_PROGRESS_REPORT,
PROGRESS_REPORT,
FILTER,
CSV_DOWNLOAD,
MODULE_ACTIVITY,
};

const LEARNER_PROGRESS_ADVANCE_EVENT_NAME = 'edx.ui.enterprise.admin-portal.admin-onboarding-tours.learner-progress.advance';
Expand Down
39 changes: 37 additions & 2 deletions src/components/ProductTours/AdminOnboardingTours/messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,50 @@ const messages = defineMessages({
description: 'Body for the welcome modal for existing users.',
},
trackLearnerProgressStepOneTitle: {
id: 'adminPortal.productTours.adminOnboarding.trackLearnerProgress.title',
id: 'adminPortal.productTours.adminOnboarding.trackLearnerProgress.title.1',
defaultMessage: TRACK_LEARNER_PROGRESS_TITLE,
description: 'Title for the learner progress tracking step',
},
trackLearnerProgressStepOneBody: {
id: 'adminPortal.productTours.adminOnboarding.trackLearnerProgress.body',
id: 'adminPortal.productTours.adminOnboarding.trackLearnerProgress.body.1',
defaultMessage: 'Track learner activity and progress across courses with the Learner Progress Report.',
description: 'Description for the learner progress tracking step one',
},
trackLearnerProgressStepTwoBody: {
id: 'adminPortal.productTours.adminOnboarding.trackLearnerProgress.body.2',
defaultMessage: 'Get a high-level view of learner enrollments, course completions, and more.',
description: 'Description for the learner progress tracking step two',
},
trackLearnerProgressStepThreeBody: {
id: 'adminPortal.productTours.adminOnboarding.trackLearnerProgress.body.3',
defaultMessage: 'Get a quick AI-generated overview of your learner analytics with just one click.',
description: 'Description for the learner progress tracking step three',
},
trackLearnerProgressStepFourBody: {
id: 'adminPortal.productTours.adminOnboarding.trackLearnerProgress.body.4',
defaultMessage: 'Scroll down for a detailed, twice-daily updated progress report.',
description: 'Description for the learner progress tracking step four',
},
trackLearnerProgressStepFiveBody: {
id: 'adminPortal.productTours.adminOnboarding.trackLearnerProgress.body.5',
defaultMessage: 'Access the full Learner Progress Report here.',
description: 'Description for the learner progress tracking step five',
},
trackLearnerProgressStepSixBody: {
id: 'adminPortal.productTours.adminOnboarding.trackLearnerProgress.body.6',
defaultMessage: 'Filter results by course, start date, or learner email.',
description: 'Description for the learner progress tracking step six',
},
trackLearnerProgressStepSevenBody: {
id: 'adminPortal.productTours.adminOnboarding.trackLearnerProgress.body.7',
defaultMessage: 'Export the report as a CSV to gain insights and organize data efficiently.',
description: 'Description for the learner progress tracking step seven',
},
trackLearnerProgressStepEightBody: {
id: 'adminPortal.productTours.adminOnboarding.trackLearnerProgress.body.8',
defaultMessage: 'View module-level details for Executive Education courses.',
description: 'Description for the learner progress tracking step eight',
},
});

export default messages;
Loading
Loading