feat: Added the Request Certificate Alert

[MICROBA-678]

When a certificate is in a unexpected state (i.e. notpassing with a
passing grade) this alert will allow the user to attempt to resolve the
issue on their own. It will run the code that checks the certificates
status. It requires that the course is configured to allow users to
Request Certificates though.
This commit is contained in:
Albert (AJ) St. Aubin
2021-06-23 10:52:54 -04:00
parent e423dddb03
commit 86a4cf9af7
3 changed files with 65 additions and 21 deletions

View File

@@ -763,6 +763,33 @@ describe('Outline Tab', () => {
});
});
describe('Requesting Certificate Alert', () => {
it('appears', async () => {
const now = new Date();
const yesterday = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 1);
setMetadata({ is_enrolled: true });
setTabData({
cert_data: {
cert_status: CERT_STATUS_TYPE.REQUESTING,
cert_web_view_url: null,
certificate_available_date: null,
download_url: null,
},
}, {
date_blocks: [
{
date_type: 'course-end-date',
date: yesterday.toISOString(),
title: 'End',
},
],
});
await fetchAndRender();
expect(screen.queryByText('Congratulations! Your certificate is ready.')).toBeInTheDocument();
expect(screen.queryByText('Request certificate')).toBeInTheDocument();
});
});
describe('Certificate (pdf) Complete Alert', () => {
it('appears', async () => {
const now = new Date();

View File

@@ -7,23 +7,29 @@ import {
intlShape,
} from '@edx/frontend-platform/i18n';
import { Alert, Button } from '@edx/paragon';
import { useDispatch } from 'react-redux';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faCheckCircle, faExclamationTriangle } from '@fortawesome/free-solid-svg-icons';
import { getConfig } from '@edx/frontend-platform';
import certMessages from './messages';
import certStatusMessages from '../../../progress-tab/certificate-status/messages';
import { requestCert } from '../../../data/thunks';
export const CERT_STATUS_TYPE = {
EARNED_NOT_AVAILABLE: 'earned_but_not_available',
DOWNLOADABLE: 'downloadable',
REQUESTING: 'requesting',
UNVERIFIED: 'unverified',
};
function CertificateStatusAlert({ intl, payload }) {
const dispatch = useDispatch();
const {
certificateAvailableDate,
certStatusType,
certStatus,
courseEndDate,
courseId,
certURL,
isWebCert,
userTimezone,
@@ -38,7 +44,7 @@ function CertificateStatusAlert({ intl, payload }) {
icon: faCheckCircle,
iconClassName: 'alert-icon text-success-500',
};
if (certStatusType === CERT_STATUS_TYPE.EARNED_NOT_AVAILABLE) {
if (certStatus === CERT_STATUS_TYPE.EARNED_NOT_AVAILABLE) {
const timezoneFormatArgs = userTimezone ? { timeZone: userTimezone } : {};
const certificateAvailableDateFormatted = <FormattedDate value={certificateAvailableDate} day="numeric" month="long" year="numeric" />;
const courseEndDateFormatted = <FormattedDate value={courseEndDate} day="numeric" month="long" year="numeric" />;
@@ -57,7 +63,7 @@ function CertificateStatusAlert({ intl, payload }) {
/>
</p>
);
} else if (certStatusType === CERT_STATUS_TYPE.DOWNLOADABLE) {
} else if (certStatus === CERT_STATUS_TYPE.DOWNLOADABLE) {
alertProps.header = intl.formatMessage(certMessages.certStatusDownloadableHeader);
if (isWebCert) {
alertProps.buttonMessage = intl.formatMessage(certStatusMessages.viewableButton);
@@ -66,6 +72,12 @@ function CertificateStatusAlert({ intl, payload }) {
}
alertProps.buttonVisible = true;
alertProps.buttonLink = certURL;
} else if (certStatus === CERT_STATUS_TYPE.REQUESTING) {
alertProps.header = intl.formatMessage(certMessages.certStatusDownloadableHeader);
alertProps.buttonMessage = intl.formatMessage(certStatusMessages.requestableButton);
alertProps.buttonVisible = true;
alertProps.buttonLink = '';
alertProps.buttonAction = () => { dispatch(requestCert(courseId)); };
}
return alertProps;
};
@@ -86,9 +98,10 @@ function CertificateStatusAlert({ intl, payload }) {
};
let alertProps = {};
switch (certStatusType) {
switch (certStatus) {
case CERT_STATUS_TYPE.EARNED_NOT_AVAILABLE:
case CERT_STATUS_TYPE.DOWNLOADABLE:
case CERT_STATUS_TYPE.REQUESTING:
alertProps = renderCertAwardedStatus();
break;
case CERT_STATUS_TYPE.UNVERIFIED:
@@ -106,8 +119,9 @@ function CertificateStatusAlert({ intl, payload }) {
iconClassName,
icon,
header,
buttonLink,
body,
buttonAction,
buttonLink,
buttonMessage,
}) => (
<Alert variant={variant}>
@@ -122,6 +136,9 @@ function CertificateStatusAlert({ intl, payload }) {
<Button
variant="primary"
href={buttonLink}
onClick={() => {
if (buttonAction) { buttonAction(); }
}}
>
{buttonMessage}
</Button>
@@ -139,8 +156,9 @@ CertificateStatusAlert.propTypes = {
intl: intlShape.isRequired,
payload: PropTypes.shape({
certificateAvailableDate: PropTypes.string,
certStatusType: PropTypes.string,
certStatus: PropTypes.string,
courseEndDate: PropTypes.string,
courseId: PropTypes.string,
certURL: PropTypes.string,
isWebCert: PropTypes.bool,
userTimezone: PropTypes.string,

View File

@@ -3,29 +3,28 @@ import React, { useMemo } from 'react';
import { getConfig } from '@edx/frontend-platform';
import { useAlert } from '../../../../generic/user-messages';
import { useModel } from '../../../../generic/model-store';
import { CERT_STATUS_TYPE } from './CertificateStatusAlert';
const CertificateStatusAlert = React.lazy(() => import('./CertificateStatusAlert'));
function verifyCertStatusType(status) {
// This method will only return cert statuses when we want to alert on them.
// It should be modified when we want to alert on a new status type.
if (status === CERT_STATUS_TYPE.DOWNLOADABLE) {
return CERT_STATUS_TYPE.DOWNLOADABLE;
switch (status) {
case CERT_STATUS_TYPE.DOWNLOADABLE:
case CERT_STATUS_TYPE.EARNED_NOT_AVAILABLE:
case CERT_STATUS_TYPE.REQUESTING:
case CERT_STATUS_TYPE.UNVERIFIED:
return true;
default:
return false;
}
if (status === CERT_STATUS_TYPE.EARNED_NOT_AVAILABLE) {
return CERT_STATUS_TYPE.EARNED_NOT_AVAILABLE;
}
if (status === CERT_STATUS_TYPE.UNVERIFIED) {
return CERT_STATUS_TYPE.UNVERIFIED;
}
return '';
}
function useCertificateStatusAlert(courseId) {
const {
isEnrolled,
} = useModel('courseHomeMeta', courseId);
const {
datesWidget: {
courseDateBlocks,
@@ -41,7 +40,6 @@ function useCertificateStatusAlert(courseId) {
downloadUrl,
} = certData || {};
const endBlock = courseDateBlocks.find(b => b.dateType === 'course-end-date');
const certStatusType = verifyCertStatusType(certStatus);
const isWebCert = downloadUrl === null;
let certURL = '';
@@ -51,14 +49,15 @@ function useCertificateStatusAlert(courseId) {
// PDF Certificate
certURL = downloadUrl;
}
const hasCertStatus = certStatusType !== '';
const hasAlertingCertStatus = verifyCertStatusType(certStatus);
// Only show if there is a known cert status that we want provide status on.
const isVisible = isEnrolled && hasCertStatus;
const isVisible = isEnrolled && hasAlertingCertStatus;
const payload = {
certificateAvailableDate,
certURL,
certStatusType,
certStatus,
courseId,
courseEndDate: endBlock && endBlock.date,
userTimezone,
isWebCert,