AA-199: Program Completion in CourseExit page (#271)

This commit is contained in:
Carla Duarte
2020-11-16 15:03:45 -05:00
committed by GitHub
parent 74149c2c54
commit a8a8cf5862
20 changed files with 246 additions and 8 deletions

View File

@@ -6,7 +6,7 @@ import { requestCert } from '../data/thunks';
import { useModel } from '../../generic/model-store';
import messages from './messages';
import VerifiedCert from '../../courseware/course/sequence/lock-paywall/assets/edx-verified-mini-cert.png';
import VerifiedCert from '../../generic/assets/edX_verified_certificate.png';
function CertificateBanner({ intl }) {
const {

View File

@@ -13,11 +13,12 @@ import { getAuthenticatedUser } from '@edx/frontend-platform/auth';
import CelebrationMobile from './assets/celebration_456x328.gif';
import CelebrationDesktop from './assets/celebration_750x540.gif';
import certificate from './assets/edx_certificate.png';
import certificateLocked from './assets/edx_certificate_locked.png';
import certificate from '../../../generic/assets/edX_verified_certificate.png';
import certificateLocked from '../../../generic/assets/edX_locked_verified_certificate.png';
import messages from './messages';
import { useModel } from '../../../generic/model-store';
import { requestCert } from '../../../course-home/data/thunks';
import ProgramCompletion from './ProgramCompletion';
import DashboardFootnote from './DashboardFootnote';
import UpgradeFootnote from './UpgradeFootnote';
import SocialIcons from '../../social-share/SocialIcons';
@@ -40,6 +41,7 @@ function CourseCelebration({ intl }) {
certificateData,
end,
linkedinAddToProfileUrl,
relatedPrograms,
verifiedMode,
verifyIdentityUrl,
} = useModel('courses', courseId);
@@ -307,6 +309,15 @@ function CourseCelebration({ intl }) {
)}
</Alert>
)}
{relatedPrograms && relatedPrograms.map(program => (
<ProgramCompletion
key={program.uuid}
progress={program.progress}
title={program.title}
type={program.slug}
url={program.url}
/>
))}
{footnote}
</div>
</div>

View File

@@ -190,6 +190,81 @@ describe('Course Exit Pages', () => {
expect(screen.getByRole('link', { name: 'View my certificate' })).toBeInTheDocument();
expect(screen.getByRole('link', { name: 'Add to LinkedIn profile' })).toBeInTheDocument();
});
describe('Program Completion experience', () => {
beforeEach(() => {
setMetadata({
certificate_data: {
cert_status: 'downloadable',
cert_web_view_url: '/certificates/cooluuidgoeshere',
},
});
});
it('Does not render ProgramCompletion no related programs', async () => {
await fetchAndRender(<CourseCelebration />);
expect(screen.queryByTestId('program-completion')).not.toBeInTheDocument();
});
it('Does not render ProgramCompletion if program is incomplete', async () => {
setMetadata({
related_programs: [{
progress: {
completed: 1,
in_progress: 1,
not_started: 1,
},
slug: 'micromasters',
title: 'Example MicroMasters Program',
uuid: '123456',
url: 'http://localhost:18000/dashboard/programs/123456',
}],
});
await fetchAndRender(<CourseCelebration />);
expect(screen.queryByTestId('program-completion')).not.toBeInTheDocument();
});
it('Renders ProgramCompletion if program is complete', async () => {
setMetadata({
related_programs: [{
progress: {
completed: 3,
in_progress: 0,
not_started: 0,
},
slug: 'micromasters',
title: 'Example MicroMasters Program',
uuid: '123456',
url: 'http://localhost:18000/dashboard/programs/123456',
}],
});
await fetchAndRender(<CourseCelebration />);
expect(screen.queryByTestId('program-completion')).toBeInTheDocument();
expect(screen.queryByTestId('micromasters')).toBeInTheDocument();
});
it('Does not render ProgramCompletion if program is an excluded type', async () => {
setMetadata({
related_programs: [{
progress: {
completed: 3,
in_progress: 0,
not_started: 0,
},
slug: 'excluded-program-type',
title: 'Example Excluded Program',
uuid: '123456',
url: 'http://localhost:18000/dashboard/programs/123456',
}],
});
await fetchAndRender(<CourseCelebration />);
expect(screen.queryByTestId('program-completion')).not.toBeInTheDocument();
expect(screen.queryByTestId('excluded-program-type')).not.toBeInTheDocument();
});
});
});
describe('Course Non-passing Experience', () => {

View File

@@ -0,0 +1,128 @@
import React from 'react';
import PropTypes from 'prop-types';
import { getConfig } from '@edx/frontend-platform';
import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { Alert, Button, Hyperlink } from '@edx/paragon';
import microBachelorsCertImage from '../../../generic/assets/edX_microBachelors_certificate.png';
import microMastersCertImage from '../../../generic/assets/edX_microMasters_certificate.png';
import professionalCertImage from '../../../generic/assets/edX_professionalCertificate_certificate.png';
import xSeriesCertImage from '../../../generic/assets/edX_xSeries_certificate.png';
import messages from './messages';
/**
* Note for Open edX developers:
* There are pieces of this component that are hard-coded and specific to edX that may not apply to your organization.
* This includes mentions of our edX program types (MicroMasters, MicroBachelors, Professional Certificate, and
* XSeries), along with their respective support article URLs and image variable names.
*
* Currently, this component will not render unless the learner's completed course has a related program of one of the
* four aforementioned types. This will not impact the parent components (i.e. CourseCelebration will render normally).
*/
function ProgramCompletion({
intl,
progress,
title,
type,
url,
}) {
if (progress.notStarted !== 0 || progress.inProgress !== 0) {
return null;
}
let certImage;
switch (type) {
case 'microbachelors':
certImage = microBachelorsCertImage;
break;
case 'micromasters':
certImage = microMastersCertImage;
break;
case 'professional-certificate':
certImage = professionalCertImage;
break;
case 'xseries':
certImage = xSeriesCertImage;
break;
default:
return null;
}
const programLink = (
<Hyperlink
style={{ textDecoration: 'underline' }}
destination={url}
className="text-reset"
>
{intl.formatMessage(messages.dashboardLink)}
</Hyperlink>
);
return (
<Alert variant="primary" className="row w-100 mx-0 my-3" data-testid="program-completion">
<div className="col order-1 order-md-0 pl-0 pr-0 pr-md-5">
<div className="h4">{intl.formatMessage(messages.programsLastCourseHeader, { title })}</div>
<p>
<FormattedMessage
id="courseExit.programCompletion.dashboardMessage"
defaultMessage="To view your certificate status, check the Programs section of your {programLink}."
values={{ programLink }}
/>
</p>
{type === 'microbachelors' && (
<>
<p>
<Hyperlink
style={{ textDecoration: 'underline' }}
destination={`${getConfig().SUPPORT_URL}/hc/en-us/articles/360004623154`}
className="text-reset"
>
{intl.formatMessage(messages.microBachelorsLearnMore)}
</Hyperlink>
</p>
<Button variant="primary" className="mb-2 mb-sm-0" href={`${getConfig().CREDENTIALS_BASE_URL}/records`}>
{intl.formatMessage(messages.applyForCredit)}
</Button>
</>
)}
{type === 'micromasters' && (
<p>
{intl.formatMessage(messages.microMastersMessage)}
{' '}
<Hyperlink
style={{ textDecoration: 'underline' }}
destination={`${getConfig().SUPPORT_URL}/hc/en-us/articles/360010346853-Does-a-Micromasters-certificate-count-towards-the-online-Master-s-degree-`}
className="text-reset"
>
{intl.formatMessage(messages.microMastersLearnMore)}
</Hyperlink>
</p>
)}
</div>
<div className="col-12 order-0 col-md-3 order-md-1 w-100 mb-3 p-0 text-center">
<img
src={certImage}
alt={`${intl.formatMessage(messages.certificateImage)}`}
className="w-100"
style={{ maxWidth: '13rem' }}
data-testid={type}
/>
</div>
</Alert>
);
}
ProgramCompletion.propTypes = {
intl: intlShape.isRequired,
progress: PropTypes.shape({
completed: PropTypes.number.isRequired,
inProgress: PropTypes.number.isRequired,
notStarted: PropTypes.number.isRequired,
}).isRequired,
title: PropTypes.string.isRequired,
type: PropTypes.string.isRequired,
url: PropTypes.string.isRequired,
};
export default injectIntl(ProgramCompletion);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

View File

@@ -1,6 +1,11 @@
import { defineMessages } from '@edx/frontend-platform/i18n';
const messages = defineMessages({
applyForCredit: {
id: 'courseExit.programs.applyForCredit',
defaultMessage: 'Apply for credit',
description: 'Button for the learner to apply for course credit',
},
certificateHeaderDownloadable: {
id: 'courseCelebration.certificateHeader.downloadable',
defaultMessage: 'Your certificate is available!',
@@ -42,7 +47,7 @@ const messages = defineMessages({
dashboardLink: {
id: 'courseExit.dashboardLink',
defaultMessage: 'Dashboard',
description: "Link to user's dashboard",
description: 'Link to users dashboard',
},
downloadButton: {
id: 'courseCelebration.downloadButton',
@@ -69,7 +74,19 @@ const messages = defineMessages({
linkedinAddToProfileButton: {
id: 'courseCelebration.linkedinAddToProfileButton',
defaultMessage: 'Add to LinkedIn profile',
description: "Button to add certificate information to the user's LinkedIn profile",
description: 'Button to add certificate information to the users LinkedIn profile',
},
microBachelorsLearnMore: {
id: 'courseExit.programs.microBachelors.learnMore',
defaultMessage: 'Learn more about how your MicroBachelors credential can be applied for credit.',
},
microMastersLearnMore: {
id: 'courseExit.programs.microMasters.learnMore',
defaultMessage: 'Learn more about the process of applying MicroMasters certificates to Masters degrees.',
},
microMastersMessage: {
id: 'courseExit.programs.microMasters.mastersMessage',
defaultMessage: 'If youre interested in using your MicroMasters certificate towards a Masters program, you can get started today!',
},
nextButtonComplete: {
id: 'learn.sequence.navigation.complete.button', // for historical reasons
@@ -82,7 +99,11 @@ const messages = defineMessages({
profileLink: {
id: 'courseExit.profileLink',
defaultMessage: 'Profile',
description: "Link to user's profile",
description: 'Link to users profile',
},
programsLastCourseHeader: {
id: 'courseExit.programs.lastCourse',
defaultMessage: 'You have completed the last course in {title}!',
},
requestCertificateBodyText: {
id: 'courseCelebration.requestCertificateBodyText',

View File

@@ -4,7 +4,7 @@ import { getConfig } from '@edx/frontend-platform';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
import LearnerQuote1 from './assets/learner-quote.png';
import LearnerQuote2 from './assets/learner-quote2.png';
import VerifiedCert from './assets/verified-cert.png';
import VerifiedCert from '../../../generic/assets/edX_verified_certificate.png';
export default class CourseSock extends Component {
constructor(props) {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

View File

@@ -5,7 +5,7 @@ import { faLock } from '@fortawesome/free-solid-svg-icons';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import messages from './messages';
import VerifiedCert from './assets/edx-verified-mini-cert.png';
import VerifiedCert from '../../../../generic/assets/edX_verified_certificate.png';
import { useModel } from '../../../../generic/model-store';
function LockPaywall({

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.9 KiB

View File

@@ -57,6 +57,7 @@ Factory.define('courseMetadata')
certificate_data: null,
verify_identity_url: null,
linkedin_add_to_profile_url: null,
related_programs: null,
}).attr(
'tabs', ['tabs', 'id'], (passedTabs, id) => {
if (passedTabs) {

View File

@@ -140,6 +140,7 @@ function normalizeMetadata(metadata) {
certificateData: camelCaseObject(metadata.certificate_data),
verifyIdentityUrl: metadata.verify_identity_url,
linkedinAddToProfileUrl: metadata.linkedin_add_to_profile_url,
relatedPrograms: camelCaseObject(metadata.related_programs),
};
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

View File

@@ -80,6 +80,7 @@ initialize({
handlers: {
config: () => {
mergeConfig({
CREDENTIALS_BASE_URL: process.env.CREDENTIALS_BASE_URL || null,
INSIGHTS_BASE_URL: process.env.INSIGHTS_BASE_URL || null,
SOCIAL_UTM_MILESTONE_CAMPAIGN: process.env.SOCIAL_UTM_MILESTONE_CAMPAIGN || null,
STUDIO_BASE_URL: process.env.STUDIO_BASE_URL || null,