Merge pull request #20265 from edx/diana/remove-diff-paid
Remove Diff Paid experimental features.
This commit is contained in:
@@ -1,71 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Modal, Button } from '@edx/paragon/static';
|
||||
|
||||
import ExperimentalCarousel from './ExperimentalCarousel.jsx';
|
||||
|
||||
// https://openedx.atlassian.net/browse/LEARNER-3926
|
||||
|
||||
export class PortfolioExperimentUpsellModal extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = { isOpen: true };
|
||||
}
|
||||
|
||||
render() {
|
||||
const slides = [
|
||||
(<div className='portfolio-slide-0'>
|
||||
<p className='description'>Upgrade to access new content: a guide for building an online portfolio and creating your first project.</p>
|
||||
<div className='checkmark-group-header'>By following the guide you will:</div>
|
||||
<ul className='upsell-modal-checkmark-group'>
|
||||
<li><span className='fa fa-check upsell-modal-checkmark' aria-hidden='true' />Use your new coding skills</li>
|
||||
<li><span className='fa fa-check upsell-modal-checkmark' aria-hidden='true' />Begin to build your portfolio</li>
|
||||
<li><span className='fa fa-check upsell-modal-checkmark' aria-hidden='true' />Share what you can do!</li>
|
||||
</ul>
|
||||
</div>),
|
||||
(<div className='portfolio-slide-1'>
|
||||
<h3 className='slide-header'><b>Use Your New Coding Skills</b></h3>
|
||||
<p>Want to practice what you've learned? We'll give you the project idea to create your own portfolio. Get creative!</p>
|
||||
</div>),
|
||||
(<div className='portfolio-slide-2'>
|
||||
<h3 className='slide-header'><b>Build Your Portfolio</b></h3>
|
||||
<p>Apply your knowledge and show them you can code - this project is the perfect start to your portfolio.</p>
|
||||
</div>),
|
||||
(<div className='portfolio-slide-3'>
|
||||
<h3 className='slide-header'><b>Share What You Can Do</b></h3>
|
||||
<p>Get tips on where to store your project and the best way to share it with employers.</p>
|
||||
</div>),
|
||||
];
|
||||
|
||||
const body = (
|
||||
<div>
|
||||
<ExperimentalCarousel id='portfolio-upsell-modal' slides={slides} />
|
||||
<img
|
||||
className="upsell-certificate"
|
||||
src="https://courses.edx.org/static/images/edx-verified-mini-cert.png"
|
||||
alt="Sample verified certificate"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
open={this.state.isOpen}
|
||||
className='portfolio-upsell-modal'
|
||||
title={'Portfolio Builder: My First Project'}
|
||||
onClose={() => {}}
|
||||
body={body}
|
||||
buttons={[
|
||||
(<Button
|
||||
label={'Upgrade ($100 USD)'}
|
||||
display={'Upgrade ($100 USD)'}
|
||||
buttonType='success'
|
||||
// unfortunately, Button components don't have an href attribute
|
||||
onClick={() => {}}
|
||||
/>),
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Modal, Button } from '@edx/paragon/static';
|
||||
|
||||
import ExperimentalCarousel from './ExperimentalCarousel.jsx';
|
||||
|
||||
// https://openedx.atlassian.net/browse/LEARNER-3583
|
||||
|
||||
export class UpsellExperimentModal extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
isOpen: true,
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const slides = [
|
||||
(<div>
|
||||
<div className="my-stats-introduction">My Stats introduces new personalized views that help you track your progress towards completing your course!</div>
|
||||
<div className="my-stats-slide-header">With My Stats you will see your:</div>
|
||||
<ul className="upsell-modal-checkmark-group">
|
||||
<li><span className="fa fa-check upsell-modal-checkmark" aria-hidden="true" />Course Activity Streak (log in every week to keep your streak alive)</li>
|
||||
<li><span className="fa fa-check upsell-modal-checkmark" aria-hidden="true" />Grade Progress (see how you're tracking towards a passing grade)</li>
|
||||
<li><span className="fa fa-check upsell-modal-checkmark" aria-hidden="true" />Discussion Forum Engagements (top learners use the forums - how do you measure up?)</li>
|
||||
</ul>
|
||||
</div>),
|
||||
(<div>
|
||||
<div className="slide-header"><b>Course Activity Streak</b></div>
|
||||
<span className="course-activity-streak-information">Did you know the learners most likely to complete a course log in every week? Let us help you track your weekly streak - log in every week and learn something new! You can also see how many of the other learners in your course logged in this week.</span>
|
||||
<img
|
||||
className="feature-screenshot"
|
||||
src="https://prod-edx-mktg-edit.edx.org/sites/default/files/week_streak.png"
|
||||
alt=""
|
||||
/>
|
||||
</div>),
|
||||
(<div>
|
||||
<div className="slide-header"><b>Grade Progress</b></div>
|
||||
<span className="grade-progress-information">Wonder how you're doing in the course so far? We can not only show you all your grades, and how much each assignment is worth, but also upcoming graded assignments. This is a great way to track what you might need to work on for a final exam.</span>
|
||||
<img
|
||||
className="feature-screenshot"
|
||||
src="https://prod-edx-mktg-edit.edx.org/sites/default/files/grading.png"
|
||||
alt=""
|
||||
/>
|
||||
</div>),
|
||||
(<div>
|
||||
<div className="slide-header"><b>Discussion engagements</b></div>
|
||||
<span className="discussion-engagements-information">A large percentage of successful learners are engaged on the discussion forums. Compare your forum stats to previous graduates!</span>
|
||||
<img
|
||||
className="feature-screenshot"
|
||||
src="https://prod-edx-mktg-edit.edx.org/sites/default/files/discussions.png"
|
||||
alt=""
|
||||
/>
|
||||
</div>),
|
||||
];
|
||||
const body = (
|
||||
<div>
|
||||
<ExperimentalCarousel id="upsell-modal" slides={slides} />
|
||||
<img
|
||||
className="upsell-certificate"
|
||||
src="https://courses.edx.org/static/images/edx-verified-mini-cert.png"
|
||||
alt=""
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
const { buttonDestinationURL } = this.props;
|
||||
return (
|
||||
<Modal
|
||||
open={this.state.isOpen}
|
||||
className="upsell-modal"
|
||||
title={"My Stats"}
|
||||
onClose={() => {}}
|
||||
body={body}
|
||||
buttons={[
|
||||
(<Button
|
||||
label={"Upgrade ($100 USD)"}
|
||||
display={"Upgrade ($100 USD)"}
|
||||
buttonType="success"
|
||||
// unfortunately, Button components don't have an href component
|
||||
onClick={() => window.location = buttonDestinationURL}
|
||||
/>),
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
UpsellExperimentModal.propTypes = {
|
||||
buttonDestinationURL: PropTypes.string.isRequired,
|
||||
};
|
||||
@@ -2275,8 +2275,6 @@ INSTALLED_APPS = [
|
||||
'openedx.features.course_search',
|
||||
'openedx.features.enterprise_support.apps.EnterpriseSupportConfig',
|
||||
'openedx.features.learner_profile',
|
||||
'openedx.features.learner_analytics',
|
||||
'openedx.features.portfolio_project',
|
||||
'openedx.features.course_duration_limits',
|
||||
'openedx.features.content_type_gating',
|
||||
|
||||
|
||||
@@ -32,7 +32,6 @@
|
||||
@import 'features/course-search';
|
||||
@import 'features/course-sock';
|
||||
@import 'features/course-upgrade-message';
|
||||
@import 'features/learner-analytics-dashboard';
|
||||
@import 'features/journals';
|
||||
@import 'features/content-type-gating';
|
||||
|
||||
|
||||
@@ -1,450 +0,0 @@
|
||||
$mint-green: #92c9d3;
|
||||
$slices: #386f77, #1abc9c, $mint-green, #397d1c, #a39d3d, #0a5aaa;
|
||||
$table-border: rgba(155, 155, 155, 0.2);
|
||||
$font-color: #636c72;
|
||||
$heading-color: #292b2c;
|
||||
$grouping-background: #f5f5f5;
|
||||
$grouping-border: #d8d8d8;
|
||||
$trophy-gold: #f39c12;
|
||||
|
||||
.content-wrapper {
|
||||
.learner-analytics-header {
|
||||
border-bottom: none;
|
||||
|
||||
.title {
|
||||
margin-bottom: 0;
|
||||
font-size: 1.75em;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.lock-icon {
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.page-content.learner-analytics-dashboard-wrapper {
|
||||
flex-direction: column;
|
||||
background: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.learner-analytics-dashboard {
|
||||
.sidebar {
|
||||
position: relative;
|
||||
|
||||
&.week-streak {
|
||||
&::before {
|
||||
content: '';
|
||||
width: 100%;
|
||||
height: 100px;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
background: {
|
||||
image: url('#{$static-path}/images/learner_analytics_dashboard/streak-768x517.jpg');
|
||||
position: center;
|
||||
repeat: no-repeat;
|
||||
size: cover;
|
||||
}
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
width: 151px;
|
||||
height: 192px;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
background: {
|
||||
image: url('#{$static-path}/images/learner_analytics_dashboard/streak-trophy.png');
|
||||
repeat: no-repeat;
|
||||
position: bottom right;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.analytics-group {
|
||||
background: $grouping-background;
|
||||
border: 1px solid $grouping-border;
|
||||
padding: 20px 20px 120px;
|
||||
margin: 20px 0 40px;
|
||||
|
||||
&:last-of-type {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.group-heading {
|
||||
color: $heading-color;
|
||||
font-size: 1.5em;
|
||||
}
|
||||
|
||||
.section-heading {
|
||||
color: $heading-color;
|
||||
font: {
|
||||
size: 1em;
|
||||
weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.grading-weight-wrapper {
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
border-bottom: 1px solid $table-border;
|
||||
margin-bottom: 25px;
|
||||
|
||||
.chart-wrapper {
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
margin-bottom: 20px;
|
||||
|
||||
.slice-1 {
|
||||
fill: nth($slices, 1);
|
||||
}
|
||||
|
||||
.slice-2 {
|
||||
fill: nth($slices, 2);
|
||||
}
|
||||
|
||||
.slice-3 {
|
||||
fill: nth($slices, 3);
|
||||
}
|
||||
|
||||
.slice-4 {
|
||||
fill: nth($slices, 4);
|
||||
}
|
||||
|
||||
.slice-5 {
|
||||
fill: nth($slices, 5);
|
||||
}
|
||||
|
||||
.slice-6 {
|
||||
fill: nth($slices, 6);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.legend {
|
||||
.legend-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0 0 20px 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.legend-item {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
font-size: 0.875em;
|
||||
|
||||
.color-swatch {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 3px;
|
||||
margin-right: 10px;
|
||||
|
||||
&.swatch-1 {
|
||||
background-color: nth($slices, 1);
|
||||
}
|
||||
|
||||
&.swatch-2 {
|
||||
background-color: nth($slices, 2);
|
||||
}
|
||||
|
||||
&.swatch-3 {
|
||||
background-color: nth($slices, 3);
|
||||
}
|
||||
|
||||
&.swatch-4 {
|
||||
background-color: nth($slices, 4);
|
||||
}
|
||||
|
||||
&.swatch-5 {
|
||||
background-color: nth($slices, 5);
|
||||
}
|
||||
|
||||
&.swatch-6 {
|
||||
background-color: nth($slices, 6);
|
||||
}
|
||||
}
|
||||
|
||||
.label,
|
||||
.percentage {
|
||||
color: $font-color;
|
||||
}
|
||||
|
||||
.label {
|
||||
margin-right: 30px;
|
||||
}
|
||||
|
||||
.percentage {
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.graded-assessments-wrapper {
|
||||
.grade-table {
|
||||
width: 100%;
|
||||
margin-bottom: 30px;
|
||||
font-size: 0.875em;
|
||||
|
||||
th {
|
||||
font-weight: bold;
|
||||
|
||||
&:nth-of-type(3) {
|
||||
width: 80px;
|
||||
}
|
||||
}
|
||||
|
||||
th,
|
||||
td {
|
||||
border: none;
|
||||
color: $font-color;
|
||||
|
||||
&:nth-of-type(2),
|
||||
&:nth-of-type(3) {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
&:nth-of-type(3) {
|
||||
background: rgba(255, 193, 7, 0.15);
|
||||
color: #000;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.totals {
|
||||
font-size: 18px;
|
||||
border-color: $table-border;
|
||||
|
||||
.footer-label {
|
||||
color: black;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.footnote {
|
||||
font-size: 0.875em;
|
||||
color: $font-color;
|
||||
}
|
||||
|
||||
.table-head {
|
||||
border-bottom: 2px solid $table-border;
|
||||
}
|
||||
|
||||
.type-group {
|
||||
border-bottom: 1px solid $table-border;
|
||||
|
||||
&:last-of-type {
|
||||
border-bottom-width: 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.due-dates {
|
||||
border-bottom: 1px solid $table-border;
|
||||
padding-bottom: 30px;
|
||||
margin-bottom: 30px;
|
||||
|
||||
.date-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
||||
.date-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
color: $font-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.week-streak-wrapper {
|
||||
color: $font-color;
|
||||
|
||||
.streak-icon-wrapper {
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.fa-trophy {
|
||||
color: $trophy-gold;
|
||||
font-size: 21px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.section-heading {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.streak-encouragement {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.streak-criteria {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.active-users-wrapper {
|
||||
.fa-user {
|
||||
border: 2px solid $mint-green;
|
||||
border-radius: 50%;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
height: 2px;
|
||||
width: 12px;
|
||||
position: absolute;
|
||||
background-color: $mint-green;
|
||||
left: 6px;
|
||||
bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.label {
|
||||
color: $font-color;
|
||||
}
|
||||
}
|
||||
|
||||
.discussions-wrapper {
|
||||
display: flex;
|
||||
flex-flow: column wrap;
|
||||
|
||||
.group-heading {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.post-counts {
|
||||
border-top: 1px solid $table-border;
|
||||
}
|
||||
}
|
||||
|
||||
.count-icon {
|
||||
color: $mint-green;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.user-count {
|
||||
font-size: 1.5em;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.count-chart {
|
||||
position: relative;
|
||||
min-height: 50px;
|
||||
margin-bottom: 20px;
|
||||
padding-left: 70px;
|
||||
|
||||
.chart-bar {
|
||||
height: 10px;
|
||||
border-radius: 2px;
|
||||
background: $mint-green;
|
||||
}
|
||||
|
||||
.chart-icon {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
background: $mint-green;
|
||||
border-radius: 50%;
|
||||
text-align: center;
|
||||
font-size: 24px;
|
||||
padding-top: 13px;
|
||||
color: white;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
&:first-of-type {
|
||||
.chart-bar,
|
||||
.chart-icon {
|
||||
background: $trophy-gold;
|
||||
}
|
||||
}
|
||||
|
||||
.chart-display {
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
align-items: baseline;
|
||||
|
||||
.user-count {
|
||||
margin-top: -10px;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include media-breakpoint-up(md) {
|
||||
.sidebar {
|
||||
&.week-streak {
|
||||
&::before {
|
||||
background-image: url('#{$static-path}/images/learner_analytics_dashboard/streak-1140x768.jpg');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.analytics-group {
|
||||
&:first-of-type {
|
||||
background: {
|
||||
image: url('#{$static-path}/images/learner_analytics_dashboard/analytics-grading.png');
|
||||
repeat: no-repeat;
|
||||
position: top right;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include media-breakpoint-up(lg) {
|
||||
.discussions-wrapper {
|
||||
flex-direction: row;
|
||||
|
||||
.comparison-charts {
|
||||
border-right: 1px solid $table-border;
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.post-counts {
|
||||
border: none;
|
||||
width: 50%;
|
||||
padding-left: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include media-breakpoint-up(xl) {
|
||||
.sidebar {
|
||||
&.week-streak {
|
||||
&::before {
|
||||
background-image: url('#{$static-path}/images/learner_analytics_dashboard/streak-768x517.jpg');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.learner-analytics-wrapper {
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
|
||||
.main-block {
|
||||
width: calc(100% - 390px);
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
width: 360px;
|
||||
margin-left: 30px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
16
lms/urls.py
16
lms/urls.py
@@ -643,22 +643,6 @@ urlpatterns += [
|
||||
r'^u/',
|
||||
include('openedx.features.learner_profile.urls'),
|
||||
),
|
||||
|
||||
# Learner analytics dashboard
|
||||
url(
|
||||
r'^courses/{}/learner_analytics/'.format(
|
||||
settings.COURSE_ID_PATTERN,
|
||||
),
|
||||
include('openedx.features.learner_analytics.urls'),
|
||||
),
|
||||
|
||||
# Portfolio project experiment
|
||||
url(
|
||||
r'^courses/{}/xfeature/portfolio/'.format(
|
||||
settings.COURSE_ID_PATTERN,
|
||||
),
|
||||
include('openedx.features.portfolio_project.urls'),
|
||||
),
|
||||
]
|
||||
|
||||
if settings.FEATURES.get('ENABLE_TEAMS'):
|
||||
|
||||
@@ -15,8 +15,6 @@ from lms.djangoapps.discussion.django_comment_client.permissions import has_perm
|
||||
from openedx.core.djangolib.js_utils import dump_js_escaped_json, js_escaped_string
|
||||
from openedx.core.djangolib.markup import HTML
|
||||
from openedx.features.course_experience import UNIFIED_COURSE_TAB_FLAG, SHOW_REVIEWS_TOOL_FLAG
|
||||
from openedx.features.learner_analytics import ENABLE_DASHBOARD_TAB
|
||||
from openedx.features.portfolio_project import INCLUDE_PORTFOLIO_UPSELL_MODAL
|
||||
%>
|
||||
|
||||
<%block name="header_extras">
|
||||
@@ -25,23 +23,6 @@ from openedx.features.portfolio_project import INCLUDE_PORTFOLIO_UPSELL_MODAL
|
||||
|
||||
<%block name="content">
|
||||
<div class="course-view page-content-container" id="course-container">
|
||||
|
||||
% if ENABLE_DASHBOARD_TAB.is_enabled(course_key):
|
||||
${static.renderReact(
|
||||
component="UpsellExperimentModal",
|
||||
id="upsell-modal",
|
||||
props={},
|
||||
)}
|
||||
% endif
|
||||
|
||||
% if INCLUDE_PORTFOLIO_UPSELL_MODAL.is_enabled():
|
||||
${static.renderReact(
|
||||
component="PortfolioExperimentUpsellModal",
|
||||
id="portfolio-experiment-upsell-modal",
|
||||
props={}
|
||||
)}
|
||||
% endif
|
||||
|
||||
<header class="page-header has-secondary">
|
||||
<div class="page-header-main">
|
||||
<nav aria-label="${_('Course Outline')}" class="sr-is-focusable" tabindex="-1">
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
Learner Analytics
|
||||
-----------------
|
||||
|
||||
This is the current home of the Learner Analytics feature. This is not a fully
|
||||
supported feature.
|
||||
|
||||
See LEARNER-3854 for details.
|
||||
|
||||
TODO: LEARNER-3854: If this feature gets implemented, this directory should
|
||||
move to lms/djangoapps and out of openedx/features.
|
||||
@@ -1,19 +0,0 @@
|
||||
"""
|
||||
Learner analytics helpers and settings
|
||||
"""
|
||||
from openedx.core.djangoapps.waffle_utils import (
|
||||
CourseWaffleFlag, WaffleFlag, WaffleFlagNamespace
|
||||
)
|
||||
|
||||
# Namespace for learner analytics waffle flags.
|
||||
WAFFLE_FLAG_NAMESPACE = WaffleFlagNamespace(name='learner_analytics')
|
||||
|
||||
# Simple safety valve in case the modal breaks DOM.
|
||||
# Used in the Learner Analytics and Enhanced Support tests
|
||||
# Please do not turn off this flag until all tests have been resolved
|
||||
# https://openedx.atlassian.net/browse/LEARNER-3377
|
||||
# https://openedx.atlassian.net/browse/LEARNER-3514
|
||||
INCLUDE_UPSELL_MODAL = WaffleFlag(WAFFLE_FLAG_NAMESPACE, 'include_upsell_modal')
|
||||
|
||||
# Enables the learner analytics page for different courses via waffle course overrides.
|
||||
ENABLE_DASHBOARD_TAB = CourseWaffleFlag(WAFFLE_FLAG_NAMESPACE, 'enable_dashboard_tab')
|
||||
@@ -1,67 +0,0 @@
|
||||
## mako
|
||||
<%page expression_filter="h"/>
|
||||
|
||||
<%! main_css = "style-main-v2" %>
|
||||
|
||||
<%inherit file="../main.html" />
|
||||
<%namespace name='static' file='../static_content.html'/>
|
||||
<%def name="online_help_token()"><% return "courseware" %></%def>
|
||||
<%def name="course_name()">
|
||||
<% return _("{course_number} Courseware").format(course_number=course.display_number_with_default) %>
|
||||
</%def>
|
||||
|
||||
<%!
|
||||
import json
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from openedx.core.djangolib.markup import HTML
|
||||
from openedx.features.course_experience import course_home_page_title
|
||||
%>
|
||||
|
||||
<%block name="bodyclass">course</%block>
|
||||
|
||||
<%block name="pagetitle">${course_name()}</%block>
|
||||
|
||||
<%include file="../courseware/course_navigation.html" args="active_page='learner_analytics'" />
|
||||
|
||||
<%block name="content">
|
||||
<div class="course-view page-content-container" id="course-container">
|
||||
<header class="page-header has-secondary learner-analytics-header">
|
||||
## TODO: LEARNER-3854: Clean-up after Learner Analytics test.
|
||||
## May not need/want breadcrumbs? Can maybe kill course_url and course_home_page_title
|
||||
## from the context?
|
||||
## Breadcrumb navigation
|
||||
<div class="page-header-main">
|
||||
<h2 class="title"><span class="fa ${'fa-unlock-alt' if has_access else 'fa-lock'} lock-icon" aria-hidden="true"></span> ${_('My Stats (Beta)')}</h2>
|
||||
</div>
|
||||
</header>
|
||||
<div class="page-content learner-analytics-dashboard-wrapper">
|
||||
<div class="learner-analytics-dashboard">
|
||||
% if has_access:
|
||||
${static.renderReact(
|
||||
component="LearnerAnalyticsDashboard",
|
||||
id="react-learner-analytics-dashboard",
|
||||
props={
|
||||
'schedule': assignment_schedule,
|
||||
'schedule_raw': assignment_schedule_raw,
|
||||
'grading_policy': grading_policy,
|
||||
'grades': assignment_grades,
|
||||
'discussion_info': discussion_info,
|
||||
'weekly_active_users': weekly_active_users,
|
||||
'week_streak': week_streak,
|
||||
'profile_images': profile_image_urls,
|
||||
'passing_grade': passing_grade,
|
||||
'percent_grade': percent_grade,
|
||||
}
|
||||
)}
|
||||
% else:
|
||||
## TODO: LEARNER-3854: Clean-up after Learner Analytics test.
|
||||
## If we move forward with this, the upsell information should
|
||||
## be added here.
|
||||
Page is not available.
|
||||
% endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</%block>
|
||||
<%namespace name='static' file='../static_content.html'/>
|
||||
@@ -1 +0,0 @@
|
||||
# TODO: LEARNER-3854: Implement tests or remove file after Learner Analytics test.
|
||||
@@ -1,15 +0,0 @@
|
||||
"""
|
||||
Url setup for learner analytics
|
||||
"""
|
||||
from django.conf.urls import url
|
||||
|
||||
from views import LearnerAnalyticsView
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
url(
|
||||
r'^$',
|
||||
LearnerAnalyticsView.as_view(),
|
||||
name='openedx.learner_analytics.dashboard',
|
||||
),
|
||||
]
|
||||
@@ -1,347 +0,0 @@
|
||||
"""
|
||||
Learner analytics dashboard views
|
||||
"""
|
||||
import logging
|
||||
import math
|
||||
import urllib
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import pytz
|
||||
import requests
|
||||
from analyticsclient.client import Client
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.core.cache import cache
|
||||
from django.urls import reverse
|
||||
from django.http import Http404
|
||||
from django.shortcuts import render_to_response
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.views.decorators.cache import cache_control
|
||||
from django.views.generic import View
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
from student.models import CourseEnrollment
|
||||
from util.views import ensure_valid_course_key
|
||||
from xmodule.modulestore.django import modulestore
|
||||
|
||||
from course_modes.models import get_cosmetic_verified_display_price
|
||||
from lms.djangoapps.course_api.blocks.api import get_blocks
|
||||
from lms.djangoapps.commerce.utils import EcommerceService
|
||||
from lms.djangoapps.courseware.courses import get_course_with_access
|
||||
from lms.djangoapps.discussion.views import create_user_profile_context
|
||||
from lms.djangoapps.grades.course_grade_factory import CourseGradeFactory
|
||||
from openedx.features.course_experience import default_course_url_name
|
||||
from openedx.core.djangoapps.user_api.accounts.image_helpers import get_profile_image_urls_for_user
|
||||
|
||||
from . import ENABLE_DASHBOARD_TAB
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class LearnerAnalyticsView(View):
|
||||
"""
|
||||
Displays the Learner Analytics Dashboard.
|
||||
"""
|
||||
def __init__(self):
|
||||
View.__init__(self)
|
||||
self.analytics_client = Client(base_url=settings.ANALYTICS_API_URL, auth_token=settings.ANALYTICS_API_KEY)
|
||||
|
||||
@method_decorator(login_required)
|
||||
@method_decorator(cache_control(no_cache=True, no_store=True, must_revalidate=True))
|
||||
@method_decorator(ensure_valid_course_key)
|
||||
def get(self, request, course_id):
|
||||
"""
|
||||
Displays the user's Learner Analytics for the specified course.
|
||||
|
||||
Arguments:
|
||||
request: HTTP request
|
||||
course_id (unicode): course id
|
||||
"""
|
||||
course_key = CourseKey.from_string(course_id)
|
||||
if not ENABLE_DASHBOARD_TAB.is_enabled(course_key):
|
||||
raise Http404
|
||||
|
||||
course = get_course_with_access(request.user, 'load', course_key, check_if_enrolled=True)
|
||||
course_url_name = default_course_url_name(course.id)
|
||||
course_url = reverse(course_url_name, kwargs={'course_id': unicode(course.id)})
|
||||
|
||||
is_verified = CourseEnrollment.is_enrolled_as_verified(request.user, course_key)
|
||||
has_access = is_verified or request.user.is_staff
|
||||
|
||||
enrollment = CourseEnrollment.get_enrollment(request.user, course_key)
|
||||
|
||||
upgrade_price = None
|
||||
upgrade_url = None
|
||||
|
||||
if enrollment and enrollment.upgrade_deadline:
|
||||
upgrade_url = EcommerceService().upgrade_url(request.user, course_key)
|
||||
upgrade_price = get_cosmetic_verified_display_price(course)
|
||||
|
||||
context = {
|
||||
'upgrade_price': upgrade_price,
|
||||
'upgrade_link': upgrade_url,
|
||||
'course': course,
|
||||
'course_url': course_url,
|
||||
'disable_courseware_js': True,
|
||||
'uses_pattern_library': True,
|
||||
'is_self_paced': course.self_paced,
|
||||
'is_verified': is_verified,
|
||||
'has_access': has_access,
|
||||
}
|
||||
|
||||
if (has_access):
|
||||
grading_policy = course.grading_policy
|
||||
|
||||
(raw_grade_data, answered_percent, percent_grade) = self.get_grade_data(request.user, course_key, grading_policy['GRADE_CUTOFFS'])
|
||||
raw_schedule_data = self.get_assignments_with_due_date(request, course_key)
|
||||
|
||||
grade_data, schedule_data = self.sort_grade_and_schedule_data(raw_grade_data, raw_schedule_data)
|
||||
|
||||
# TODO: LEARNER-3854: Fix hacked defaults with real error handling if implementing Learner Analytics.
|
||||
try:
|
||||
weekly_active_users = self.get_weekly_course_activity_count(course_key)
|
||||
week_streak = self.consecutive_weeks_of_course_activity_for_user(
|
||||
request.user.username, course_key
|
||||
)
|
||||
except Exception as e:
|
||||
logging.exception(e)
|
||||
weekly_active_users = 134
|
||||
week_streak = 1
|
||||
|
||||
context.update({
|
||||
'grading_policy': grading_policy,
|
||||
'assignment_grades': grade_data,
|
||||
'answered_percent': answered_percent,
|
||||
'assignment_schedule': schedule_data,
|
||||
'assignment_schedule_raw': raw_schedule_data,
|
||||
'profile_image_urls': get_profile_image_urls_for_user(request.user, request),
|
||||
'discussion_info': self.get_discussion_data(request, course_key),
|
||||
'passing_grade': math.ceil(100 * course.lowest_passing_grade),
|
||||
'percent_grade': math.ceil(100 * percent_grade),
|
||||
'weekly_active_users': weekly_active_users,
|
||||
'week_streak': week_streak,
|
||||
})
|
||||
|
||||
return render_to_response('learner_analytics/dashboard.html', context)
|
||||
|
||||
def get_grade_data(self, user, course_key, grade_cutoffs):
|
||||
"""
|
||||
Collects and formats the grades data for a particular user and course.
|
||||
|
||||
Args:
|
||||
user (User)
|
||||
course_key (CourseKey)
|
||||
grade_cutoffs: # TODO: LEARNER-3854: Complete docstring if implementing Learner Analytics.
|
||||
"""
|
||||
course_grade = CourseGradeFactory().read(user, course_key=course_key)
|
||||
grades = []
|
||||
total_earned = 0
|
||||
total_possible = 0
|
||||
# answered_percent seems to be unused and it does not take into account assignment type weightings
|
||||
answered_percent = None
|
||||
|
||||
chapter_grades = course_grade.chapter_grades.values()
|
||||
|
||||
for chapter in chapter_grades:
|
||||
# Note: this code exists on the progress page. We should be able to remove it going forward.
|
||||
if not chapter['display_name'] == "hidden":
|
||||
for subsection_grade in chapter['sections']:
|
||||
log.info(subsection_grade.display_name)
|
||||
possible = subsection_grade.graded_total.possible
|
||||
earned = subsection_grade.graded_total.earned
|
||||
passing_grade = math.ceil(possible * grade_cutoffs['Pass'])
|
||||
grades.append({
|
||||
'assignment_type': subsection_grade.format,
|
||||
'total_earned': earned,
|
||||
'total_possible': possible,
|
||||
'passing_grade': passing_grade,
|
||||
'display_name': subsection_grade.display_name,
|
||||
'location': unicode(subsection_grade.location),
|
||||
'assigment_url': reverse('jump_to_id', kwargs={
|
||||
'course_id': unicode(course_key),
|
||||
'module_id': unicode(subsection_grade.location),
|
||||
})
|
||||
})
|
||||
if earned > 0:
|
||||
total_earned += earned
|
||||
total_possible += possible
|
||||
|
||||
if total_possible > 0:
|
||||
answered_percent = float(total_earned) / total_possible
|
||||
return (grades, answered_percent, course_grade.percent)
|
||||
|
||||
def sort_grade_and_schedule_data(self, grade_data, schedule_data):
|
||||
"""
|
||||
Sort the assignments in grade_data and schedule_data to be in the same order.
|
||||
"""
|
||||
schedule_dict = {assignment['location']: assignment for assignment in schedule_data}
|
||||
|
||||
sorted_schedule_data = []
|
||||
sorted_grade_data = []
|
||||
for grade in grade_data:
|
||||
assignment = schedule_dict.get(grade['location'])
|
||||
if assignment:
|
||||
sorted_grade_data.append(grade)
|
||||
sorted_schedule_data.append(assignment)
|
||||
|
||||
return sorted_grade_data, sorted_schedule_data
|
||||
|
||||
def get_discussion_data(self, request, course_key):
|
||||
"""
|
||||
Collects and formats the discussion data from a particular user and course.
|
||||
|
||||
Args:
|
||||
request (HttpRequest)
|
||||
course_key (CourseKey)
|
||||
"""
|
||||
try:
|
||||
context = create_user_profile_context(request, course_key, request.user.id)
|
||||
except Exception as e:
|
||||
# TODO: LEARNER-3854: Clean-up error handling if continuing support.
|
||||
return {
|
||||
'content_authored': 0,
|
||||
'thread_votes': 0,
|
||||
}
|
||||
|
||||
threads = context['threads']
|
||||
profiled_user = context['profiled_user']
|
||||
|
||||
# TODO: LEARNER-3854: If implementing Learner Analytics, rename to content_authored_count.
|
||||
content_authored = profiled_user['threads_count'] + profiled_user['comments_count']
|
||||
thread_votes = 0
|
||||
for thread in threads:
|
||||
if thread['user_id'] == profiled_user['external_id']:
|
||||
thread_votes += thread['votes']['count']
|
||||
discussion_data = {
|
||||
'content_authored': content_authored,
|
||||
'thread_votes': thread_votes,
|
||||
}
|
||||
return discussion_data
|
||||
|
||||
def get_assignments_with_due_date(self, request, course_key):
|
||||
"""
|
||||
Returns a list of assignment (graded) blocks with due dates, including
|
||||
due date and location.
|
||||
|
||||
Args:
|
||||
request (HttpRequest)
|
||||
course_key (CourseKey)
|
||||
"""
|
||||
course_usage_key = modulestore().make_course_usage_key(course_key)
|
||||
all_blocks = get_blocks(
|
||||
request,
|
||||
course_usage_key,
|
||||
user=request.user,
|
||||
nav_depth=3,
|
||||
requested_fields=['display_name', 'due', 'graded', 'format'],
|
||||
block_types_filter=['sequential']
|
||||
)
|
||||
assignment_blocks = []
|
||||
for (location, block) in all_blocks['blocks'].iteritems():
|
||||
if block.get('graded', False):
|
||||
assignment_blocks.append(block)
|
||||
block['due'] = block['due'].isoformat() if block.get('due') is not None else None
|
||||
block['location'] = unicode(location)
|
||||
|
||||
return assignment_blocks
|
||||
|
||||
def get_weekly_course_activity_count(self, course_key):
|
||||
"""
|
||||
Get the count of any course activity (total for all users) from previous 7 days.
|
||||
|
||||
Args:
|
||||
course_key (CourseKey)
|
||||
"""
|
||||
cache_key = 'learner_analytics_{course_key}_weekly_activities'.format(course_key=course_key)
|
||||
activities = cache.get(cache_key)
|
||||
|
||||
if not activities:
|
||||
log.info(u'Weekly course activities for course {course_key} was not cached - fetching from Analytics API'
|
||||
.format(course_key=course_key))
|
||||
weekly_course_activities = self.analytics_client.courses(course_key).activity()
|
||||
|
||||
if not weekly_course_activities or 'any' not in weekly_course_activities[0]:
|
||||
return 0
|
||||
|
||||
# weekly course activities should only have one item
|
||||
activities = weekly_course_activities[0]
|
||||
cache.set(cache_key, activities, LearnerAnalyticsView.seconds_to_cache_expiration())
|
||||
|
||||
return activities['any']
|
||||
|
||||
def consecutive_weeks_of_course_activity_for_user(self, username, course_key):
|
||||
"""
|
||||
Get the most recent count of consecutive days that a user has performed a course activity
|
||||
|
||||
Args:
|
||||
username (str)
|
||||
course_key (CourseKey)
|
||||
"""
|
||||
cache_key = 'learner_analytics_{username}_{course_key}_engagement_timeline'\
|
||||
.format(username=username, course_key=course_key)
|
||||
timeline = cache.get(cache_key)
|
||||
|
||||
if not timeline:
|
||||
log.info(u'Engagement timeline for course {course_key} was not cached - fetching from Analytics API'
|
||||
.format(course_key=course_key))
|
||||
|
||||
# TODO (LEARNER-3470): @jaebradley replace this once the Analytics client has an engagement timeline method
|
||||
url = '{base_url}/engagement_timelines/{username}?course_id={course_key}'\
|
||||
.format(base_url=settings.ANALYTICS_API_URL,
|
||||
username=username,
|
||||
course_key=urllib.quote_plus(unicode(course_key)))
|
||||
headers = {'Authorization': u'Token {token}'.format(token=settings.ANALYTICS_API_KEY)}
|
||||
response = requests.get(url=url, headers=headers)
|
||||
data = response.json()
|
||||
|
||||
if not data or 'days' not in data or not data['days']:
|
||||
return 0
|
||||
|
||||
# Analytics API returns data in ascending (by date) order - we want to count starting from most recent day
|
||||
data_ordered_by_date_descending = list(reversed(data['days']))
|
||||
|
||||
cache.set(cache_key, data_ordered_by_date_descending, LearnerAnalyticsView.seconds_to_cache_expiration())
|
||||
timeline = data_ordered_by_date_descending
|
||||
|
||||
return LearnerAnalyticsView.calculate_week_streak(timeline)
|
||||
|
||||
@staticmethod
|
||||
def calculate_week_streak(daily_activities):
|
||||
"""
|
||||
Check number of weeks in a row that a user has performed some activity.
|
||||
|
||||
Regardless of when a week starts, a sufficient condition for checking if a specific week had any user activity
|
||||
(given a list of daily activities ordered by date) is to iterate through the list of days 7 days at a time and
|
||||
check to see if any of those days had any activity.
|
||||
|
||||
Args:
|
||||
daily_activities: sorted list of dictionaries containing activities and their counts
|
||||
"""
|
||||
week_streak = 0
|
||||
seven_day_buckets = [daily_activities[i:i + 7] for i in range(0, len(daily_activities), 7)]
|
||||
for bucket in seven_day_buckets:
|
||||
if any(LearnerAnalyticsView.has_activity(day) for day in bucket):
|
||||
week_streak += 1
|
||||
else:
|
||||
return week_streak
|
||||
return week_streak
|
||||
|
||||
@staticmethod
|
||||
def has_activity(daily_activity):
|
||||
"""
|
||||
Validate that a course had some activity that day
|
||||
|
||||
Args:
|
||||
daily_activity: dictionary of activities and their counts
|
||||
"""
|
||||
return int(daily_activity['problems_attempted']) > 0 \
|
||||
or int(daily_activity['problems_completed']) > 0 \
|
||||
or int(daily_activity['discussion_contributions']) > 0 \
|
||||
or int(daily_activity['videos_viewed']) > 0
|
||||
|
||||
@staticmethod
|
||||
def seconds_to_cache_expiration():
|
||||
"""Calculate cache expiration seconds. Currently set to seconds until midnight UTC"""
|
||||
next_midnight_utc = (datetime.today() + timedelta(days=1)).replace(hour=0, minute=0, second=0,
|
||||
microsecond=0, tzinfo=pytz.utc)
|
||||
now_utc = datetime.now(tz=pytz.utc)
|
||||
return round((next_midnight_utc - now_utc).total_seconds())
|
||||
@@ -1,12 +0,0 @@
|
||||
Portfolio Project
|
||||
-----------------
|
||||
|
||||
This is the current home of the Portfolio Project feature. This is not a fully
|
||||
supported feature.
|
||||
|
||||
See `LEARNER-3515`_ for details.
|
||||
|
||||
TODO: `LEARNER-3515`_: If this feature gets implemented, this directory should
|
||||
move to lms/djangoapps and out of openedx/features.
|
||||
|
||||
.. _LEARNER-3515: https://openedx.atlassian.net/browse/LEARNER-3515
|
||||
@@ -1,13 +0,0 @@
|
||||
"""
|
||||
Portfolio project helpers and settings
|
||||
"""
|
||||
from openedx.core.djangoapps.waffle_utils import (
|
||||
CourseWaffleFlag, WaffleFlag, WaffleFlagNamespace
|
||||
)
|
||||
|
||||
# Namespace for portfolio project waffle flags.
|
||||
WAFFLE_FLAG_NAMESPACE = WaffleFlagNamespace(name='portfolio_project')
|
||||
|
||||
|
||||
# https://openedx.atlassian.net/browse/LEARNER-3926
|
||||
INCLUDE_PORTFOLIO_UPSELL_MODAL = WaffleFlag(WAFFLE_FLAG_NAMESPACE, 'include_upsell_modal')
|
||||
@@ -1,10 +0,0 @@
|
||||
## mako
|
||||
<%page expression_filter="h"/>
|
||||
|
||||
<%namespace name='static' file='../static_content.html'/>
|
||||
|
||||
<%block name="content">
|
||||
<div class="course-view page-content-container" id="course-container">
|
||||
</div>
|
||||
</%block>
|
||||
<%namespace name='static' file='../static_content.html'/>
|
||||
@@ -1,15 +0,0 @@
|
||||
"""
|
||||
Url setup for portfolio project.
|
||||
"""
|
||||
from django.conf.urls import url
|
||||
|
||||
from views import GenericTabView
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
url(
|
||||
r'^$',
|
||||
GenericTabView.as_view(),
|
||||
name='openedx.portfolio.generic_tab',
|
||||
),
|
||||
]
|
||||
@@ -1,45 +0,0 @@
|
||||
"""
|
||||
Portfolio views.
|
||||
"""
|
||||
|
||||
from django.template.loader import render_to_string
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.views.decorators.cache import cache_control
|
||||
|
||||
from lms.djangoapps.courseware.views.views import CourseTabView
|
||||
|
||||
from util.views import ensure_valid_course_key
|
||||
|
||||
from web_fragments.fragment import Fragment
|
||||
|
||||
|
||||
class GenericTabView(CourseTabView):
|
||||
"""
|
||||
Provides a blank page that acts as its own tab in courseware for displaying content.
|
||||
"""
|
||||
|
||||
def uses_bootstrap(self, request, course, tab):
|
||||
"""
|
||||
Forces the generic tab to use bootstrap styling.
|
||||
"""
|
||||
return True
|
||||
|
||||
@method_decorator(cache_control(no_cache=True, no_store=True, must_revalidate=True))
|
||||
@method_decorator(ensure_valid_course_key)
|
||||
def get(self, request, course_id, **kwargs):
|
||||
"""
|
||||
Displays a generic tab for the specified course.
|
||||
"""
|
||||
return super(GenericTabView, self).get(request, course_id, 'courseware', **kwargs)
|
||||
|
||||
def render_to_fragment(self, request, course=None, tab=None, **kwargs):
|
||||
"""
|
||||
Render out the bootstrap page.
|
||||
"""
|
||||
context = {
|
||||
'course': course,
|
||||
'user': request.user,
|
||||
'tab_name': tab,
|
||||
}
|
||||
html = render_to_string('portfolio_project/generic_tab.html', context)
|
||||
return Fragment(html)
|
||||
@@ -7,8 +7,6 @@ from django.urls import reverse
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
from openedx.core.djangolib.js_utils import dump_js_escaped_json, js_escaped_string
|
||||
from openedx.core.djangolib.markup import HTML, Text
|
||||
from openedx.features.learner_analytics import INCLUDE_UPSELL_MODAL
|
||||
from openedx.features.portfolio_project import INCLUDE_PORTFOLIO_UPSELL_MODAL
|
||||
%>
|
||||
|
||||
<%namespace name='static' file='/static_content.html'/>
|
||||
@@ -86,21 +84,6 @@ from openedx.features.portfolio_project import INCLUDE_PORTFOLIO_UPSELL_MODAL
|
||||
|
||||
<div id="currency_data" value="${currency_data}"></div>
|
||||
<div class="container">
|
||||
% if INCLUDE_UPSELL_MODAL.is_enabled():
|
||||
${static.renderReact(
|
||||
component="UpsellExperimentModal",
|
||||
id="upsell-modal",
|
||||
props={},
|
||||
)}
|
||||
% endif
|
||||
|
||||
% if INCLUDE_PORTFOLIO_UPSELL_MODAL.is_enabled():
|
||||
${static.renderReact(
|
||||
component="PortfolioExperimentUpsellModal",
|
||||
id="portfolio-experiment-upsell-modal",
|
||||
props={}
|
||||
)}
|
||||
% endif
|
||||
<section class="wrapper">
|
||||
<div class="wrapper-register-choose wrapper-content-main">
|
||||
<article class="register-choose content-main">
|
||||
|
||||
@@ -19,9 +19,6 @@ from openedx.core.djangoapps.theming import helpers as theming_helpers
|
||||
from openedx.core.djangolib.js_utils import dump_js_escaped_json, js_escaped_string
|
||||
from openedx.core.djangolib.markup import HTML, Text
|
||||
|
||||
from openedx.features.learner_analytics import INCLUDE_UPSELL_MODAL
|
||||
from openedx.features.portfolio_project import INCLUDE_PORTFOLIO_UPSELL_MODAL
|
||||
|
||||
from entitlements.models import CourseEntitlement
|
||||
from student.models import CourseEnrollment
|
||||
%>
|
||||
@@ -143,21 +140,6 @@ from student.models import CourseEnrollment
|
||||
|
||||
<section class="dashboard" id="dashboard-main">
|
||||
<main class="main-container" id="main" aria-label="Content" tabindex="-1">
|
||||
% if INCLUDE_UPSELL_MODAL.is_enabled():
|
||||
${static.renderReact(
|
||||
component="UpsellExperimentModal",
|
||||
id="upsell-modal",
|
||||
props={},
|
||||
)}
|
||||
% endif
|
||||
|
||||
% if INCLUDE_PORTFOLIO_UPSELL_MODAL.is_enabled():
|
||||
${static.renderReact(
|
||||
component="PortfolioExperimentUpsellModal",
|
||||
id="portfolio-experiment-upsell-modal",
|
||||
props={},
|
||||
)}
|
||||
% endif
|
||||
<section class="my-courses" id="my-courses">
|
||||
<header class="wrapper-header-courses">
|
||||
<h2 class="header-courses">${_("My Courses")}</h2>
|
||||
|
||||
@@ -84,9 +84,6 @@ module.exports = Merge.smart({
|
||||
// LMS
|
||||
SingleSupportForm: './lms/static/support/jsx/single_support_form.jsx',
|
||||
AlertStatusBar: './lms/static/js/accessible_components/StatusBarAlert.jsx',
|
||||
LearnerAnalyticsDashboard: './lms/static/js/learner_analytics_dashboard/LearnerAnalyticsDashboard.jsx',
|
||||
UpsellExperimentModal: './lms/static/common/js/components/UpsellExperimentModal.jsx',
|
||||
PortfolioExperimentUpsellModal: './lms/static/common/js/components/PortfolioExperimentUpsellModal.jsx',
|
||||
EntitlementSupportPage: './lms/djangoapps/support/static/support/jsx/entitlements/index.jsx',
|
||||
PasswordResetConfirmation: './lms/static/js/student_account/components/PasswordResetConfirmation.jsx',
|
||||
StudentAccountDeletion: './lms/static/js/student_account/components/StudentAccountDeletion.jsx',
|
||||
|
||||
Reference in New Issue
Block a user