Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-platform
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
edx-platform
Commits
dcf528a5
Commit
dcf528a5
authored
Aug 09, 2017
by
Diana Huang
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add new latest-update fragment and waffle flag.
parent
e1ad295a
Show whitespace changes
Inline
Side-by-side
Showing
15 changed files
with
231 additions
and
54 deletions
+231
-54
lms/static/sass/features/_course-experience.scss
+2
-2
openedx/features/course_experience/__init__.py
+6
-0
openedx/features/course_experience/static/course_experience/fixtures/latest-update-fragment.html
+7
-0
openedx/features/course_experience/static/course_experience/js/LatestUpdate.js
+15
-0
openedx/features/course_experience/static/course_experience/js/spec/LatestUpdate_spec.js
+38
-0
openedx/features/course_experience/templates/course_experience/course-home-fragment.html
+3
-3
openedx/features/course_experience/templates/course_experience/latest-update-fragment.html
+24
-0
openedx/features/course_experience/tests/views/test_course_updates.py
+2
-2
openedx/features/course_experience/tests/views/test_welcome_message.py
+30
-9
openedx/features/course_experience/urls.py
+6
-0
openedx/features/course_experience/views/course_home.py
+10
-3
openedx/features/course_experience/views/course_updates.py
+33
-33
openedx/features/course_experience/views/latest_update.py
+52
-0
openedx/features/course_experience/views/welcome_message.py
+2
-2
webpack.config.js
+1
-0
No files found.
lms/static/sass/features/_course-experience.scss
View file @
dcf528a5
...
...
@@ -64,8 +64,8 @@
}
}
// Welcome message
.welcome-message
{
// Welcome message
/ Latest Update message
.welcome-message
,
.update-message
{
border
:
solid
1px
$lms-border-color
;
@include
border-left
(
solid
4px
$black
);
margin-bottom
:
$baseline
;
...
...
openedx/features/course_experience/__init__.py
View file @
dcf528a5
...
...
@@ -25,6 +25,12 @@ COURSE_PRE_START_ACCESS_FLAG = CourseWaffleFlag(WAFFLE_FLAG_NAMESPACE, 'pre_star
# Waffle flag to enable a review page link from the unified home page
SHOW_REVIEWS_TOOL_FLAG
=
CourseWaffleFlag
(
WAFFLE_FLAG_NAMESPACE
,
'show_reviews_tool'
)
# Waffle flag to switch between the 'welcome message' and 'latest update' on the course home page.
# Important Admin Note: This is meant to be configured using waffle_utils course
# override only. Either do not create the actual waffle flag, or be sure to unset the
# flag even for Superusers.
LATEST_UPDATE_FLAG
=
CourseWaffleFlag
(
WAFFLE_FLAG_NAMESPACE
,
'latest_update'
)
def
course_home_page_title
(
course
):
# pylint: disable=unused-argument
"""
...
...
openedx/features/course_experience/static/course_experience/fixtures/latest-update-fragment.html
0 → 100644
View file @
dcf528a5
<div
class=
"update-message"
>
<h3>
Latest Update
</h3>
<div
class=
"dismiss-message"
>
<button
type=
"button"
class=
"btn-link"
>
Dismiss
</button>
</div>
This is an update.
</div>
openedx/features/course_experience/static/course_experience/js/LatestUpdate.js
0 → 100644
View file @
dcf528a5
/* globals $ */
import
'jquery.cookie'
;
export
class
LatestUpdate
{
// eslint-disable-line import/prefer-default-export
constructor
(
options
)
{
if
(
$
.
cookie
(
'update-message'
)
===
'hide'
)
{
$
(
options
.
messageContainer
).
hide
();
}
$
(
options
.
dismissButton
).
click
(()
=>
{
$
.
cookie
(
'update-message'
,
'hide'
,
{
expires
:
1
});
$
(
options
.
messageContainer
).
hide
();
});
}
}
openedx/features/course_experience/static/course_experience/js/spec/LatestUpdate_spec.js
0 → 100644
View file @
dcf528a5
/* globals $, loadFixtures */
import
'jquery.cookie'
;
import
{
LatestUpdate
}
from
'../LatestUpdate'
;
describe
(
'LatestUpdate tests'
,
()
=>
{
function
createLatestUpdate
()
{
new
LatestUpdate
({
messageContainer
:
'.update-message'
,
dismissButton
:
'.dismiss-message button'
});
// eslint-disable-line no-new
}
describe
(
'Test dismiss'
,
()
=>
{
beforeEach
(()
=>
{
// This causes the cookie to be deleted.
$
.
cookie
(
'update-message'
,
''
,
{
expires
:
-
1
});
loadFixtures
(
'course_experience/fixtures/latest-update-fragment.html'
);
});
it
(
'Test dismiss button'
,
()
=>
{
expect
(
$
.
cookie
(
'update-message'
)).
toBe
(
null
);
createLatestUpdate
();
expect
(
$
(
'.update-message'
).
attr
(
'style'
)).
toBe
(
undefined
);
$
(
'.dismiss-message button'
).
click
();
expect
(
$
(
'.update-message'
).
attr
(
'style'
)).
toBe
(
'display: none;'
);
expect
(
$
.
cookie
(
'update-message'
)).
toBe
(
'hide'
);
});
it
(
'Test cookie hides update'
,
()
=>
{
$
.
cookie
(
'update-message'
,
'hide'
);
createLatestUpdate
();
expect
(
$
(
'.update-message'
).
attr
(
'style'
)).
toBe
(
'display: none;'
);
$
.
cookie
(
'update-message'
,
''
,
{
expires
:
-
1
});
loadFixtures
(
'course_experience/fixtures/latest-update-fragment.html'
);
createLatestUpdate
();
expect
(
$
(
'.update-message'
).
attr
(
'style'
)).
toBe
(
undefined
);
});
});
});
openedx/features/course_experience/templates/course_experience/course-home-fragment.html
View file @
dcf528a5
...
...
@@ -61,9 +61,9 @@ from openedx.features.course_experience import UNIFIED_COURSE_TAB_FLAG, SHOW_REV
${HTML(course_home_message_fragment.body_html())}
% endif
% if
welcom
e_message_fragment and UNIFIED_COURSE_TAB_FLAG.is_enabled(course.id):
<div
class=
"section section-
dates
"
>
${HTML(
welcom
e_message_fragment.body_html())}
% if
updat
e_message_fragment and UNIFIED_COURSE_TAB_FLAG.is_enabled(course.id):
<div
class=
"section section-
update-message
"
>
${HTML(
updat
e_message_fragment.body_html())}
</div>
% endif
...
...
openedx/features/course_experience/templates/course_experience/latest-update-fragment.html
0 → 100644
View file @
dcf528a5
## mako
<
%
page
expression_filter=
"h"
/>
<
%
namespace
name=
'static'
file=
'../static_content.html'
/>
<
%!
from
django
.
utils
.
translation
import
ugettext
as
_
from
openedx
.
core
.
djangolib
.
markup
import
HTML
%
>
<
%
block
name=
"content"
>
<div
class=
"update-message"
>
<div
class=
"dismiss-message"
>
<button
type=
"button"
class=
"btn-link"
>
${_("Dismiss")}
</button>
</div>
<h3>
${_("Latest Update")}
</h3>
${HTML(update_html)}
</div>
</
%
block>
<
%
static:webpack
entry=
"LatestUpdate"
>
new LatestUpdate( { messageContainer: '.update-message', dismissButton: '.dismiss-message button'});
</
%
static:webpack>
openedx/features/course_experience/tests/views/test_course_updates.py
View file @
dcf528a5
...
...
@@ -4,7 +4,7 @@ Tests for the course updates page.
from
courseware.courses
import
get_course_info_usage_key
from
django.core.urlresolvers
import
reverse
from
openedx.core.djangoapps.waffle_utils.testutils
import
WAFFLE_TABLES
from
openedx.features.course_experience.views.course_updates
import
CourseUpdatesFragmentView
from
openedx.features.course_experience.views.course_updates
import
STATUS_VISIBLE
from
student.models
import
CourseEnrollment
from
student.tests.factories
import
UserFactory
from
xmodule.modulestore
import
ModuleStoreEnum
...
...
@@ -43,7 +43,7 @@ def create_course_update(course, user, content, date='December 31, 1999'):
"id"
:
len
(
course_updates
.
items
)
+
1
,
"date"
:
date
,
"content"
:
content
,
"status"
:
CourseUpdatesFragmentView
.
STATUS_VISIBLE
"status"
:
STATUS_VISIBLE
})
modulestore
()
.
update_item
(
course_updates
,
user
.
id
)
...
...
openedx/features/course_experience/tests/views/test_welcome_message.py
View file @
dcf528a5
"""
Tests for course welcome messages.
"""
import
ddt
from
django.core.urlresolvers
import
reverse
from
student.models
import
CourseEnrollment
...
...
@@ -27,6 +28,18 @@ def welcome_message_url(course):
)
def
latest_update_url
(
course
):
"""
Returns the URL for the latest update view.
"""
return
reverse
(
'openedx.course_experience.latest_update_fragment_view'
,
kwargs
=
{
'course_id'
:
unicode
(
course
.
id
),
}
)
def
dismiss_message_url
(
course
):
"""
Returns the URL for the dismiss message endpoint.
...
...
@@ -39,9 +52,12 @@ def dismiss_message_url(course):
)
@ddt.ddt
class
TestWelcomeMessageView
(
ModuleStoreTestCase
):
"""
Tests for the course welcome message fragment view.
Also tests the LatestUpdate view because the functionality is similar.
"""
def
setUp
(
self
):
"""Set up the simplest course possible, then set up and enroll our fake user in the course."""
...
...
@@ -61,30 +77,35 @@ class TestWelcomeMessageView(ModuleStoreTestCase):
remove_course_updates
(
self
.
user
,
self
.
course
)
super
(
TestWelcomeMessageView
,
self
)
.
tearDown
()
def
test_welcome_message
(
self
):
@ddt.data
(
welcome_message_url
,
latest_update_url
)
def
test_message_display
(
self
,
url_generator
):
create_course_update
(
self
.
course
,
self
.
user
,
'First Update'
,
date
=
'January 1, 2000'
)
create_course_update
(
self
.
course
,
self
.
user
,
'Second Update'
,
date
=
'January 1, 2017'
)
create_course_update
(
self
.
course
,
self
.
user
,
'Retroactive Update'
,
date
=
'January 1, 2010'
)
response
=
self
.
client
.
get
(
welcome_message_url
(
self
.
course
))
response
=
self
.
client
.
get
(
url_generator
(
self
.
course
))
self
.
assertEqual
(
response
.
status_code
,
200
)
self
.
assertContains
(
response
,
'Second Update'
)
self
.
assertContains
(
response
,
'Dismiss'
)
def
test_replace_urls
(
self
):
@ddt.data
(
welcome_message_url
,
latest_update_url
)
def
test_replace_urls
(
self
,
url_generator
):
img_url
=
'img.png'
create_course_update
(
self
.
course
,
self
.
user
,
"<img src='/static/{url}'>"
.
format
(
url
=
img_url
))
response
=
self
.
client
.
get
(
welcome_message_url
(
self
.
course
))
self
.
assertContains
(
response
,
"/asset-v1:{org}+{course}+{run}+type@asset+block/
img.png
"
.
format
(
response
=
self
.
client
.
get
(
url_generator
(
self
.
course
))
self
.
assertContains
(
response
,
"/asset-v1:{org}+{course}+{run}+type@asset+block/
{url}
"
.
format
(
org
=
self
.
course
.
id
.
org
,
course
=
self
.
course
.
id
.
course
,
run
=
self
.
course
.
id
.
run
run
=
self
.
course
.
id
.
run
,
url
=
img_url
,
))
def
test_empty_welcome_message
(
self
):
response
=
self
.
client
.
get
(
welcome_message_url
(
self
.
course
))
@ddt.data
(
welcome_message_url
,
latest_update_url
)
def
test_empty_message
(
self
,
url_generator
):
response
=
self
.
client
.
get
(
url_generator
(
self
.
course
))
self
.
assertEqual
(
response
.
status_code
,
204
)
def
test_dismiss_message
(
self
):
def
test_dismiss_welcome_message
(
self
):
# Latest update is dimssed in JS and has no server/backend component.
create_course_update
(
self
.
course
,
self
.
user
,
'First Update'
,
date
=
'January 1, 2017'
)
response
=
self
.
client
.
get
(
welcome_message_url
(
self
.
course
))
...
...
openedx/features/course_experience/urls.py
View file @
dcf528a5
...
...
@@ -9,6 +9,7 @@ from views.course_outline import CourseOutlineFragmentView
from
views.course_reviews
import
CourseReviewsView
from
views.course_updates
import
CourseUpdatesFragmentView
,
CourseUpdatesView
from
views.course_sock
import
CourseSockFragmentView
from
views.latest_update
import
LatestUpdateFragmentView
from
views.welcome_message
import
WelcomeMessageFragmentView
,
dismiss_welcome_message
urlpatterns
=
[
...
...
@@ -48,6 +49,11 @@ urlpatterns = [
name
=
'openedx.course_experience.welcome_message_fragment_view'
,
),
url
(
r'^latest_update_fragment$'
,
LatestUpdateFragmentView
.
as_view
(),
name
=
'openedx.course_experience.latest_update_fragment_view'
,
),
url
(
r'course_sock_fragment$'
,
CourseSockFragmentView
.
as_view
(),
name
=
'openedx.course_experience.course_sock_fragment_view'
,
...
...
openedx/features/course_experience/views/course_home.py
View file @
dcf528a5
...
...
@@ -24,11 +24,13 @@ from student.models import CourseEnrollment
from
util.views
import
ensure_valid_course_key
from
web_fragments.fragment
import
Fragment
from
..
import
LATEST_UPDATE_FLAG
from
..utils
import
get_course_outline_block_tree
from
.course_dates
import
CourseDatesFragmentView
from
.course_home_messages
import
CourseHomeMessageFragmentView
from
.course_outline
import
CourseOutlineFragmentView
from
.course_sock
import
CourseSockFragmentView
from
.latest_update
import
LatestUpdateFragmentView
from
.welcome_message
import
WelcomeMessageFragmentView
EMPTY_HANDOUTS_HTML
=
u'<ol></ol>'
...
...
@@ -121,7 +123,12 @@ class CourseHomeFragmentView(EdxFragmentView):
}
if
user_access
[
'is_enrolled'
]
or
user_access
[
'is_staff'
]:
outline_fragment
=
CourseOutlineFragmentView
()
.
render_to_fragment
(
request
,
course_id
=
course_id
,
**
kwargs
)
welcome_message_fragment
=
WelcomeMessageFragmentView
()
.
render_to_fragment
(
if
LATEST_UPDATE_FLAG
.
is_enabled
(
course_key
):
update_message_fragment
=
LatestUpdateFragmentView
()
.
render_to_fragment
(
request
,
course_id
=
course_id
,
**
kwargs
)
else
:
update_message_fragment
=
WelcomeMessageFragmentView
()
.
render_to_fragment
(
request
,
course_id
=
course_id
,
**
kwargs
)
course_sock_fragment
=
CourseSockFragmentView
()
.
render_to_fragment
(
request
,
course
=
course
,
**
kwargs
)
...
...
@@ -134,7 +141,7 @@ class CourseHomeFragmentView(EdxFragmentView):
# Set all the fragments
outline_fragment
=
None
welcom
e_message_fragment
=
None
updat
e_message_fragment
=
None
course_sock_fragment
=
None
has_visited_course
=
None
resume_course_url
=
None
...
...
@@ -163,7 +170,7 @@ class CourseHomeFragmentView(EdxFragmentView):
'resume_course_url'
:
resume_course_url
,
'course_tools'
:
course_tools
,
'dates_fragment'
:
dates_fragment
,
'
welcome_message_fragment'
:
welcom
e_message_fragment
,
'
update_message_fragment'
:
updat
e_message_fragment
,
'course_sock_fragment'
:
course_sock_fragment
,
'disable_courseware_js'
:
True
,
'uses_pattern_library'
:
True
,
...
...
openedx/features/course_experience/views/course_updates.py
View file @
dcf528a5
...
...
@@ -17,6 +17,37 @@ from lms.djangoapps.courseware.views.views import CourseTabView
from
openedx.core.djangoapps.plugin_api.views
import
EdxFragmentView
from
openedx.features.course_experience
import
default_course_url_name
STATUS_VISIBLE
=
'visible'
STATUS_DELETED
=
'deleted'
def
get_ordered_updates
(
request
,
course
):
"""
Returns any course updates in reverse chronological order.
"""
info_module
=
get_course_info_section_module
(
request
,
request
.
user
,
course
,
'updates'
)
updates
=
info_module
.
items
if
info_module
else
[]
info_block
=
getattr
(
info_module
,
'_xmodule'
,
info_module
)
if
info_module
else
None
ordered_updates
=
[
update
for
update
in
updates
if
update
.
get
(
'status'
)
==
STATUS_VISIBLE
]
ordered_updates
.
sort
(
key
=
lambda
item
:
(
safe_parse_date
(
item
[
'date'
]),
item
[
'id'
]),
reverse
=
True
)
for
update
in
ordered_updates
:
update
[
'content'
]
=
info_block
.
system
.
replace_urls
(
update
[
'content'
])
return
ordered_updates
def
safe_parse_date
(
date
):
"""
Since this is used solely for ordering purposes, use today's date as a default
"""
try
:
return
datetime
.
strptime
(
date
,
'
%
B
%
d,
%
Y'
)
except
ValueError
:
# occurs for ill-formatted date values
return
datetime
.
today
()
class
CourseUpdatesView
(
CourseTabView
):
"""
...
...
@@ -41,9 +72,6 @@ class CourseUpdatesFragmentView(EdxFragmentView):
"""
A fragment to render the updates page for a course.
"""
STATUS_VISIBLE
=
'visible'
STATUS_DELETED
=
'deleted'
def
render_to_fragment
(
self
,
request
,
course_id
=
None
,
**
kwargs
):
"""
Renders the course's home page as a fragment.
...
...
@@ -53,7 +81,7 @@ class CourseUpdatesFragmentView(EdxFragmentView):
course_url_name
=
default_course_url_name
(
course
.
id
)
course_url
=
reverse
(
course_url_name
,
kwargs
=
{
'course_id'
:
unicode
(
course
.
id
)})
ordered_updates
=
self
.
get_ordered_updates
(
request
,
course
)
ordered_updates
=
get_ordered_updates
(
request
,
course
)
plain_html_updates
=
''
if
ordered_updates
:
plain_html_updates
=
self
.
get_plain_html_updates
(
request
,
course
)
...
...
@@ -72,26 +100,8 @@ class CourseUpdatesFragmentView(EdxFragmentView):
return
Fragment
(
html
)
@classmethod
def
get_ordered_updates
(
self
,
request
,
course
):
"""
Returns any course updates in reverse chronological order.
"""
info_module
=
get_course_info_section_module
(
request
,
request
.
user
,
course
,
'updates'
)
updates
=
info_module
.
items
if
info_module
else
[]
info_block
=
getattr
(
info_module
,
'_xmodule'
,
info_module
)
if
info_module
else
None
ordered_updates
=
[
update
for
update
in
updates
if
update
.
get
(
'status'
)
==
self
.
STATUS_VISIBLE
]
ordered_updates
.
sort
(
key
=
lambda
item
:
(
self
.
safe_parse_date
(
item
[
'date'
]),
item
[
'id'
]),
reverse
=
True
)
for
update
in
ordered_updates
:
update
[
'content'
]
=
info_block
.
system
.
replace_urls
(
update
[
'content'
])
return
ordered_updates
@classmethod
def
has_updates
(
self
,
request
,
course
):
return
len
(
self
.
get_ordered_updates
(
request
,
course
))
>
0
return
len
(
get_ordered_updates
(
request
,
course
))
>
0
@classmethod
def
get_plain_html_updates
(
self
,
request
,
course
):
...
...
@@ -103,13 +113,3 @@ class CourseUpdatesFragmentView(EdxFragmentView):
info_module
=
get_course_info_section_module
(
request
,
request
.
user
,
course
,
'updates'
)
info_block
=
getattr
(
info_module
,
'_xmodule'
,
info_module
)
return
info_block
.
system
.
replace_urls
(
info_module
.
data
)
if
info_module
else
''
@staticmethod
def
safe_parse_date
(
date
):
"""
Since this is used solely for ordering purposes, use today's date as a default
"""
try
:
return
datetime
.
strptime
(
date
,
'
%
B
%
d,
%
Y'
)
except
ValueError
:
# occurs for ill-formatted date values
return
datetime
.
today
()
openedx/features/course_experience/views/latest_update.py
0 → 100644
View file @
dcf528a5
"""
View logic for handling latest course updates.
Although the welcome message fragment also displays the latest update,
this fragment dismisses the message for a limited time so new updates
will continue to appear, where the welcome message gets permanently
dismissed.
"""
from
django.template.loader
import
render_to_string
from
opaque_keys.edx.keys
import
CourseKey
from
web_fragments.fragment
import
Fragment
from
courseware.courses
import
get_course_with_access
from
openedx.core.djangoapps.plugin_api.views
import
EdxFragmentView
from
openedx.features.course_experience.views.course_updates
import
get_ordered_updates
class
LatestUpdateFragmentView
(
EdxFragmentView
):
"""
A fragment that displays the latest course update.
"""
def
render_to_fragment
(
self
,
request
,
course_id
=
None
,
**
kwargs
):
"""
Renders the latest update message fragment for the specified course.
Returns: A fragment, or None if there is no latest update message.
"""
course_key
=
CourseKey
.
from_string
(
course_id
)
course
=
get_course_with_access
(
request
.
user
,
'load'
,
course_key
,
check_if_enrolled
=
True
)
update_html
=
self
.
latest_update_html
(
request
,
course
)
if
not
update_html
:
return
None
context
=
{
'update_html'
:
update_html
,
}
html
=
render_to_string
(
'course_experience/latest-update-fragment.html'
,
context
)
return
Fragment
(
html
)
@classmethod
def
latest_update_html
(
cls
,
request
,
course
):
"""
Returns the course's latest update message or None if it doesn't have one.
"""
# Return the course update with the most recent publish date
ordered_updates
=
get_ordered_updates
(
request
,
course
)
content
=
None
if
ordered_updates
:
content
=
ordered_updates
[
0
][
'content'
]
return
content
openedx/features/course_experience/views/welcome_message.py
View file @
dcf528a5
...
...
@@ -9,7 +9,7 @@ from django.views.decorators.csrf import ensure_csrf_cookie
from
opaque_keys.edx.keys
import
CourseKey
from
web_fragments.fragment
import
Fragment
from
course_updates
import
CourseUpdatesFragmentView
from
course_updates
import
get_ordered_updates
from
courseware.courses
import
get_course_info_section_module
,
get_course_with_access
from
openedx.core.djangoapps.plugin_api.views
import
EdxFragmentView
from
openedx.core.djangoapps.user_api.course_tag.api
import
set_course_tag
,
get_course_tag
...
...
@@ -54,7 +54,7 @@ class WelcomeMessageFragmentView(EdxFragmentView):
Returns the course's welcome message or None if it doesn't have one.
"""
# Return the course update with the most recent publish date
ordered_updates
=
CourseUpdatesFragmentView
.
get_ordered_updates
(
request
,
course
)
ordered_updates
=
get_ordered_updates
(
request
,
course
)
content
=
None
if
ordered_updates
:
content
=
ordered_updates
[
0
][
'content'
]
...
...
webpack.config.js
View file @
dcf528a5
...
...
@@ -22,6 +22,7 @@ var wpconfig = {
CourseOutline
:
'./openedx/features/course_experience/static/course_experience/js/CourseOutline.js'
,
CourseSock
:
'./openedx/features/course_experience/static/course_experience/js/CourseSock.js'
,
CourseTalkReviews
:
'./openedx/features/course_experience/static/course_experience/js/CourseTalkReviews.js'
,
LatestUpdate
:
'./openedx/features/course_experience/static/course_experience/js/LatestUpdate.js'
,
WelcomeMessage
:
'./openedx/features/course_experience/static/course_experience/js/WelcomeMessage.js'
,
Enrollment
:
'./openedx/features/course_experience/static/course_experience/js/Enrollment.js'
,
Import
:
'./cms/static/js/features/import/factories/import.js'
,
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment