From 525aa6e033dc0174c473f9f7432378200fc05848 Mon Sep 17 00:00:00 2001 From: Tyler Hallada Date: Fri, 14 Jul 2017 12:39:30 -0400 Subject: [PATCH] Replace require.js with webpack, remove bower (#701) * Replace bower with npm. Really slow r.js build * WIP replace r.js with webpack Successfully replaced r.js with webpack and have gotten a local instance of Insights working with webpack-built js and css bundles. There are many issues remaining. Just to name a few: * The size of the bundles are very large compared to r.js bundles * Libraries are repeatedly included in many bundles * The speed of building bundles, while faster than r.js, is still slow * A separate prod and dev configs are needed * A proper webpack dev environment still needs to be set up (e.g. hot module reloading) * reference correct location for pattern library npm packages * add bourbon npm package and link to it * globalization functionality is split into its own chunk * Optimized bundle size * Set-up webpack-dev-server * Remove vendor folder. fast-sass-loader. * Use fast source-map method for development. * Split dev/prod webpack config. Lots of comments! * No need for "vendor" bundle, just use "manifest". * Git ignore webpack-stats.json * Common chunks vendor bundle. Fix sassc. * Prettier the webpack configs * Run prettier-eslint instead * Passing karma tests with webpack * Fix linting error * Fix bootstrap dropdowns * Remove legacy bower, requirejs, django-compressor * Fix the travis build * Precompile the pattern-library to .css file * Fix post-install font copy paths * Update translations * Missed a few legacy rjs related lines * Linting: stray import in urls.py * Dropdowns in application-main. Compile to .scss * Use sass-loader. Update README. * Require full bootstrap to try fix acceptance tests * Load webpack in base.html so landing works on dev * Output webpack bundles to /static not /assets * Update translations * Add compression-webpack-plugin TBD whether django can actually figure out how to find the .gz files, but until then here is the best webpack prod config I could come up with. * Add babel-loader. Convert two js files to es2015 * Fix eslint errors: separate es6 eslint config * Fix packages for NPM 5. Add lock file. * Ignore /static/bundles in extract_translations * Try adding q to package.json? * Remove extraneous merge header * Remove cldr-data-downloader & q from package.json * Move webpack packages from dev to dependencies * Use backbone 1.2.3. Reintroduce router execute. * Remove package-lock.json until I figure it out * Remove webpack-compression-plugin (using CDN now) * Use webpack-merge for config inheritance * Use chunkhash in filename in production * Create source-maps in uglify on prod * Fix eslint & errors. Add prettier. More ES2015. * Address PR comments in _font.scss * Address PR comments in npm-post-install.sh * Make DJANGO_DEV_SERVER webpack proxy configurable --- .es6-eslintrc.json | 27 ++ .eslintignore | 1 + .eslintrc.json | 24 +- .gitignore | 4 + .travis/run_tests.sh | 4 - Makefile | 26 +- README.md | 52 ++- .../conf/locale/en/LC_MESSAGES/django.mo | Bin 378 -> 378 bytes .../conf/locale/en/LC_MESSAGES/django.po | 171 ++++---- .../conf/locale/en/LC_MESSAGES/djangojs.mo | Bin 378 -> 378 bytes .../conf/locale/en/LC_MESSAGES/djangojs.po | 392 ++++-------------- .../base_performance_answer_distribution.html | 4 +- .../base_performance_learning_outcomes.html | 1 - .../templates/courses/engagement_content.html | 4 +- .../courses/engagement_grouped_content.html | 4 +- .../engagement_video_by_subsection.html | 4 +- .../courses/engagement_video_timeline.html | 4 +- .../courses/enrollment_activity.html | 4 +- .../courses/enrollment_demographics_age.html | 5 +- .../enrollment_demographics_education.html | 4 +- .../enrollment_demographics_gender.html | 4 +- .../courses/enrollment_geography.html | 4 +- .../courses/templates/courses/index.html | 10 +- .../courses/templates/courses/learners.html | 11 +- .../courses/performance_assignment.html | 4 +- .../performance_graded_content_by_type.html | 1 - .../courses/performance_grouped_content.html | 4 +- ...performance_learning_outcomes_content.html | 4 +- ...performance_learning_outcomes_section.html | 4 +- .../performance_ungraded_by_subsection.html | 4 +- analytics_dashboard/django_rjs/__init__.py | 0 analytics_dashboard/django_rjs/models.py | 1 - .../django_rjs/templatetags/__init__.py | 0 .../django_rjs/templatetags/rjs.py | 45 -- analytics_dashboard/django_rjs/tests.py | 25 -- analytics_dashboard/settings/base.py | 29 +- analytics_dashboard/settings/production.py | 13 - analytics_dashboard/settings/test.py | 5 - .../apps/components/alert/views/alert-view.js | 2 +- .../download/views/download-data.js | 2 +- .../filter/views/checkbox-filter.js | 2 +- .../filter/views/drop-down-filter.js | 2 +- .../common/collections/collection.js | 12 +- .../generic-list/list/views/active-filters.js | 2 +- .../list/views/base-header-cell.js | 2 +- .../generic-list/list/views/list.js | 3 - .../generic-list/list/views/num-results.js | 6 +- .../generic-list/list/views/paging-footer.js | 2 +- .../generic-list/list/views/table.js | 2 +- .../apps/components/header/views/header.js | 2 +- .../static/apps/components/root/views/root.js | 2 +- .../static/apps/components/utils/utils.js | 3 +- .../apps/course-list/app/.eslintrc.json | 1 + .../static/apps/course-list/app/app.js | 138 +++--- .../static/apps/course-list/app/controller.js | 275 ++++++------ .../apps/course-list/app/course-list-main.js | 29 +- .../static/apps/course-list/app/router.js | 73 ++-- .../course-list/app/spec/controller-spec.js | 208 +++++----- .../apps/course-list/app/spec/router-spec.js | 152 ++++--- .../apps/course-list/list/views/controls.js | 2 +- .../list/views/course-id-and-name-cell.js | 2 +- .../course-list/list/views/course-list.js | 2 +- .../apps/course-list/list/views/search.js | 2 +- .../apps/course-list/list/views/table.js | 4 +- .../static/apps/learners/app/controller.js | 3 +- .../static/apps/learners/app/learners-main.js | 6 +- .../learners/detail/views/engagement-table.js | 2 +- .../detail/views/engagement-timeline.js | 2 +- .../learners/detail/views/learner-detail.js | 6 +- .../learners/detail/views/learner-names.js | 2 +- .../learners/detail/views/learner-return.js | 2 +- .../detail/views/learner-summary-field.js | 2 +- .../learners/roster/views/active-filters.js | 2 +- .../apps/learners/roster/views/controls.js | 2 +- .../roster/views/name-username-cell.js | 2 +- .../apps/learners/roster/views/roster.js | 12 +- .../apps/learners/roster/views/search.js | 2 +- .../learners/roster/views/spec/roster-spec.js | 4 +- .../apps/learners/roster/views/table.js | 2 +- .../static/js/application-main.js | 13 +- analytics_dashboard/static/js/common.js | 8 +- analytics_dashboard/static/js/config.js | 125 ------ .../static/js/engagement-content-main.js | 2 +- .../js/engagement-video-content-main.js | 2 +- .../js/engagement-video-timeline-main.js | 4 +- .../static/js/engagement-videos-main.js | 2 +- .../static/js/enrollment-activity-main.js | 6 +- .../js/enrollment-demographics-age-main.js | 2 +- .../enrollment-demographics-education-main.js | 2 +- .../js/enrollment-demographics-gender-main.js | 2 +- .../static/js/enrollment-geography-main.js | 2 +- .../static/js/load/init-models.js | 1 + .../static/js/load/init-tooltips.js | 3 +- .../static/js/load/init-tracking.js | 1 + .../performance-answer-distribution-main.js | 2 +- .../static/js/performance-content-main.js | 2 +- ...formance-learning-outcomes-content-main.js | 2 +- ...formance-learning-outcomes-section-main.js | 2 +- .../static/js/performance-problems-main.js | 2 +- .../static/js/test/browser-shims/gettext.js | 5 + .../static/js/test/browser-shims/ngettext.js | 9 + .../static/js/test/spec-runner.html | 15 - .../static/js/test/spec-runner.js | 76 ---- .../static/js/utils/globalization.js | 8 +- .../static/js/views/data-table-view.js | 7 +- .../static/js/views/world-map-view.js | 3 +- .../static/sass/_bootstrap-components.scss | 78 ++-- .../static/sass/_config-variables.scss | 2 +- .../static/sass/global/_fonts.scss | 2 +- .../static/sass/style-application.scss | 17 +- .../vendor/dataTables/dataTables.bootstrap.js | 186 --------- .../dataTables/dataTables.bootstrap.scss | 283 ------------- .../dataTables/dataTables.fontAwesome.scss | 156 ------- analytics_dashboard/static/vendor/domReady.js | 129 ------ analytics_dashboard/templates/base.html | 23 +- .../templates/base_dashboard.html | 20 - bower-post-install.sh | 33 -- bower.json | 51 --- build.js | 88 ---- gulpfile.js | 39 +- karma.conf.js | 146 +++++-- npm-post-install.sh | 64 +++ package.json | 83 +++- requirements/base.txt | 3 +- setup.cfg | 2 +- webpack.common.config.js | 139 +++++++ webpack.config.js | 97 +++++ webpack.prod.config.js | 95 +++++ 128 files changed, 1512 insertions(+), 2418 deletions(-) create mode 100644 .es6-eslintrc.json delete mode 100644 analytics_dashboard/django_rjs/__init__.py delete mode 100644 analytics_dashboard/django_rjs/models.py delete mode 100644 analytics_dashboard/django_rjs/templatetags/__init__.py delete mode 100644 analytics_dashboard/django_rjs/templatetags/rjs.py delete mode 100644 analytics_dashboard/django_rjs/tests.py create mode 120000 analytics_dashboard/static/apps/course-list/app/.eslintrc.json delete mode 100644 analytics_dashboard/static/js/config.js create mode 100644 analytics_dashboard/static/js/test/browser-shims/gettext.js create mode 100644 analytics_dashboard/static/js/test/browser-shims/ngettext.js delete mode 100644 analytics_dashboard/static/js/test/spec-runner.html delete mode 100644 analytics_dashboard/static/js/test/spec-runner.js delete mode 100644 analytics_dashboard/static/vendor/dataTables/dataTables.bootstrap.js delete mode 100644 analytics_dashboard/static/vendor/dataTables/dataTables.bootstrap.scss delete mode 100644 analytics_dashboard/static/vendor/dataTables/dataTables.fontAwesome.scss delete mode 100644 analytics_dashboard/static/vendor/domReady.js delete mode 100755 bower-post-install.sh delete mode 100644 bower.json delete mode 100644 build.js create mode 100755 npm-post-install.sh create mode 100644 webpack.common.config.js create mode 100644 webpack.config.js create mode 100644 webpack.prod.config.js diff --git a/.es6-eslintrc.json b/.es6-eslintrc.json new file mode 100644 index 000000000..3a2fe0a87 --- /dev/null +++ b/.es6-eslintrc.json @@ -0,0 +1,27 @@ +{ + "extends": "eslint-config-edx", + "parser": "babel-eslint", + "parserOptions": { + "sourceType": "module", + "allowImportExportEverywhere": true, + "plugins": ["syntax-dynamic-import"] + }, + "plugins": [ + "import" + ], + "globals": { + "gettext": true, // added by django for i18n + "setFixtures": true, // added globally by jasmine-jquery + "sinon": true, // used for mocking + "requirejs": true + }, + "settings": { + "import/resolver": "webpack" + }, + "rules": { + "import/no-unresolved": ["error", {"amd": true}], + "global-require" : "off" + // global-require turned off due to https://github.com/eslint/eslint/issues/4812. Fixed in v3.1.1 + // consider turning rule on after upgrading to ESLint 3.1.1 + } +} diff --git a/.eslintignore b/.eslintignore index 1cc523126..4865452d5 100644 --- a/.eslintignore +++ b/.eslintignore @@ -3,3 +3,4 @@ node_modules/ analytics_dashboard/static/bower_components/ analytics_dashboard/static/dist/ analytics_dashboard/static/vendor/ +analytics_dashboard/static/bundles/ diff --git a/.eslintrc.json b/.eslintrc.json index 36069e9cf..75e2221b7 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,5 +1,8 @@ { - "extends": "eslint-config-edx", + "extends": "eslint-config-edx-es5", + "plugins": [ + "import" + ], "globals": { "gettext": true, // added by django for i18n "setFixtures": true, // added globally by jasmine-jquery @@ -7,24 +10,7 @@ "requirejs": true }, "settings": { - "import/resolver": { - "node": { - "extensions": [".js", ".json"], - "moduleDirectory": ["node_modules", "analytics_dashboard/static", - "analytics_dashboard/static/js"] - } - }, - "import/core-modules": ["jquery", "underscore", "backbone", "backbone.paginator", "backbone.wreqr", - "backbone.babysitter", "backgrid", "backgrid-filter", "backgrid-paginator", "bootstrap", - "bootstrap_accessibility", "models", "collections", "views", "utils", "load", - "datatables", "dataTablesBootstrap", "naturalSort", "d3", "nvd3", "topojson", - "datamaps", "moment", "text", "json", "cldr", "cldr-data", "globalize", "globalization", - "marionette", "uitk", "URI", "IPv6", "punycode", "SecondLevelDomains", "learners", - "axe-core", "sinon", "nprogress", - "vendor/domReady!", "globalize/number", "boot", - "uitk/disclosure/disclosure-view", - "json!cldr-data/supplemental/likelySubtags.json", - "json!cldr-data/supplemental/numberingSystems.json"] + "import/resolver": "webpack" }, "rules": { "import/no-unresolved": ["error", {"amd": true}], diff --git a/.gitignore b/.gitignore index 7043ec7c5..ad1faf2d2 100644 --- a/.gitignore +++ b/.gitignore @@ -43,6 +43,7 @@ assets/ node_modules/ analytics_dashboard/static/bower_components/ analytics_dashboard/static/dist/ +analytics_dashboard/static/bundles/ .coverage nosetests.xml reports/ @@ -63,3 +64,6 @@ conf/locale/en/LC_MESSAGES/*.mo conf/locale/fake*/LC_MESSAGES/*.po conf/locale/fake*/LC_MESSAGES/*.mo conf/locale/messages.mo + +# Webpack +webpack-stats.json diff --git a/.travis/run_tests.sh b/.travis/run_tests.sh index 55c9f05d4..426c01008 100755 --- a/.travis/run_tests.sh +++ b/.travis/run_tests.sh @@ -15,10 +15,6 @@ ln -s /opt/firefox/firefox /usr/bin/firefox cd /edx/app/insights/edx_analytics_dashboard export PATH=$PATH:$PWD/node_modules/.bin -# Make it so bower can run without sudo. -# https://github.com/GeoNode/geonode/pull/1070 -echo '{ "allow_root": true }' > /root/.bowerrc - # Output node.js version node --version npm --version diff --git a/Makefile b/Makefile index 7a16753bd..64dafd745 100644 --- a/Makefile +++ b/Makefile @@ -14,8 +14,7 @@ requirements.py: pip install -q -r requirements/base.txt --exists-action w requirements.js: - npm install - $(NODE_BIN)/bower install + npm install --unsafe-perm test.requirements: requirements pip install -q -r requirements/test.txt --exists-action w @@ -35,8 +34,8 @@ test_python_no_compress: clean --cover-package=analytics_dashboard --cover-package=common --cover-branches --cover-html --cover-html-dir=$(COVERAGE)/html/ \ --with-ignore-docstrings --cover-xml --cover-xml-file=$(COVERAGE)/coverage.xml -test_compress: static_no_compress - python manage.py compress --settings=analytics_dashboard.settings.test +test_compress: static + # No longer does anything. Kept for legacy support. test_python: test_compress test_python_no_compress @@ -79,7 +78,7 @@ validate_python: test.requirements test_python quality #FIXME validate_js: requirements.js validate_js: $(NODE_BIN)/gulp test - $(NODE_BIN)/gulp lint + npm run lint -s validate: validate_python validate_js @@ -94,8 +93,8 @@ compile_translations: # creates the source django & djangojs files extract_translations: - cd analytics_dashboard && python ../manage.py makemessages -l en -v1 --ignore="docs/*" --ignore="src/*" --ignore="i18n/*" --ignore="assets/*" -d django - cd analytics_dashboard && python ../manage.py makemessages -l en -v1 --ignore="docs/*" --ignore="src/*" --ignore="i18n/*" --ignore="assets/*" -d djangojs + cd analytics_dashboard && python ../manage.py makemessages -l en -v1 --ignore="docs/*" --ignore="src/*" --ignore="i18n/*" --ignore="assets/*" --ignore="static/bundles/*" -d django + cd analytics_dashboard && python ../manage.py makemessages -l en -v1 --ignore="docs/*" --ignore="src/*" --ignore="i18n/*" --ignore="assets/*" --ignore="static/bundles/*" -d djangojs dummy_translations: cd analytics_dashboard && i18n_tool dummy -v @@ -115,10 +114,11 @@ detect_changed_source_translations: validate_translations: extract_translations compile_translations detect_changed_source_translations cd analytics_dashboard && i18n_tool validate -static_no_compress: - $(NODE_BIN)/r.js -o build.js - # collectstatic creates way too much output so silence it with verbosity=0 - python manage.py collectstatic --noinput -v 0 +static_no_compress: static + # No longer does anything. Kept for legacy support. -static: static_no_compress - python manage.py compress +static: + $(NODE_BIN)/webpack --config webpack.prod.config.js + # collectstatic creates way too much output with the cldr-data directory output so silence that directory + echo "Running collectstatic while silencing cldr-data/main/* ..." + python manage.py collectstatic --noinput | sed -n '/.*node_modules\/cldr-data\/main\/.*/!p' diff --git a/README.md b/README.md index 7ea2f47b1..d460eb30b 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ Prerequisites Getting Started --------------- 1. Get the code (e.g. clone the repository). -2. Install the Python/Node/Bower requirements: +2. Install the Python/Node requirements: $ make develop @@ -23,9 +23,19 @@ Getting Started $ make migrate -4. Run the server: +4. Run the webpack-dev-server: - $ ./manage.py runserver 0.0.0.0:9000 + $ npm start + +If you plan on running the Django development server on a different port or +host, make sure to set the `DJANGO_DEV_SERVER` environmental variable. For +example: + + $ DJANGO_DEV_SERVER='http://localhost:9000' npm start + +5. In a separate terminal run the Django development server: + + $ ./manage.py runserver 0.0.0.0:8110 By default the Django Default Toolbar is disabled. To enable it set the environmental variable ENABLE_DJANGO_TOOLBAR. @@ -33,6 +43,8 @@ Alternatively, you can launch the server using: $ ENABLE_DJANGO_TOOLBAR=1 ./manage.py runserver +Visit http://localhost:9000 in your browser and then login through the LMS to +access Insights (see **Authentication & Authorization** below for more details). Site-Wide Announcements ----------------------- @@ -139,16 +151,34 @@ Note that only the following files (for each language) should be committed to th Asset Pipeline -------------- -Static files are managed via [django-compressor](http://django-compressor.readthedocs.org/) and [RequireJS](http://requirejs.org/). -RequireJS (and r.js) are used to manage JavaScript dependencies. django-compressor compiles SASS, minifies JavaScript ( -using [Closure Compiler](https://developers.google.com/closure/compiler/)), and handles naming files to facilitate -cache busting during deployment. +Static files are managed via [webpack](https://webpack.js.org/). + +To run the webpack-dev-server, which will watch for changes to static files +(`.js`, `.css`, `.sass`, `.underscore`, etc. files) and incrementally recompile +webpack bundles and try to hot-reload them in your browser, run this in a +terminal: + + $ npm start + +Alternatively, you can compile production webpack bundles by running (runs +webpack using the prod config and then exits): + + $ make static + +Before committing new JavaScript, make sure it conforms to our style guide by +running [eslint](http://eslint.org/), and fixing any errors. + + $ npm run lint -s + +You can also try automatically fixing the errors and applying an additional +level of standardized formatting with +[prettier](https://github.com/prettier/prettier) by running +[prettier-eslint](https://github.com/prettier/prettier-eslint). -Both tools should operate seamlessly in a local development environment. When deploying to production, call -`make static` to compile all static assets and move them to the proper location to be served. + $ npm run format -When creating new pages that utilize RequireJS dependencies, remember to use the `static_rjs` templatetag to load -the script, and to add a new module to `build.js`. +Note: this will only format a subset of the JavaScript, we haven't converted the +formatting of all of our files yet. Edit the directory list in `package.json`. Theming and Branding -------------------- diff --git a/analytics_dashboard/conf/locale/en/LC_MESSAGES/django.mo b/analytics_dashboard/conf/locale/en/LC_MESSAGES/django.mo index 1214a5482ed96d752728d42de97fbe3326292724..67fa05f4502f53c6b07f92867f318fc4c6caf131 100644 GIT binary patch delta 18 Zcmeyx^owaiKZlW_f}w?#vC+ioj{!h12DShI delta 18 Zcmeyx^owaiKZk+2f}xR>vB|{gj{!h52DShI diff --git a/analytics_dashboard/conf/locale/en/LC_MESSAGES/django.po b/analytics_dashboard/conf/locale/en/LC_MESSAGES/django.po index 090fcc418..7826cf286 100644 --- a/analytics_dashboard/conf/locale/en/LC_MESSAGES/django.po +++ b/analytics_dashboard/conf/locale/en/LC_MESSAGES/django.po @@ -3,11 +3,12 @@ # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # +#, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-06-07 12:34-0400\n" +"POT-Creation-Date: 2017-06-21 18:32-0400\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -28,7 +29,7 @@ msgstr "" msgid "Insights to help course teams improve courses." msgstr "" -#: core/templates/core/landing.html:42 courses/views/__init__.py:497 +#: core/templates/core/landing.html:42 courses/views/__init__.py:494 msgid "Who are my learners?" msgstr "" @@ -38,8 +39,8 @@ msgstr "" #: core/templates/core/landing.html:44 #: courses/templates/courses/enrollment_geography.html:28 -#: courses/tests/test_views/__init__.py:219 courses/views/__init__.py:293 -#: courses/views/__init__.py:495 +#: courses/tests/test_views/__init__.py:220 courses/views/__init__.py:291 +#: courses/views/__init__.py:492 msgid "Enrollment" msgstr "" @@ -54,8 +55,8 @@ msgid "" msgstr "" #: core/templates/core/landing.html:49 -#: courses/tests/test_views/test_engagement.py:44 -#: courses/views/__init__.py:304 courses/views/__init__.py:554 +#: courses/tests/test_views/test_engagement.py:44 courses/views/__init__.py:302 +#: courses/views/__init__.py:551 msgid "Engagement" msgstr "" @@ -69,7 +70,7 @@ msgstr "" #: core/templates/core/landing.html:54 #: courses/tests/test_views/test_performance.py:48 -#: courses/views/__init__.py:315 courses/views/__init__.py:634 +#: courses/views/__init__.py:313 courses/views/__init__.py:631 msgid "Performance" msgstr "" @@ -134,7 +135,7 @@ msgstr "" #. Translators: Unknown gender #. Translators: This describes the learner's education level. #: courses/presenters/enrollment.py:28 courses/presenters/enrollment.py:63 -#: courses/presenters/enrollment.py:460 +#: courses/presenters/enrollment.py:459 msgid "Unknown" msgstr "" @@ -318,7 +319,7 @@ msgstr "" #: courses/templates/courses/engagement_content.html:96 #: courses/templates/courses/engagement_video_timeline.html:87 #: courses/templates/courses/enrollment_activity.html:106 -#: courses/templates/courses/enrollment_demographics_age.html:108 +#: courses/templates/courses/enrollment_demographics_age.html:109 #: courses/templates/courses/enrollment_demographics_education.html:100 #: courses/templates/courses/enrollment_demographics_gender.html:65 #: courses/templates/courses/enrollment_geography.html:97 @@ -339,12 +340,12 @@ msgid "Weekly Learner Engagement" msgstr "" #: courses/templates/courses/engagement_content.html:24 -#: courses/views/__init__.py:559 +#: courses/views/__init__.py:556 msgid "How many learners are interacting with my course?" msgstr "" #: courses/templates/courses/engagement_content.html:31 -#: courses/views/__init__.py:327 courses/views/__init__.py:642 +#: courses/views/__init__.py:325 courses/views/__init__.py:639 #: courses/views/learners.py:20 msgid "Learners" msgstr "" @@ -430,7 +431,7 @@ msgid "" msgstr "" #: courses/templates/courses/engagement_video_course.html:5 -#: courses/views/__init__.py:572 +#: courses/views/__init__.py:569 msgid "How did learners interact with course videos?" msgstr "" @@ -501,7 +502,7 @@ msgid "Daily Learner Enrollment" msgstr "" #: courses/templates/courses/enrollment_activity.html:25 -#: courses/views/__init__.py:500 +#: courses/views/__init__.py:497 msgid "How many learners are in my course?" msgstr "" @@ -522,7 +523,7 @@ msgstr "" #. Translators: This is a label to identify the number of learners who ever enrolled in the course. #. Translators: This is a label to identify the maximum number of learners ever in all of the instructor's courses. #: courses/templates/courses/enrollment_activity.html:59 -#: courses/templates/courses/index.html:25 +#: courses/templates/courses/index.html:26 msgid "Total Enrollment" msgstr "" @@ -534,7 +535,7 @@ msgstr "" #. Translators: This is a label to identify current learner enrollment. #. Translators: This is a label to identify the current number of learners in all of the instructor's courses. #: courses/templates/courses/enrollment_activity.html:68 -#: courses/templates/courses/index.html:32 +#: courses/templates/courses/index.html:33 msgid "Current Enrollment" msgstr "" @@ -546,7 +547,7 @@ msgstr "" #. Translators: This is a label indicating the change in the number of learners enrolled in a course since the previous week. #. Translators: This is a label to identify the change in the number of learners in all of the instructor's courses in the last week. #: courses/templates/courses/enrollment_activity.html:76 -#: courses/templates/courses/index.html:39 +#: courses/templates/courses/index.html:40 msgid "Change in Last Week" msgstr "" @@ -557,7 +558,7 @@ msgstr "" #. Translators: This is a label to identify enrollment of learners on the verified track. #. Translators: This is a label to identify the current number of verified learners in all of the instructor's courses. #: courses/templates/courses/enrollment_activity.html:84 -#: courses/templates/courses/index.html:46 +#: courses/templates/courses/index.html:47 msgid "Verified Enrollment" msgstr "" @@ -570,78 +571,78 @@ msgstr "" #: courses/templates/courses/enrollment_activity.html:99 #: courses/templates/courses/enrollment_activity.html:107 -#: courses/templates/courses/enrollment_demographics_age.html:109 +#: courses/templates/courses/enrollment_demographics_age.html:110 #: courses/templates/courses/enrollment_demographics_education.html:101 #: courses/templates/courses/enrollment_demographics_gender.html:66 msgid "Enrollment Over Time" msgstr "" -#: courses/templates/courses/enrollment_demographics_age.html:25 -#: courses/views/__init__.py:510 +#: courses/templates/courses/enrollment_demographics_age.html:26 +#: courses/views/__init__.py:507 msgid "How old are my learners?" msgstr "" #. Translators: Maintain the double percentage symbols (%%) but move them to the appropriate location (before/after the value placeholder) for your language. -#: courses/templates/courses/enrollment_demographics_age.html:38 +#: courses/templates/courses/enrollment_demographics_age.html:39 #, python-format msgid "" "This age histogram presents data computed for the %(value)s%% of enrolled " "learners who provided a year of birth." msgstr "" -#: courses/templates/courses/enrollment_demographics_age.html:55 +#: courses/templates/courses/enrollment_demographics_age.html:56 msgid "Age Metrics" msgstr "" #. Translators: This is a label to identify the median age of enrolled learners. -#: courses/templates/courses/enrollment_demographics_age.html:64 +#: courses/templates/courses/enrollment_demographics_age.html:65 msgid "Median Learner Age" msgstr "" -#: courses/templates/courses/enrollment_demographics_age.html:65 +#: courses/templates/courses/enrollment_demographics_age.html:66 msgid "" "The midpoint of the learner ages, computed from the provided year of birth." msgstr "" #. Translators: This is a label to identify the number of learners in the age range. -#: courses/templates/courses/enrollment_demographics_age.html:71 +#: courses/templates/courses/enrollment_demographics_age.html:72 msgid "Learners 25 and Under" msgstr "" -#: courses/templates/courses/enrollment_demographics_age.html:72 +#: courses/templates/courses/enrollment_demographics_age.html:73 msgid "" "The percentage of learners aged 25 years or younger (of those who provided a " "year of birth)." msgstr "" #. Translators: This is a label to identify the number of learners in the age range. -#: courses/templates/courses/enrollment_demographics_age.html:78 +#: courses/templates/courses/enrollment_demographics_age.html:79 msgid "Learners 26 to 40" msgstr "" -#: courses/templates/courses/enrollment_demographics_age.html:79 +#: courses/templates/courses/enrollment_demographics_age.html:80 msgid "" "The percentage of learners aged from 26 to 40 years (of those who provided a " "year of birth)." msgstr "" #. Translators: This is a label to identify the number of learners in the age range. -#: courses/templates/courses/enrollment_demographics_age.html:85 +#: courses/templates/courses/enrollment_demographics_age.html:86 msgid "Learners 41 and Over" msgstr "" -#: courses/templates/courses/enrollment_demographics_age.html:86 +#: courses/templates/courses/enrollment_demographics_age.html:87 msgid "" "The percentage of learners aged 41 years or older (of those who provided a " "year of birth)." msgstr "" -#: courses/templates/courses/enrollment_demographics_age.html:100 +#: courses/templates/courses/enrollment_demographics_age.html:101 msgid "Age Breakdown" msgstr "" #: courses/templates/courses/enrollment_demographics_education.html:25 -#: courses/views/__init__.py:520 +#: courses/views/__init__.py:517 msgid "What level of education do my learners have?" msgstr "" @@ -696,7 +697,7 @@ msgid "Educational Breakdown" msgstr "" #: courses/templates/courses/enrollment_demographics_gender.html:25 -#: courses/views/__init__.py:530 +#: courses/views/__init__.py:527 msgid "What is the learner gender breakdown?" msgstr "" @@ -717,7 +718,7 @@ msgid "Geographic Distribution" msgstr "" #: courses/templates/courses/enrollment_geography.html:22 -#: courses/views/__init__.py:540 +#: courses/views/__init__.py:537 msgid "Where are my learners?" msgstr "" @@ -777,13 +778,13 @@ msgid "Geographic Breakdown" msgstr "" #. Translators: Application here refers to the web site/application being used (e.g. the dashboard). -#: courses/templates/courses/home.html:11 templates/base_dashboard.html:30 +#: courses/templates/courses/home.html:11 templates/base_dashboard.html:28 #: templates/footer.html:5 templates/lens-navigation.html:9 msgid "Application" msgstr "" #. Translators: This refers to the homepage of a course. -#: courses/templates/courses/home.html:15 courses/views/__init__.py:489 +#: courses/templates/courses/home.html:15 courses/views/__init__.py:486 #: templates/lens-navigation.html:20 msgid "Course Home" msgstr "" @@ -812,27 +813,27 @@ msgstr "" msgid "External Tools" msgstr "" -#: courses/templates/courses/index.html:9 courses/views/course_summaries.py:32 +#: courses/templates/courses/index.html:10 courses/views/course_summaries.py:32 msgid "Courses" msgstr "" -#: courses/templates/courses/index.html:16 +#: courses/templates/courses/index.html:17 msgid "Across all your courses:" msgstr "" -#: courses/templates/courses/index.html:26 +#: courses/templates/courses/index.html:27 msgid "Total enrollments across all of your courses." msgstr "" -#: courses/templates/courses/index.html:33 +#: courses/templates/courses/index.html:34 msgid "Current enrollments across all of your courses." msgstr "" -#: courses/templates/courses/index.html:40 +#: courses/templates/courses/index.html:41 msgid "Total change in enrollment last week across all of your courses." msgstr "" -#: courses/templates/courses/index.html:47 +#: courses/templates/courses/index.html:48 msgid "Verified enrollments across all of your courses." msgstr "" @@ -885,7 +886,7 @@ msgstr "" msgid "%(policy_ratio)s%%" msgstr "" -#: courses/templates/courses/performance_graded_content_by_type.html:8 +#: courses/templates/courses/performance_graded_content_by_type.html:7 #, python-format msgid "" "\n" @@ -893,14 +894,14 @@ msgid "" " " msgstr "" -#: courses/templates/courses/performance_graded_content_by_type.html:16 +#: courses/templates/courses/performance_graded_content_by_type.html:15 msgid "" "Each bar shows the average number of correct and incorrect submissions for " "problems in the assignment. Only the last submission from each learner is " "counted." msgstr "" -#: courses/templates/courses/performance_graded_content_by_type.html:20 +#: courses/templates/courses/performance_graded_content_by_type.html:19 msgid "Assignment Submissions" msgstr "" @@ -944,7 +945,7 @@ msgid "How are learners doing on this subsection?" msgstr "" #: courses/templates/courses/performance_ungraded_content.html:7 -#: courses/views/__init__.py:595 +#: courses/views/__init__.py:592 msgid "How are learners doing on ungraded exercises?" msgstr "" @@ -974,142 +975,142 @@ msgstr "" msgid "Try Again" msgstr "" -#: courses/tests/test_views/__init__.py:235 courses/views/__init__.py:502 +#: courses/tests/test_views/__init__.py:236 courses/views/__init__.py:499 #: courses/views/enrollment.py:23 msgid "Activity" msgstr "" -#: courses/tests/test_views/__init__.py:245 courses/views/__init__.py:512 -#: courses/views/__init__.py:522 courses/views/__init__.py:532 +#: courses/tests/test_views/__init__.py:246 courses/views/__init__.py:509 +#: courses/views/__init__.py:519 courses/views/__init__.py:529 #: courses/views/enrollment.py:32 msgid "Demographics" msgstr "" -#: courses/tests/test_views/__init__.py:255 courses/views/__init__.py:542 +#: courses/tests/test_views/__init__.py:256 courses/views/__init__.py:539 #: courses/views/enrollment.py:41 msgid "Geography" msgstr "" -#: courses/tests/test_views/__init__.py:293 courses/views/__init__.py:512 +#: courses/tests/test_views/__init__.py:294 courses/views/__init__.py:509 #: courses/views/enrollment.py:61 msgid "Age" msgstr "" -#: courses/tests/test_views/__init__.py:303 courses/views/__init__.py:522 +#: courses/tests/test_views/__init__.py:304 courses/views/__init__.py:519 #: courses/views/enrollment.py:70 msgid "Education" msgstr "" -#: courses/tests/test_views/__init__.py:313 courses/views/__init__.py:532 +#: courses/tests/test_views/__init__.py:314 courses/views/__init__.py:529 #: courses/views/enrollment.py:79 msgid "Gender" msgstr "" #. Translators: Content as in course content (e.g. things, not the feeling) -#: courses/tests/test_views/test_engagement.py:62 -#: courses/views/__init__.py:561 courses/views/engagement.py:27 +#: courses/tests/test_views/test_engagement.py:62 courses/views/__init__.py:558 +#: courses/views/engagement.py:27 msgid "Content" msgstr "" -#: courses/tests/test_views/test_engagement.py:73 -#: courses/views/__init__.py:574 courses/views/engagement.py:36 +#: courses/tests/test_views/test_engagement.py:73 courses/views/__init__.py:571 +#: courses/views/engagement.py:36 msgid "Videos" msgstr "" #: courses/tests/test_views/test_performance.py:65 -#: courses/views/__init__.py:588 courses/views/performance.py:38 +#: courses/views/__init__.py:585 courses/views/performance.py:38 #: courses/views/performance.py:101 msgid "Graded Content" msgstr "" #: courses/tests/test_views/test_performance.py:76 -#: courses/views/__init__.py:597 courses/views/performance.py:47 +#: courses/views/__init__.py:594 courses/views/performance.py:47 #: courses/views/performance.py:86 msgid "Ungraded Problems" msgstr "" #: courses/tests/test_views/test_performance.py:529 -#: courses/views/__init__.py:609 courses/views/performance.py:65 +#: courses/views/__init__.py:606 courses/views/performance.py:65 msgid "Learning Outcomes" msgstr "" -#: courses/views/__init__.py:556 +#: courses/views/__init__.py:553 msgid "What are learners doing in my course?" msgstr "" -#: courses/views/__init__.py:586 +#: courses/views/__init__.py:583 msgid "How are learners doing on graded course assignments?" msgstr "" -#: courses/views/__init__.py:607 +#: courses/views/__init__.py:604 msgid "What is the breakdown for course learning outcomes?" msgstr "" -#: courses/views/__init__.py:627 +#: courses/views/__init__.py:624 msgid "How are learners responding to questions?" msgstr "" -#: courses/views/__init__.py:629 +#: courses/views/__init__.py:626 msgid "Problem Response Report" msgstr "" -#: courses/views/__init__.py:636 +#: courses/views/__init__.py:633 msgid "How are learners doing on course assignments?" msgstr "" -#: courses/views/__init__.py:644 +#: courses/views/__init__.py:641 msgid "What are individual learners doing?" msgstr "" -#: courses/views/__init__.py:647 +#: courses/views/__init__.py:644 msgid "Who is engaged? Who isn't?" msgstr "" -#: courses/views/__init__.py:649 +#: courses/views/__init__.py:646 msgid "All Learners" msgstr "" #. Translators: 'Course ID' is 'Course Identifier', the unique code that identifies the course -#: courses/views/__init__.py:697 +#: courses/views/__init__.py:694 msgid "Course ID" msgstr "" -#: courses/views/__init__.py:699 +#: courses/views/__init__.py:696 msgid "Course Name" msgstr "" #. Translators: 'In Progress' and 'Ended' refer to whether learners are #. actively using the course or it is over. -#: courses/views/__init__.py:716 +#: courses/views/__init__.py:713 msgid "In Progress" msgstr "" -#: courses/views/__init__.py:716 +#: courses/views/__init__.py:713 msgid "Ended" msgstr "" #. Translators: This refers to a course that has not yet begun. -#: courses/views/__init__.py:719 +#: courses/views/__init__.py:716 msgid "Not Started Yet" msgstr "" -#: courses/views/__init__.py:721 +#: courses/views/__init__.py:718 msgid "Start Date" msgstr "" -#: courses/views/__init__.py:722 +#: courses/views/__init__.py:719 msgid "End Date" msgstr "" -#: courses/views/__init__.py:723 +#: courses/views/__init__.py:720 msgid "Status" msgstr "" -#: courses/views/__init__.py:732 +#: courses/views/__init__.py:729 msgid "Instructor Dashboard" msgstr "" -#: courses/views/__init__.py:737 +#: courses/views/__init__.py:734 msgid "Courseware" msgstr "" @@ -1253,11 +1254,11 @@ msgstr "" msgid "Performance: Learning Outcomes" msgstr "" -#: settings/base.py:455 +#: settings/base.py:419 msgid "Terms of Service" msgstr "" -#: settings/base.py:456 +#: settings/base.py:420 msgid "Privacy Policy" msgstr "" @@ -1333,19 +1334,19 @@ msgid "" " reserved. " msgstr "" -#: templates/header.html:14 +#: templates/header.html:13 msgid "Toggle navigation" msgstr "" -#: templates/header.html:41 +#: templates/header.html:40 msgid "Help" msgstr "" -#: templates/header.html:47 +#: templates/header.html:46 msgid "Menu for" msgstr "" -#: templates/header.html:61 +#: templates/header.html:60 msgid "Logout" msgstr "" diff --git a/analytics_dashboard/conf/locale/en/LC_MESSAGES/djangojs.mo b/analytics_dashboard/conf/locale/en/LC_MESSAGES/djangojs.mo index 1214a5482ed96d752728d42de97fbe3326292724..67fa05f4502f53c6b07f92867f318fc4c6caf131 100644 GIT binary patch delta 18 Zcmeyx^owaiKZlW_f}w?#vC+ioj{!h12DShI delta 18 Zcmeyx^owaiKZk+2f}xR>vB|{gj{!h52DShI diff --git a/analytics_dashboard/conf/locale/en/LC_MESSAGES/djangojs.po b/analytics_dashboard/conf/locale/en/LC_MESSAGES/djangojs.po index 80f1237b5..02d9c1856 100644 --- a/analytics_dashboard/conf/locale/en/LC_MESSAGES/djangojs.po +++ b/analytics_dashboard/conf/locale/en/LC_MESSAGES/djangojs.po @@ -3,11 +3,12 @@ # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # +#, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-06-07 12:34-0400\n" +"POT-Creation-Date: 2017-06-21 18:32-0400\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,269 +18,202 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" #: static/apps/components/download/views/download-data.js:25 -#: static/dist/apps/course-list/app/course-list-main.js:6492 -#: static/dist/apps/learners/app/learners-main.js:31532 msgid "Download CSV" msgstr "" #: static/apps/components/download/views/download-data.js:26 -#: static/dist/apps/course-list/app/course-list-main.js:6493 -#: static/dist/apps/learners/app/learners-main.js:31533 msgid "Download search results to CSV" msgstr "" +#. Translators: "All" refers to viewing all items (e.g. all courses). #: static/apps/components/filter/views/drop-down-filter.js:31 -#: static/dist/apps/learners/app/learners-main.js:32577 msgid "All" msgstr "" +#. Translators: 'name' here refers to the name of the filter, while 'count' refers to the number of items belonging to that filter. #: static/apps/components/filter/views/filter.js:90 -#: static/dist/apps/course-list/app/course-list-main.js:2062 -#: static/dist/apps/learners/app/learners-main.js:32454 msgid "<%= name %> (<%= count %>)" msgstr "" +#. Translators: this is a label describing a filter selection that the user initiated. #: static/apps/components/generic-list/list/views/active-filters.js:62 -#: static/dist/apps/course-list/app/course-list-main.js:1913 -#: static/dist/apps/learners/app/learners-main.js:31394 msgid "<%= filterDisplayName %>: <%= filterVal %>" msgstr "" #: static/apps/components/generic-list/list/views/active-filters.js:87 -#: static/dist/apps/course-list/app/course-list-main.js:1938 -#: static/dist/apps/learners/app/learners-main.js:31419 msgid "Filters In Use:" msgstr "" #: static/apps/components/generic-list/list/views/active-filters.js:88 -#: static/dist/apps/course-list/app/course-list-main.js:1939 -#: static/dist/apps/learners/app/learners-main.js:31420 msgid "Click to remove this filter" msgstr "" +#. Translators: "Clear" in this context means "remove all of the filters" #: static/apps/components/generic-list/list/views/active-filters.js:90 -#: static/dist/apps/course-list/app/course-list-main.js:1941 -#: static/dist/apps/learners/app/learners-main.js:31422 msgid "Clear" msgstr "" #: static/apps/components/generic-list/list/views/active-filters.js:91 -#: static/dist/apps/course-list/app/course-list-main.js:1942 -#: static/dist/apps/learners/app/learners-main.js:31423 msgid "Click to remove all filters" msgstr "" +#. Translators: "sort ascending" describes the current sort state to the user. #: static/apps/components/generic-list/list/views/base-header-cell.js:71 -#: static/dist/apps/course-list/app/course-list-main.js:5727 -#: static/dist/apps/learners/app/learners-main.js:31691 msgid "sort ascending" msgstr "" +#. Translators: "sort descending" describes the current sort state to the user. #: static/apps/components/generic-list/list/views/base-header-cell.js:73 -#: static/dist/apps/course-list/app/course-list-main.js:5729 -#: static/dist/apps/learners/app/learners-main.js:31693 msgid "sort descending" msgstr "" +#. Translators: "click to sort" tells the user that they can click this link to sort by the current field. #: static/apps/components/generic-list/list/views/base-header-cell.js:76 -#: static/dist/apps/course-list/app/course-list-main.js:5732 -#: static/dist/apps/learners/app/learners-main.js:31696 msgid "click to sort" msgstr "" -#: static/apps/components/generic-list/list/views/list.js:54 -#: static/dist/apps/course-list/app/course-list-main.js:6667 -#: static/dist/apps/learners/app/learners-main.js:31246 +#: static/apps/components/generic-list/list/views/list.js:51 msgid "Results" msgstr "" +#. Translators: 'count' refers to the number of results contained in the table. #: static/apps/components/generic-list/list/views/num-results.js:30 -#: static/dist/apps/course-list/app/course-list-main.js:1830 -#: static/dist/apps/learners/app/learners-main.js:31311 msgid "Number of results: <%= count %>" msgstr "" #: static/apps/components/generic-list/list/views/paging-footer.js:70 -#: static/dist/apps/course-list/app/course-list-main.js:5995 -#: static/dist/apps/learners/app/learners-main.js:31959 msgid "first page" msgstr "" #: static/apps/components/generic-list/list/views/paging-footer.js:72 -#: static/dist/apps/course-list/app/course-list-main.js:5997 -#: static/dist/apps/learners/app/learners-main.js:31961 msgid "previous page" msgstr "" #: static/apps/components/generic-list/list/views/paging-footer.js:74 -#: static/dist/apps/course-list/app/course-list-main.js:5999 -#: static/dist/apps/learners/app/learners-main.js:31963 msgid "next page" msgstr "" #: static/apps/components/generic-list/list/views/paging-footer.js:76 -#: static/dist/apps/course-list/app/course-list-main.js:6001 -#: static/dist/apps/learners/app/learners-main.js:31965 msgid "last page" msgstr "" #: static/apps/components/generic-list/list/views/paging-footer.js:78 -#: static/dist/apps/course-list/app/course-list-main.js:6003 -#: static/dist/apps/learners/app/learners-main.js:31967 msgid "page" msgstr "" +#. Translators: describes the state of a pagination button as not clickable #: static/apps/components/generic-list/list/views/paging-footer.js:88 -#: static/dist/apps/course-list/app/course-list-main.js:6013 -#: static/dist/apps/learners/app/learners-main.js:31977 msgid "disabled" msgstr "" +#. Translators: describes a pagination button as representing the current page #: static/apps/components/generic-list/list/views/paging-footer.js:91 -#: static/dist/apps/course-list/app/course-list-main.js:6016 -#: static/dist/apps/learners/app/learners-main.js:31980 msgid "active" msgstr "" #: static/apps/components/generic-list/list/views/table.js:27 -#: static/dist/apps/course-list/app/course-list-main.js:6067 -#: static/dist/apps/learners/app/learners-main.js:32031 msgid "Generic List" msgstr "" +#. Translators: This message indicates content is loading in the page. #: static/apps/components/loading/views/loading-view.js:20 -#: static/dist/apps/learners/app/learners-main.js:27752 msgid "Loading..." msgstr "" +#. Translators: "504" is a number representing a server error, so please leave it as "504". #: static/apps/components/utils/utils.js:61 -#: static/dist/apps/course-list/app/course-list-main.js:1078 -#: static/dist/apps/learners/app/learners-main.js:566 msgid "504: Server error" msgstr "" #: static/apps/components/utils/utils.js:63 -#: static/dist/apps/course-list/app/course-list-main.js:1080 -#: static/dist/apps/learners/app/learners-main.js:568 msgid "" "Processing your request took too long to complete. Reload the page to try " "again." msgstr "" #: static/apps/components/utils/utils.js:67 -#: static/dist/apps/course-list/app/course-list-main.js:1084 -#: static/dist/apps/learners/app/learners-main.js:572 msgid "Server error" msgstr "" #: static/apps/components/utils/utils.js:68 #: static/apps/components/utils/utils.js:80 -#: static/dist/apps/course-list/app/course-list-main.js:1085 -#: static/dist/apps/course-list/app/course-list-main.js:1097 -#: static/dist/apps/learners/app/learners-main.js:573 -#: static/dist/apps/learners/app/learners-main.js:585 msgid "Your request could not be processed. Reload the page to try again." msgstr "" #: static/apps/components/utils/utils.js:79 -#: static/dist/apps/course-list/app/course-list-main.js:1096 -#: static/dist/apps/learners/app/learners-main.js:584 msgid "Network error" msgstr "" -#: static/apps/course-list/app/app.js:45 -#: static/dist/apps/course-list/app/course-list-main.js:7215 +#: static/apps/course-list/app/app.js:39 msgid "Instructor-Paced" msgstr "" -#: static/apps/course-list/app/app.js:46 -#: static/dist/apps/course-list/app/course-list-main.js:7216 +#: static/apps/course-list/app/app.js:40 msgid "Self-Paced" msgstr "" -#: static/apps/course-list/app/app.js:49 -#: static/dist/apps/course-list/app/course-list-main.js:7219 +#: static/apps/course-list/app/app.js:43 msgid "Upcoming" msgstr "" -#: static/apps/course-list/app/app.js:50 -#: static/dist/apps/course-list/app/course-list-main.js:7220 +#: static/apps/course-list/app/app.js:44 msgid "Current" msgstr "" -#: static/apps/course-list/app/app.js:51 -#: static/dist/apps/course-list/app/course-list-main.js:7221 +#: static/apps/course-list/app/app.js:45 msgid "Archived" msgstr "" -#: static/apps/course-list/app/app.js:52 -#: static/dist/apps/course-list/app/course-list-main.js:7222 +#: static/apps/course-list/app/app.js:46 msgid "Unknown" msgstr "" #: static/apps/course-list/app/controller.js:48 -#: static/apps/course-list/app/controller.js:107 -#: static/dist/apps/course-list/app/course-list-main.js:6822 -#: static/dist/apps/course-list/app/course-list-main.js:6881 +#: static/apps/course-list/app/controller.js:108 msgid "Course List" msgstr "" -#: static/apps/course-list/app/controller.js:97 -#: static/apps/learners/app/controller.js:94 -#: static/dist/apps/course-list/app/course-list-main.js:6871 -#: static/dist/apps/learners/app/learners-main.js:33082 +#: static/apps/course-list/app/controller.js:98 +#: static/apps/learners/app/controller.js:96 msgid "Invalid Parameters" msgstr "" -#: static/apps/course-list/app/controller.js:98 -#: static/dist/apps/course-list/app/course-list-main.js:6872 +#: static/apps/course-list/app/controller.js:99 msgid "Sorry, we couldn't find any courses that matched that query." msgstr "" -#: static/apps/course-list/app/controller.js:99 -#: static/dist/apps/course-list/app/course-list-main.js:6873 +#: static/apps/course-list/app/controller.js:100 msgid "Return to the Course List page." msgstr "" -#: static/apps/course-list/app/controller.js:124 -#: static/apps/learners/app/controller.js:176 -#: static/dist/apps/course-list/app/course-list-main.js:6898 -#: static/dist/apps/learners/app/learners-main.js:33164 +#: static/apps/course-list/app/controller.js:125 +#: static/apps/learners/app/controller.js:181 msgid "Sorry, we couldn't find the page you're looking for." msgstr "" -#: static/apps/course-list/app/controller.js:127 -#: static/apps/learners/app/controller.js:179 -#: static/dist/apps/course-list/app/course-list-main.js:6901 -#: static/dist/apps/learners/app/learners-main.js:33167 +#: static/apps/course-list/app/controller.js:129 +#: static/apps/learners/app/controller.js:186 msgid "Page Not Found" msgstr "" #: static/apps/course-list/common/collections/course-list.js:77 -#: static/dist/apps/course-list/app/course-list-main.js:1612 msgid "Course Name" msgstr "" #: static/apps/course-list/common/collections/course-list.js:78 -#: static/dist/apps/course-list/app/course-list-main.js:1613 msgid "Start Date" msgstr "" #: static/apps/course-list/common/collections/course-list.js:79 -#: static/dist/apps/course-list/app/course-list-main.js:1614 msgid "End Date" msgstr "" #: static/apps/course-list/common/collections/course-list.js:80 -#: static/dist/apps/course-list/app/course-list-main.js:1615 msgid "Total Enrollment" msgstr "" +#. Translators: The noun count (e.g. number of learners) #: static/apps/course-list/common/collections/course-list.js:81 -#: static/dist/apps/course-list/app/course-list-main.js:1616 -#: static/dist/js/enrollment-activity-main.js:25422 -#: static/dist/js/enrollment-demographics-gender-main.js:25539 -#: static/dist/js/enrollment-geography-main.js:22484 #: static/js/enrollment-activity-main.js:27 #: static/js/enrollment-demographics-gender-main.js:32 #: static/js/enrollment-geography-main.js:29 @@ -287,258 +221,200 @@ msgid "Current Enrollment" msgstr "" #: static/apps/course-list/common/collections/course-list.js:82 -#: static/dist/apps/course-list/app/course-list-main.js:1617 msgid "Change Last Week" msgstr "" #: static/apps/course-list/common/collections/course-list.js:83 -#: static/dist/apps/course-list/app/course-list-main.js:1618 msgid "Verified Enrollment" msgstr "" #: static/apps/course-list/common/collections/course-list.js:86 -#: static/dist/apps/course-list/app/course-list-main.js:1621 msgid "Passing Learners" msgstr "" #: static/apps/course-list/common/collections/course-list.js:89 -#: static/dist/apps/course-list/app/course-list-main.js:1624 msgid "Availability" msgstr "" #: static/apps/course-list/common/collections/course-list.js:90 -#: static/dist/apps/course-list/app/course-list-main.js:1625 msgid "Pacing Type" msgstr "" #: static/apps/course-list/common/collections/course-list.js:91 -#: static/dist/apps/course-list/app/course-list-main.js:1626 msgid "Programs" msgstr "" #: static/apps/course-list/list/views/base-header-cell.js:11 -#: static/dist/apps/course-list/app/course-list-main.js:6152 msgid "Course name advertised on edX site." msgstr "" #: static/apps/course-list/list/views/base-header-cell.js:12 -#: static/dist/apps/course-list/app/course-list-main.js:6153 msgid "Start date advertised on edX site." msgstr "" #: static/apps/course-list/list/views/base-header-cell.js:13 -#: static/dist/apps/course-list/app/course-list-main.js:6154 msgid "End date set in edX Studio." msgstr "" #: static/apps/course-list/list/views/base-header-cell.js:14 -#: static/dist/apps/course-list/app/course-list-main.js:6155 msgid "Learners who ever enrolled in the course." msgstr "" #: static/apps/course-list/list/views/base-header-cell.js:15 -#: static/dist/apps/course-list/app/course-list-main.js:6156 msgid "Learners currently enrolled in the course." msgstr "" #: static/apps/course-list/list/views/base-header-cell.js:16 -#: static/dist/apps/course-list/app/course-list-main.js:6157 msgid "Net difference in current enrollment in the last week." msgstr "" #: static/apps/course-list/list/views/base-header-cell.js:18 -#: static/dist/apps/course-list/app/course-list-main.js:6159 msgid "" "Number of currently enrolled learners pursuing a verified certificate of " "achievement." msgstr "" #: static/apps/course-list/list/views/base-header-cell.js:19 -#: static/dist/apps/course-list/app/course-list-main.js:6160 msgid "Learners who have earned a passing grade." msgstr "" #: static/apps/course-list/list/views/controls.js:38 #: static/apps/learners/roster/views/controls.js:39 -#: static/dist/apps/course-list/app/course-list-main.js:5520 -#: static/dist/apps/learners/app/learners-main.js:32781 msgid "Skip to results" msgstr "" #: static/apps/course-list/list/views/controls.js:58 -#: static/dist/apps/course-list/app/course-list-main.js:5540 msgid "Find a course" msgstr "" #: static/apps/course-list/list/views/controls.js:68 -#: static/dist/apps/course-list/app/course-list-main.js:5550 msgid "availability" msgstr "" #: static/apps/course-list/list/views/controls.js:76 -#: static/dist/apps/course-list/app/course-list-main.js:5558 msgid "pacing_type" msgstr "" #: static/apps/course-list/list/views/controls.js:84 -#: static/dist/apps/course-list/app/course-list-main.js:5566 msgid "program_ids" msgstr "" #: static/apps/course-list/list/views/course-list.js:66 -#: static/dist/apps/course-list/app/course-list-main.js:6743 msgid "Download full course list to CSV" msgstr "" #: static/apps/course-list/list/views/course-list.js:91 -#: static/dist/apps/course-list/app/course-list-main.js:6768 msgid "Course list controls" msgstr "" #: static/apps/course-list/list/views/results.js:47 -#: static/dist/apps/course-list/app/course-list-main.js:6440 msgid "No courses matched your criteria." msgstr "" #: static/apps/course-list/list/views/results.js:49 #: static/apps/learners/roster/views/results.js:49 -#: static/dist/apps/course-list/app/course-list-main.js:6442 -#: static/dist/apps/learners/app/learners-main.js:32339 msgid "Try a different search." msgstr "" #: static/apps/course-list/list/views/results.js:52 #: static/apps/learners/roster/views/results.js:52 -#: static/dist/apps/course-list/app/course-list-main.js:6445 -#: static/dist/apps/learners/app/learners-main.js:32342 msgid "Try clearing the filters." msgstr "" #: static/apps/course-list/list/views/results.js:55 -#: static/dist/apps/course-list/app/course-list-main.js:6448 msgid "No course data is currently available for your course." msgstr "" #: static/apps/course-list/list/views/results.js:57 -#: static/dist/apps/course-list/app/course-list-main.js:6450 msgid "" "No courses are enrolled, or course activity data has not yet been processed. " "Data is updated every day, so check back regularly for up-to-date metrics." msgstr "" #: static/apps/course-list/list/views/search.js:71 -#: static/dist/apps/course-list/app/course-list-main.js:5374 msgid "Search courses" msgstr "" #: static/apps/course-list/list/views/search.js:72 #: static/apps/learners/roster/views/search.js:56 -#: static/dist/apps/course-list/app/course-list-main.js:5375 -#: static/dist/apps/learners/app/learners-main.js:32667 msgid "search" msgstr "" #: static/apps/course-list/list/views/search.js:73 #: static/apps/learners/roster/views/search.js:57 -#: static/dist/apps/course-list/app/course-list-main.js:5376 -#: static/dist/apps/learners/app/learners-main.js:32668 msgid "clear search" msgstr "" #: static/apps/course-list/list/views/table.js:48 -#: static/dist/apps/course-list/app/course-list-main.js:6374 msgid "Date not available" msgstr "" #: static/apps/learners/app/controller.js:59 -#: static/dist/apps/learners/app/learners-main.js:33047 msgid "Learner Roster" msgstr "" -#: static/apps/learners/app/controller.js:95 -#: static/dist/apps/learners/app/learners-main.js:33083 +#: static/apps/learners/app/controller.js:97 msgid "Sorry, we couldn't find any learners who matched that query." msgstr "" -#: static/apps/learners/app/controller.js:96 -#: static/dist/apps/learners/app/learners-main.js:33084 +#: static/apps/learners/app/controller.js:98 msgid "Return to the Learners page." msgstr "" -#: static/apps/learners/app/controller.js:104 -#: static/dist/apps/learners/app/learners-main.js:33092 +#: static/apps/learners/app/controller.js:106 msgid "Learners" msgstr "" -#: static/apps/learners/app/controller.js:136 -#: static/dist/apps/learners/app/learners-main.js:33124 +#: static/apps/learners/app/controller.js:141 msgid "Learner Details" msgstr "" #: static/apps/learners/common/collections/learners.js:17 -#: static/dist/apps/learners/app/learners-main.js:2021 msgid "Name (Username)" msgstr "" #: static/apps/learners/common/collections/learners.js:18 -#: static/dist/apps/learners/app/learners-main.js:2022 msgid "Problems Tried" msgstr "" #: static/apps/learners/common/collections/learners.js:19 #: static/apps/learners/detail/views/engagement-table.js:33 #: static/apps/learners/detail/views/engagement-timeline.js:35 -#: static/dist/apps/learners/app/learners-main.js:2023 -#: static/dist/apps/learners/app/learners-main.js:2201 -#: static/dist/apps/learners/app/learners-main.js:27625 msgid "Problems Correct" msgstr "" #: static/apps/learners/common/collections/learners.js:20 -#: static/dist/apps/learners/app/learners-main.js:2024 msgid "Attempts per Problem Correct" msgstr "" #: static/apps/learners/common/collections/learners.js:21 -#: static/dist/apps/learners/app/learners-main.js:2025 -#: static/dist/js/engagement-video-content-main.js:25598 #: static/js/engagement-video-content-main.js:33 msgid "Videos" msgstr "" #: static/apps/learners/common/collections/learners.js:22 -#: static/dist/apps/learners/app/learners-main.js:2026 msgid "Discussions" msgstr "" #: static/apps/learners/common/collections/learners.js:24 -#: static/dist/apps/learners/app/learners-main.js:2028 msgid "Segments" msgstr "" #: static/apps/learners/common/collections/learners.js:25 -#: static/dist/apps/learners/app/learners-main.js:2029 msgid "Segments to Ignore" msgstr "" #: static/apps/learners/common/collections/learners.js:26 #: static/apps/learners/detail/views/learner-detail.js:101 -#: static/dist/apps/learners/app/learners-main.js:2030 -#: static/dist/apps/learners/app/learners-main.js:27879 msgid "Cohort" msgstr "" #: static/apps/learners/common/collections/learners.js:27 -#: static/dist/apps/learners/app/learners-main.js:2031 msgid "Enrollment Mode" msgstr "" #: static/apps/learners/detail/views/engagement-table.js:22 #: static/apps/learners/detail/views/engagement-timeline.js:43 -#: static/dist/apps/learners/app/learners-main.js:2190 -#: static/dist/apps/learners/app/learners-main.js:27633 -#: static/dist/js/enrollment-activity-main.js:25417 -#: static/dist/js/enrollment-demographics-gender-main.js:25538 #: static/js/enrollment-activity-main.js:22 #: static/js/enrollment-demographics-gender-main.js:31 msgid "Date" @@ -546,293 +422,200 @@ msgstr "" #: static/apps/learners/detail/views/engagement-table.js:27 #: static/apps/learners/detail/views/engagement-timeline.js:31 -#: static/dist/apps/learners/app/learners-main.js:2195 -#: static/dist/apps/learners/app/learners-main.js:27621 msgid "Discussion Contributions" msgstr "" #: static/apps/learners/detail/views/engagement-table.js:39 #: static/apps/learners/detail/views/engagement-timeline.js:39 -#: static/dist/apps/learners/app/learners-main.js:2207 -#: static/dist/apps/learners/app/learners-main.js:27629 msgid "Videos Viewed" msgstr "" #: static/apps/learners/detail/views/engagement-timeline.js:47 -#: static/dist/apps/learners/app/learners-main.js:27637 msgid "Value" msgstr "" +#. Translators: e.g. Learner engagement activity #: static/apps/learners/detail/views/learner-detail.js:28 -#: static/dist/apps/learners/app/learners-main.js:27806 msgid "Engagement Activity" msgstr "" #: static/apps/learners/detail/views/learner-detail.js:29 -#: static/dist/apps/learners/app/learners-main.js:27807 msgid "Daily Activity" msgstr "" #: static/apps/learners/detail/views/learner-detail.js:30 -#: static/dist/apps/learners/app/learners-main.js:27808 msgid "Activity Over Time" msgstr "" #: static/apps/learners/detail/views/learner-detail.js:95 -#: static/dist/apps/learners/app/learners-main.js:27873 msgid "Enrollment" msgstr "" #: static/apps/learners/detail/views/learner-detail.js:106 -#: static/dist/apps/learners/app/learners-main.js:27884 msgid "Last Accessed" msgstr "" +#. Translators: 'n/a' means 'not available' #: static/apps/learners/detail/views/learner-detail.js:109 #: static/apps/learners/detail/views/learner-summary-field.js:44 #: static/apps/learners/roster/views/activity-date-range.js:16 -#: static/dist/apps/learners/app/learners-main.js:27718 -#: static/dist/apps/learners/app/learners-main.js:27887 -#: static/dist/apps/learners/app/learners-main.js:31269 msgid "n/a" msgstr "" #: static/apps/learners/detail/views/learner-detail.js:139 -#: static/dist/apps/learners/app/learners-main.js:27917 msgid "Check back daily for up-to-date data." msgstr "" #: static/apps/learners/detail/views/learner-detail.js:149 -#: static/dist/apps/learners/app/learners-main.js:27927 msgid "No course activity data is available for this learner." msgstr "" #: static/apps/learners/detail/views/learner-detail.js:156 -#: static/dist/apps/learners/app/learners-main.js:27934 msgid "No learner data is available." msgstr "" #: static/apps/learners/detail/views/learner-return.js:12 -#: static/dist/apps/learners/app/learners-main.js:32979 msgid "Return to Learners" msgstr "" +#. Translators: this is a label describing a selection that the user initiated. #: static/apps/learners/roster/views/active-filters.js:23 -#: static/dist/apps/learners/app/learners-main.js:31476 msgid "Cohort: <%= filterVal %>" msgstr "" +#. Translators: this is a label describing a selection that the user initiated. #: static/apps/learners/roster/views/active-filters.js:27 -#: static/dist/apps/learners/app/learners-main.js:31480 msgid "Enrollment Track: <%= filterVal %>" msgstr "" #: static/apps/learners/roster/views/active-filters.js:32 -#: static/dist/apps/learners/app/learners-main.js:31485 -#: static/dist/js/engagement-content-main.js:25386 #: static/js/engagement-content-main.js:20 msgid "Active Learners" msgstr "" #: static/apps/learners/roster/views/activity-date-range.js:11 -#: static/dist/apps/learners/app/learners-main.js:31264 msgid "Activity between <%- startDate %> - <%- endDate %>" msgstr "" #: static/apps/learners/roster/views/base-header-cell.js:10 -#: static/dist/apps/learners/app/learners-main.js:32115 msgid "The name and username of this learner. Click to sort by username." msgstr "" #: static/apps/learners/roster/views/base-header-cell.js:11 -#: static/dist/apps/learners/app/learners-main.js:32116 msgid "Number of unique problems this learner attempted." msgstr "" #: static/apps/learners/roster/views/base-header-cell.js:12 -#: static/dist/apps/learners/app/learners-main.js:32117 msgid "Number of unique problems the learner answered correctly." msgstr "" #: static/apps/learners/roster/views/base-header-cell.js:13 -#: static/dist/apps/learners/app/learners-main.js:32118 msgid "Number of unique videos this learner played." msgstr "" #: static/apps/learners/roster/views/base-header-cell.js:15 -#: static/dist/apps/learners/app/learners-main.js:32120 msgid "" "Average number of attempts per correct problem. Learners with a relatively " "high value compared to their peers may be struggling." msgstr "" #: static/apps/learners/roster/views/base-header-cell.js:17 -#: static/dist/apps/learners/app/learners-main.js:32122 msgid "" "Number of contributions by this learner, including posts, responses, and " "comments." msgstr "" #: static/apps/learners/roster/views/controls.js:62 -#: static/dist/apps/learners/app/learners-main.js:32804 msgid "Find a learner" msgstr "" #: static/apps/learners/roster/views/controls.js:75 -#: static/dist/apps/learners/app/learners-main.js:32817 msgid "Cohort Groups" msgstr "" #: static/apps/learners/roster/views/controls.js:73 -#: static/dist/apps/learners/app/learners-main.js:32815 msgid "cohort" msgstr "" #: static/apps/learners/roster/views/controls.js:87 -#: static/dist/apps/learners/app/learners-main.js:32829 msgid "Enrollment Tracks" msgstr "" #: static/apps/learners/roster/views/controls.js:85 -#: static/dist/apps/learners/app/learners-main.js:32827 msgid "enrollment_mode" msgstr "" +#. Translators: inactive meaning that these learners have not interacted with the +#. course recently. #: static/apps/learners/roster/views/controls.js:103 -#: static/dist/apps/learners/app/learners-main.js:32845 msgid "Hide Inactive Learners" msgstr "" #: static/apps/learners/roster/views/controls.js:97 -#: static/dist/apps/learners/app/learners-main.js:32839 msgid "ignore_segments" msgstr "" #: static/apps/learners/roster/views/results.js:47 -#: static/dist/apps/learners/app/learners-main.js:32337 msgid "No learners matched your criteria." msgstr "" #: static/apps/learners/roster/views/results.js:55 -#: static/dist/apps/learners/app/learners-main.js:32345 msgid "No learner data is currently available for your course." msgstr "" #: static/apps/learners/roster/views/results.js:57 -#: static/dist/apps/learners/app/learners-main.js:32347 msgid "" "No learners are enrolled, or course activity data has not yet been " "processed. Data is updated every day, so check back regularly for up-to-date " "metrics." msgstr "" -#: static/apps/learners/roster/views/roster.js:100 -#: static/dist/apps/learners/app/learners-main.js:32958 +#: static/apps/learners/roster/views/roster.js:110 msgid "Learner roster controls" msgstr "" #: static/apps/learners/roster/views/search.js:55 -#: static/dist/apps/learners/app/learners-main.js:32666 msgid "Search learners" msgstr "" +#. Translators: 'N/A' is an abbreviation of "Not Applicable". Please translate accordingly. #: static/apps/learners/roster/views/table.js:40 -#: static/dist/apps/learners/app/learners-main.js:32225 msgid "N/A" msgstr "" -#: static/dist/js/common.js:23183 static/js/utils/utils.js:93 -msgid "..." -msgstr "" - -#: static/dist/js/common.js:39338 -#: static/dist/js/engagement-video-content-main.js:25482 -#: static/dist/js/engagement-videos-main.js:25482 -#: static/dist/js/enrollment-demographics-education-main.js:25482 -#: static/dist/js/enrollment-demographics-gender-main.js:25482 -#: static/dist/js/performance-answer-distribution-main.js:25482 -#: static/dist/js/performance-content-main.js:25482 -#: static/dist/js/performance-learning-outcomes-content-main.js:25482 -#: static/dist/js/performance-learning-outcomes-section-main.js:25482 -#: static/dist/js/performance-problems-main.js:25482 -#: static/js/views/data-table-view.js:198 -#: static/js/views/discrete-bar-view.js:21 -msgid "(empty)" -msgstr "" - -#: static/dist/js/common.js:39355 -#: static/dist/js/performance-answer-distribution-main.js:25522 -#: static/dist/js/performance-answer-distribution-main.js:25550 -#: static/dist/js/performance-learning-outcomes-section-main.js:25576 -#: static/dist/js/performance-learning-outcomes-section-main.js:25602 -#: static/dist/js/performance-problems-main.js:25576 -#: static/js/performance-answer-distribution-main.js:15 -#: static/js/performance-answer-distribution-main.js:43 -#: static/js/performance-learning-outcomes-section-main.js:11 -#: static/js/performance-learning-outcomes-section-main.js:37 -#: static/js/performance-problems-main.js:11 -#: static/js/views/data-table-view.js:215 -msgid "Correct" -msgstr "" - -#: static/dist/js/engagement-content-main.js:25381 #: static/js/engagement-content-main.js:15 msgid "Week Ending" msgstr "" -#: static/dist/js/engagement-content-main.js:25393 #: static/js/engagement-content-main.js:27 msgid "Watched a Video" msgstr "" -#: static/dist/js/engagement-content-main.js:25400 #: static/js/engagement-content-main.js:34 msgid "Tried a Problem" msgstr "" -#: static/dist/js/engagement-content-main.js:25407 #: static/js/engagement-content-main.js:41 msgid "Participated in Discussions" msgstr "" -#: static/dist/js/engagement-content-main.js:25414 #: static/js/engagement-content-main.js:48 msgid "Percent of Current Learners" msgstr "" -#: static/dist/js/engagement-content-main.js:25451 +#. Translators: <%=value%> will be replaced with a date. #: static/js/engagement-content-main.js:85 msgid "Week Ending <%=value%>" msgstr "" -#. Translators: <%=value%> will be replaced by a number followed by a percentage. -#. For example, "400 (29%)" */ -#: static/dist/js/engagement-video-content-main.js:25520 -#: static/dist/js/engagement-videos-main.js:25520 -#: static/dist/js/performance-content-main.js:25520 -#: static/dist/js/performance-learning-outcomes-content-main.js:25520 -#: static/dist/js/performance-learning-outcomes-section-main.js:25520 -#: static/dist/js/performance-problems-main.js:25520 -#: static/js/views/stacked-bar-view.js:13 -msgid "<%=value%> (<%=percent%>)" -msgstr "" - -#: static/dist/js/engagement-video-content-main.js:25579 #: static/js/engagement-video-content-main.js:14 msgid "Average Complete Views" msgstr "" -#: static/dist/js/engagement-video-content-main.js:25588 #: static/js/engagement-video-content-main.js:23 msgid "Average Incomplete Views" msgstr "" -#: static/dist/js/engagement-video-content-main.js:25596 -#: static/dist/js/engagement-videos-main.js:25594 -#: static/dist/js/performance-content-main.js:25593 -#: static/dist/js/performance-learning-outcomes-content-main.js:25593 -#: static/dist/js/performance-learning-outcomes-section-main.js:25593 -#: static/dist/js/performance-problems-main.js:25591 #: static/js/engagement-video-content-main.js:31 #: static/js/engagement-videos-main.js:29 #: static/js/performance-content-main.js:28 @@ -842,165 +625,143 @@ msgstr "" msgid "Order" msgstr "" -#: static/dist/js/engagement-video-content-main.js:25606 -#: static/dist/js/engagement-videos-main.js:25603 #: static/js/engagement-video-content-main.js:41 #: static/js/engagement-videos-main.js:38 msgid "Completion Percentage" msgstr "" -#: static/dist/js/engagement-video-timeline-main.js:25539 #: static/js/engagement-video-timeline-main.js:25 msgid "Unique Viewers" msgstr "" -#: static/dist/js/engagement-video-timeline-main.js:25546 #: static/js/engagement-video-timeline-main.js:32 msgid "Replays" msgstr "" -#: static/dist/js/engagement-video-timeline-main.js:25553 #: static/js/engagement-video-timeline-main.js:39 msgid "Time" msgstr "" -#: static/dist/js/engagement-videos-main.js:25579 #: static/js/engagement-videos-main.js:14 msgid "Complete Views" msgstr "" -#: static/dist/js/engagement-videos-main.js:25587 #: static/js/engagement-videos-main.js:22 msgid "Incomplete Views" msgstr "" -#: static/dist/js/enrollment-activity-main.js:25428 +#. Translators: this describe the learner's enrollment track (e.g. Honor certificate) #: static/js/enrollment-activity-main.js:33 msgid "Honor" msgstr "" -#: static/dist/js/enrollment-activity-main.js:25433 #: static/js/enrollment-activity-main.js:38 msgid "Audit" msgstr "" -#: static/dist/js/enrollment-activity-main.js:25438 #: static/js/enrollment-activity-main.js:43 msgid "Verified" msgstr "" -#: static/dist/js/enrollment-activity-main.js:25443 #: static/js/enrollment-activity-main.js:48 msgid "Professional" msgstr "" -#: static/dist/js/enrollment-activity-main.js:25449 +#. Translators: this label indicates the learner has registered for academic credit #: static/js/enrollment-activity-main.js:54 msgid "Verified with Credit" msgstr "" -#: static/dist/js/enrollment-demographics-age-main.js:25504 -#: static/dist/js/enrollment-demographics-age-main.js:25519 -#: static/dist/js/enrollment-demographics-education-main.js:25539 #: static/js/enrollment-demographics-age-main.js:19 #: static/js/enrollment-demographics-age-main.js:34 #: static/js/enrollment-demographics-education-main.js:32 msgid "Number of Learners" msgstr "" -#: static/dist/js/enrollment-demographics-age-main.js:25511 +#. Translators: <%=value%> will be replaced with an age. #: static/js/enrollment-demographics-age-main.js:26 msgid "Age: <%=value%>" msgstr "" -#: static/dist/js/enrollment-demographics-age-main.js:25518 #: static/js/enrollment-demographics-age-main.js:33 msgid "Age" msgstr "" -#: static/dist/js/enrollment-demographics-age-main.js:25520 #: static/js/enrollment-demographics-age-main.js:35 msgid "Percent of Total" msgstr "" -#: static/dist/js/enrollment-demographics-education-main.js:25525 -#: static/dist/js/enrollment-demographics-gender-main.js:25524 #: static/js/enrollment-demographics-education-main.js:18 #: static/js/enrollment-demographics-gender-main.js:17 msgid "Percentage" msgstr "" -#: static/dist/js/enrollment-demographics-education-main.js:25531 +#. Translators: <%=value%> will be replaced with a level of education (e.g. Doctorate). #: static/js/enrollment-demographics-education-main.js:24 msgid "Education: <%=value%>" msgstr "" -#: static/dist/js/enrollment-demographics-education-main.js:25538 #: static/js/enrollment-demographics-education-main.js:31 msgid "Educational Background" msgstr "" -#: static/dist/js/enrollment-demographics-gender-main.js:25530 +#. Translators: <%=value%> will be replaced with a level of gender (e.g. Female). #: static/js/enrollment-demographics-gender-main.js:23 msgid "Gender: <%=value%>" msgstr "" -#: static/dist/js/enrollment-demographics-gender-main.js:25540 #: static/js/enrollment-demographics-gender-main.js:33 msgid "Female" msgstr "" -#: static/dist/js/enrollment-demographics-gender-main.js:25541 #: static/js/enrollment-demographics-gender-main.js:34 msgid "Male" msgstr "" -#: static/dist/js/enrollment-demographics-gender-main.js:25542 #: static/js/enrollment-demographics-gender-main.js:35 msgid "Other" msgstr "" -#: static/dist/js/enrollment-demographics-gender-main.js:25543 #: static/js/enrollment-demographics-gender-main.js:36 msgid "Not Reported" msgstr "" -#: static/dist/js/enrollment-geography-main.js:22473 #: static/js/enrollment-geography-main.js:18 msgid "" "Learner location is determined from IP address. This map shows where " "learners most recently connected." msgstr "" -#: static/dist/js/enrollment-geography-main.js:22481 #: static/js/enrollment-geography-main.js:26 msgid "Country or Region" msgstr "" -#: static/dist/js/enrollment-geography-main.js:22482 #: static/js/enrollment-geography-main.js:27 msgid "Percent" msgstr "" -#: static/dist/js/performance-answer-distribution-main.js:25519 #: static/js/performance-answer-distribution-main.js:12 msgid "Answer" msgstr "" -#: static/dist/js/performance-answer-distribution-main.js:25523 +#. Translators: "Correct" is displayed in a table.. +#: static/js/performance-answer-distribution-main.js:15 +#: static/js/performance-answer-distribution-main.js:43 +#: static/js/performance-learning-outcomes-section-main.js:11 +#: static/js/performance-learning-outcomes-section-main.js:37 +#: static/js/performance-problems-main.js:11 +#: static/js/views/data-table-view.js:216 +msgid "Correct" +msgstr "" + #: static/js/performance-answer-distribution-main.js:16 msgid "Submission Count" msgstr "" -#: static/dist/js/performance-answer-distribution-main.js:25537 #: static/js/performance-answer-distribution-main.js:30 msgid "Variant" msgstr "" -#: static/dist/js/performance-answer-distribution-main.js:25552 -#: static/dist/js/performance-learning-outcomes-section-main.js:25585 -#: static/dist/js/performance-learning-outcomes-section-main.js:25610 -#: static/dist/js/performance-problems-main.js:25584 #: static/js/performance-answer-distribution-main.js:45 #: static/js/performance-learning-outcomes-section-main.js:20 #: static/js/performance-learning-outcomes-section-main.js:45 @@ -1008,39 +769,29 @@ msgstr "" msgid "Incorrect" msgstr "" -#: static/dist/js/performance-answer-distribution-main.js:25567 +#. Translators: <%=value%> will be replaced by a learner response to a question asked in a course. #: static/js/performance-answer-distribution-main.js:60 msgid "Answer: <%=value%>" msgstr "" -#: static/dist/js/performance-content-main.js:25576 -#: static/dist/js/performance-learning-outcomes-content-main.js:25576 #: static/js/performance-content-main.js:11 #: static/js/performance-learning-outcomes-content-main.js:11 msgid "Average Correct" msgstr "" -#: static/dist/js/performance-content-main.js:25585 -#: static/dist/js/performance-learning-outcomes-content-main.js:25585 #: static/js/performance-content-main.js:20 #: static/js/performance-learning-outcomes-content-main.js:20 msgid "Average Incorrect" msgstr "" -#: static/dist/js/performance-content-main.js:25595 #: static/js/performance-content-main.js:30 msgid "Problems" msgstr "" -#: static/dist/js/performance-content-main.js:25604 #: static/js/performance-content-main.js:39 msgid "Average Submissions Per Problem" msgstr "" -#: static/dist/js/performance-content-main.js:25613 -#: static/dist/js/performance-learning-outcomes-content-main.js:25611 -#: static/dist/js/performance-learning-outcomes-section-main.js:25626 -#: static/dist/js/performance-problems-main.js:25608 #: static/js/performance-content-main.js:48 #: static/js/performance-learning-outcomes-content-main.js:46 #: static/js/performance-learning-outcomes-section-main.js:61 @@ -1048,19 +799,34 @@ msgstr "" msgid "Percentage Correct" msgstr "" -#: static/dist/js/performance-learning-outcomes-content-main.js:25603 #: static/js/performance-learning-outcomes-content-main.js:38 msgid "Average Submissions per Problem" msgstr "" -#: static/dist/js/performance-learning-outcomes-section-main.js:25595 #: static/js/performance-learning-outcomes-section-main.js:30 msgid "Difficulty" msgstr "" -#: static/dist/js/performance-learning-outcomes-section-main.js:25618 -#: static/dist/js/performance-problems-main.js:25600 #: static/js/performance-learning-outcomes-section-main.js:53 #: static/js/performance-problems-main.js:35 msgid "Total" msgstr "" + +#: static/js/utils/utils.js:93 +msgid "..." +msgstr "" + +#. Translators: (empty) is displayed in a table and indicates no label/value. +#. Keep text in the parenthesis or an equivalent symbol. +#. +#. Translators: (empty) is displayed as a label in a chart and indicates that no label was provided. +#: static/js/views/data-table-view.js:199 +#: static/js/views/discrete-bar-view.js:21 +msgid "(empty)" +msgstr "" + +#. Translators: <%=value%> will be replaced by a number followed by a percentage. +#. For example, "400 (29%)" +#: static/js/views/stacked-bar-view.js:13 +msgid "<%=value%> (<%=percent%>)" +msgstr "" diff --git a/analytics_dashboard/courses/templates/courses/base_performance_answer_distribution.html b/analytics_dashboard/courses/templates/courses/base_performance_answer_distribution.html index 4978c014a..82b48fcf4 100644 --- a/analytics_dashboard/courses/templates/courses/base_performance_answer_distribution.html +++ b/analytics_dashboard/courses/templates/courses/base_performance_answer_distribution.html @@ -2,13 +2,13 @@ {% load i18n %} {% load dashboard_extras %} {% load staticfiles %} -{% load rjs %} +{% load render_bundle from webpack_loader %} {% block view-name %}view-course-enrollment view-dashboard{% endblock view-name %} {% block javascript %} {{ block.super }} - + {% render_bundle 'performance-answer-distribution-main' %} {% endblock javascript %} diff --git a/analytics_dashboard/courses/templates/courses/base_performance_learning_outcomes.html b/analytics_dashboard/courses/templates/courses/base_performance_learning_outcomes.html index 1c2f3bfa3..14ad5e2e2 100644 --- a/analytics_dashboard/courses/templates/courses/base_performance_learning_outcomes.html +++ b/analytics_dashboard/courses/templates/courses/base_performance_learning_outcomes.html @@ -2,7 +2,6 @@ {% load i18n %} {% load dashboard_extras %} {% load staticfiles %} -{% load rjs %} {% block view-name %}view-course-enrollment view-dashboard{% endblock view-name %} diff --git a/analytics_dashboard/courses/templates/courses/engagement_content.html b/analytics_dashboard/courses/templates/courses/engagement_content.html index cb65336b1..5378a1dbe 100644 --- a/analytics_dashboard/courses/templates/courses/engagement_content.html +++ b/analytics_dashboard/courses/templates/courses/engagement_content.html @@ -4,7 +4,7 @@ {% load staticfiles %} {% load waffle_tags %} {% load dashboard_extras %} -{% load rjs %} +{% load render_bundle from webpack_loader %} {% comment %} Individual course-centric engagement content view. @@ -14,7 +14,7 @@ {% block javascript %} {{ block.super }} - + {% render_bundle 'engagement-content-main' %} {% endblock javascript %} {% block child_content %} diff --git a/analytics_dashboard/courses/templates/courses/engagement_grouped_content.html b/analytics_dashboard/courses/templates/courses/engagement_grouped_content.html index 93f1c74e4..5b48f7e77 100644 --- a/analytics_dashboard/courses/templates/courses/engagement_grouped_content.html +++ b/analytics_dashboard/courses/templates/courses/engagement_grouped_content.html @@ -1,7 +1,7 @@ {% extends "courses/base_grouped_content.html" %} {% load i18n %} -{% load rjs %} +{% load render_bundle from webpack_loader %} {% block view-name %}view-course-engagement-video-content view-dashboard{% endblock view-name %} @@ -9,7 +9,7 @@ {{ block.super }} {# override for custom JS loading #} {% block engagement_javascript %} - + {% render_bundle 'engagement-video-content-main' %} {% endblock engagement_javascript %} {% endblock javascript %} diff --git a/analytics_dashboard/courses/templates/courses/engagement_video_by_subsection.html b/analytics_dashboard/courses/templates/courses/engagement_video_by_subsection.html index f2c8f1fa0..419fc1944 100644 --- a/analytics_dashboard/courses/templates/courses/engagement_video_by_subsection.html +++ b/analytics_dashboard/courses/templates/courses/engagement_video_by_subsection.html @@ -1,9 +1,9 @@ {% extends "courses/engagement_grouped_content.html" %} {% load i18n %} -{% load rjs %} +{% load render_bundle from webpack_loader %} {% block engagement_javascript %} - + {% render_bundle 'engagement-videos-main' %} {% endblock engagement_javascript %} {% block content_nav %} diff --git a/analytics_dashboard/courses/templates/courses/engagement_video_timeline.html b/analytics_dashboard/courses/templates/courses/engagement_video_timeline.html index 11cb8bebb..c0c622972 100644 --- a/analytics_dashboard/courses/templates/courses/engagement_video_timeline.html +++ b/analytics_dashboard/courses/templates/courses/engagement_video_timeline.html @@ -2,13 +2,13 @@ {% load dashboard_extras %} {% load i18n %} -{% load rjs %} +{% load render_bundle from webpack_loader %} {% block view-name %}view-course-engagement-video-timeline view-dashboard{% endblock view-name %} {% block javascript %} {{ block.super }} - + {% render_bundle 'engagement-video-timeline-main' %} {% endblock javascript %} {% block hover_category %}engagement_video_timeline{% endblock hover_category %} diff --git a/analytics_dashboard/courses/templates/courses/enrollment_activity.html b/analytics_dashboard/courses/templates/courses/enrollment_activity.html index 0fd83678f..ba6bc018e 100644 --- a/analytics_dashboard/courses/templates/courses/enrollment_activity.html +++ b/analytics_dashboard/courses/templates/courses/enrollment_activity.html @@ -2,7 +2,7 @@ {% load i18n %} {% load dashboard_extras %} {% load staticfiles %} -{% load rjs %} +{% load render_bundle from webpack_loader %} {% load waffle_tags %} {% comment %} @@ -13,7 +13,7 @@ {% block javascript %} {{ block.super }} - + {% render_bundle 'enrollment-activity-main' %} {% endblock javascript %} diff --git a/analytics_dashboard/courses/templates/courses/enrollment_demographics_age.html b/analytics_dashboard/courses/templates/courses/enrollment_demographics_age.html index eacd2c39a..181d347cb 100644 --- a/analytics_dashboard/courses/templates/courses/enrollment_demographics_age.html +++ b/analytics_dashboard/courses/templates/courses/enrollment_demographics_age.html @@ -2,13 +2,14 @@ {% load i18n %} {% load dashboard_extras %} {% load staticfiles %} -{% load rjs %} +{% load render_bundle from webpack_loader %} {% block view-name %}view-course-enrollment view-dashboard{% endblock view-name %} {% block javascript %} {{ block.super }} - +{% render_bundle 'enrollment-demographics-age-main' %} + {% endblock javascript %} diff --git a/analytics_dashboard/courses/templates/courses/enrollment_demographics_education.html b/analytics_dashboard/courses/templates/courses/enrollment_demographics_education.html index ff76acac1..c6fe26a8c 100644 --- a/analytics_dashboard/courses/templates/courses/enrollment_demographics_education.html +++ b/analytics_dashboard/courses/templates/courses/enrollment_demographics_education.html @@ -2,13 +2,13 @@ {% load i18n %} {% load dashboard_extras %} {% load staticfiles %} -{% load rjs %} +{% load render_bundle from webpack_loader %} {% block view-name %}view-course-enrollment view-dashboard{% endblock view-name %} {% block javascript %} {{ block.super }} - + {% render_bundle 'enrollment-demographics-education-main' %} {% endblock javascript %} diff --git a/analytics_dashboard/courses/templates/courses/enrollment_demographics_gender.html b/analytics_dashboard/courses/templates/courses/enrollment_demographics_gender.html index 2f666d0b2..4004d9564 100644 --- a/analytics_dashboard/courses/templates/courses/enrollment_demographics_gender.html +++ b/analytics_dashboard/courses/templates/courses/enrollment_demographics_gender.html @@ -2,13 +2,13 @@ {% load i18n %} {% load dashboard_extras %} {% load staticfiles %} -{% load rjs %} +{% load render_bundle from webpack_loader %} {% block view-name %}view-course-enrollment view-dashboard{% endblock view-name %} {% block javascript %} {{ block.super }} - + {% render_bundle 'enrollment-demographics-gender-main' %} {% endblock javascript %} diff --git a/analytics_dashboard/courses/templates/courses/enrollment_geography.html b/analytics_dashboard/courses/templates/courses/enrollment_geography.html index d5231245f..4a2987073 100644 --- a/analytics_dashboard/courses/templates/courses/enrollment_geography.html +++ b/analytics_dashboard/courses/templates/courses/enrollment_geography.html @@ -2,7 +2,7 @@ {% load i18n %} {% load staticfiles %} {% load dashboard_extras %} -{% load rjs %} +{% load render_bundle from webpack_loader %} {% comment %} Individual course-centric enrollment geography view. @@ -12,7 +12,7 @@ {% block javascript %} {{ block.super }} - + {% render_bundle 'enrollment-geography-main' %} {% endblock javascript %} {% block child_content %} diff --git a/analytics_dashboard/courses/templates/courses/index.html b/analytics_dashboard/courses/templates/courses/index.html index 9bf8af0a5..60871470d 100644 --- a/analytics_dashboard/courses/templates/courses/index.html +++ b/analytics_dashboard/courses/templates/courses/index.html @@ -2,7 +2,8 @@ {% load i18n %} {% load staticfiles %} {% load dashboard_extras %} -{% load rjs %} +{% load render_bundle from webpack_loader %} +{% load get_files from webpack_loader %} {% block view-name %}view-course-list{% endblock view-name %} @@ -57,12 +58,15 @@

{% block stylesheets %} {{ block.super }} - + {% get_files 'learners-main' 'css' as common_css %} + {% for css_file in common_css %} + + {% endfor %} {% endblock stylesheets %} {% block javascript %} {{ block.super }} - + {% render_bundle 'course-list-main' %} {% endblock javascript %} {% block content %} diff --git a/analytics_dashboard/courses/templates/courses/learners.html b/analytics_dashboard/courses/templates/courses/learners.html index 6ae16cece..d4fe9afd0 100644 --- a/analytics_dashboard/courses/templates/courses/learners.html +++ b/analytics_dashboard/courses/templates/courses/learners.html @@ -3,7 +3,8 @@ {% load dashboard_extras %} {% load i18n %} {% load staticfiles %} -{% load rjs %} +{% load render_bundle from webpack_loader %} +{% load get_files from webpack_loader %} {% comment %} View of individual learners within a course. @@ -13,13 +14,15 @@ {% block stylesheets %} {{ block.super }} - - + {% get_files 'learners-main' 'css' as common_css %} + {% for css_file in common_css %} + + {% endfor %} {% endblock stylesheets %} {% block javascript %} {{ block.super }} - + {% render_bundle 'learners-main' %} {% endblock javascript %} {% block child_content %} diff --git a/analytics_dashboard/courses/templates/courses/performance_assignment.html b/analytics_dashboard/courses/templates/courses/performance_assignment.html index 01c0c6670..392a30611 100644 --- a/analytics_dashboard/courses/templates/courses/performance_assignment.html +++ b/analytics_dashboard/courses/templates/courses/performance_assignment.html @@ -1,9 +1,9 @@ {% extends "courses/performance_grouped_content.html" %} {% load i18n %} -{% load rjs %} +{% load render_bundle from webpack_loader %} {% block performance_javascript %} - + {% render_bundle 'performance-problems-main' %} {% endblock performance_javascript %} {% block content_nav %} diff --git a/analytics_dashboard/courses/templates/courses/performance_graded_content_by_type.html b/analytics_dashboard/courses/templates/courses/performance_graded_content_by_type.html index 3f6fae841..33f1f7358 100644 --- a/analytics_dashboard/courses/templates/courses/performance_graded_content_by_type.html +++ b/analytics_dashboard/courses/templates/courses/performance_graded_content_by_type.html @@ -1,7 +1,6 @@ {% extends "courses/performance_grouped_content.html" %} {% load i18n %} {% load dashboard_extras %} -{% load rjs %} {% block content_nav %} {% captureas heading_note %} diff --git a/analytics_dashboard/courses/templates/courses/performance_grouped_content.html b/analytics_dashboard/courses/templates/courses/performance_grouped_content.html index 55943ee39..803bd5574 100644 --- a/analytics_dashboard/courses/templates/courses/performance_grouped_content.html +++ b/analytics_dashboard/courses/templates/courses/performance_grouped_content.html @@ -1,12 +1,12 @@ {% extends "courses/base_grouped_content.html" %} -{% load rjs %} +{% load render_bundle from webpack_loader %} {% block javascript %} {{ block.super }} {# override for custom JS loading #} {% block performance_javascript %} - + {% render_bundle 'performance-content-main' %} {% endblock performance_javascript %} {% endblock javascript %} diff --git a/analytics_dashboard/courses/templates/courses/performance_learning_outcomes_content.html b/analytics_dashboard/courses/templates/courses/performance_learning_outcomes_content.html index c4103c715..6fbce9062 100644 --- a/analytics_dashboard/courses/templates/courses/performance_learning_outcomes_content.html +++ b/analytics_dashboard/courses/templates/courses/performance_learning_outcomes_content.html @@ -1,6 +1,6 @@ {% extends "courses/base_performance_learning_outcomes.html" %} {% load i18n %} -{% load rjs %} +{% load render_bundle from webpack_loader %} {% block content_nav %} {% trans "Measured by learning outcome, how are learners performing?" as heading_note %} @@ -9,7 +9,7 @@ {% block javascript %} {{ block.super }} - + {% render_bundle 'performance-learning-outcomes-content-main' %} {% endblock javascript %} {% block chart_tip_text %} diff --git a/analytics_dashboard/courses/templates/courses/performance_learning_outcomes_section.html b/analytics_dashboard/courses/templates/courses/performance_learning_outcomes_section.html index dbc1c78d0..b2418c069 100644 --- a/analytics_dashboard/courses/templates/courses/performance_learning_outcomes_section.html +++ b/analytics_dashboard/courses/templates/courses/performance_learning_outcomes_section.html @@ -1,6 +1,6 @@ {% extends "courses/base_performance_learning_outcomes.html" %} {% load i18n %} -{% load rjs %} +{% load render_bundle from webpack_loader %} {% block content_nav %} {% trans "Measured by learning outcome, how are learners performing?" as heading_note %} @@ -9,7 +9,7 @@ {% block javascript %} {{ block.super }} - + {% render_bundle 'performance-learning-outcomes-section-main' %} {% endblock javascript %} {% block chart_tip_text %} diff --git a/analytics_dashboard/courses/templates/courses/performance_ungraded_by_subsection.html b/analytics_dashboard/courses/templates/courses/performance_ungraded_by_subsection.html index 909df60a5..4b33007cd 100644 --- a/analytics_dashboard/courses/templates/courses/performance_ungraded_by_subsection.html +++ b/analytics_dashboard/courses/templates/courses/performance_ungraded_by_subsection.html @@ -1,9 +1,9 @@ {% extends "courses/performance_grouped_content.html" %} {% load i18n %} -{% load rjs %} +{% load render_bundle from webpack_loader %} {% block performance_javascript %} - + {% render_bundle 'performance-problems-main' %} {% endblock performance_javascript %} {% block content_nav %} diff --git a/analytics_dashboard/django_rjs/__init__.py b/analytics_dashboard/django_rjs/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/analytics_dashboard/django_rjs/models.py b/analytics_dashboard/django_rjs/models.py deleted file mode 100644 index bc9633f13..000000000 --- a/analytics_dashboard/django_rjs/models.py +++ /dev/null @@ -1 +0,0 @@ -# This app does not require any models. diff --git a/analytics_dashboard/django_rjs/templatetags/__init__.py b/analytics_dashboard/django_rjs/templatetags/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/analytics_dashboard/django_rjs/templatetags/rjs.py b/analytics_dashboard/django_rjs/templatetags/rjs.py deleted file mode 100644 index dbfd888af..000000000 --- a/analytics_dashboard/django_rjs/templatetags/rjs.py +++ /dev/null @@ -1,45 +0,0 @@ -from django import template -from django.conf import settings -from django.core.exceptions import ImproperlyConfigured -from django.templatetags.static import StaticNode -from django.contrib.staticfiles.storage import staticfiles_storage - - -register = template.Library() - - -def get_rjs_path(path): - if getattr(settings, 'RJS_OPTIMIZATION_ENABLED', False): - rjs_output_dir = getattr(settings, 'RJS_OUTPUT_DIR') - - if rjs_output_dir: - return '{0}/{1}'.format(rjs_output_dir, path) - - raise ImproperlyConfigured('RJS_OPTIMIZATION_ENABLED is set to True, but RJS_OUTPUT_DIR has not been set.') - - return path - - -class RjsStaticFilesNode(StaticNode): - def url(self, context): - path = self.path.resolve(context) - path = get_rjs_path(path) - return staticfiles_storage.url(path) - - -@register.tag('static_rjs') -def do_static(parser, token): - """ - A template tag that returns the URL to a file (that has been optimized by r.js) - using staticfiles' storage backend - - Usage:: - - {% static_rjs path [as varname] %} - - Examples:: - - {% static_rjs "myapp/js/main.js" %} - - """ - return RjsStaticFilesNode.handle_token(parser, token) diff --git a/analytics_dashboard/django_rjs/tests.py b/analytics_dashboard/django_rjs/tests.py deleted file mode 100644 index 8757954ea..000000000 --- a/analytics_dashboard/django_rjs/tests.py +++ /dev/null @@ -1,25 +0,0 @@ -from django.core.exceptions import ImproperlyConfigured -from django.test import TestCase - -from django_rjs.templatetags.rjs import get_rjs_path - - -class RjsTests(TestCase): - def test_get_rjs_path(self): - rjs_dir = 'rjs-test' - path = 'js/test.js' - - # The path should not be modified if r.js optimization is disabled. - with self.settings(RJS_OPTIMIZATION_ENABLED=False): - actual = get_rjs_path(path) - self.assertEqual(actual, path) - - with self.settings(RJS_OPTIMIZATION_ENABLED=True): - # An exception should be raised if optimization is enabled but no output directory is configured. - with self.settings(RJS_OUTPUT_DIR=None): - self.assertRaises(ImproperlyConfigured, get_rjs_path, path) - - # If optimization is enabled and a directory configured, the directory should be prepended to the path. - with self.settings(RJS_OUTPUT_DIR=rjs_dir): - actual = get_rjs_path(path) - self.assertEqual(actual, rjs_dir + '/' + path) diff --git a/analytics_dashboard/settings/base.py b/analytics_dashboard/settings/base.py index ffcc79f02..a610afa0f 100644 --- a/analytics_dashboard/settings/base.py +++ b/analytics_dashboard/settings/base.py @@ -8,6 +8,7 @@ ########## PATH CONFIGURATION # Absolute filesystem path to the Django project directory: DJANGO_ROOT = dirname(dirname(abspath(__file__))) +BASE_DIR = DJANGO_ROOT # Absolute filesystem path to the top-level project folder: SITE_ROOT = dirname(DJANGO_ROOT) @@ -106,18 +107,7 @@ STATICFILES_FINDERS = ( 'django.contrib.staticfiles.finders.FileSystemFinder', 'django.contrib.staticfiles.finders.AppDirectoriesFinder', - 'compressor.finders.CompressorFinder', ) - -COMPRESS_PRECOMPILERS = ( - ('text/x-scss', 'django_libsass.SassCompiler'), -) - -COMPRESS_CSS_FILTERS = ['compressor.filters.css_default.CssAbsoluteFilter'] -COMPRESS_JS_FILTERS = ['compressor.filters.closure.ClosureCompilerFilter'] -COMPRESS_CLOSURE_COMPILER_BINARY = 'java -jar scripts/closure-compiler.jar' -COMPRESS_CLOSURE_JS_ARGUMENTS = {'compilation_level': 'ADVANCED_OPTIMIZATIONS',} -COMPRESS_CLOSURE_COMPILER_ARGUMENTS = "--language_in=ECMASCRIPT5" ########## END STATIC FILE CONFIGURATION @@ -216,14 +206,12 @@ 'waffle', 'django_countries', 'pinax.announcements', - 'compressor', ) # Apps specific for this project go here. LOCAL_APPS = ( 'core', 'courses', - 'django_rjs', 'help', 'soapbox', ) @@ -231,7 +219,8 @@ THIRD_PARTY_APPS = ( 'release_util', 'rest_framework', - 'social_django' + 'social_django', + 'webpack_loader' ) # See: https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps @@ -380,9 +369,6 @@ APPLICATION_NAME = 'Insights' FULL_APPLICATION_NAME = '{0} {1}'.format(PLATFORM_NAME, APPLICATION_NAME) -RJS_OUTPUT_DIR = 'dist' -RJS_OPTIMIZATION_ENABLED = False - ########## DOCS/HELP CONFIGURATION DOCS_ROOT = join(SITE_ROOT, 'docs') @@ -462,6 +448,15 @@ COURSE_SUMMARIES_CACHE_TIMEOUT = 3600 # 1 hour timeout ########## END CACHE CONFIGURATION +########## WEBPACK CONFIGURATION +WEBPACK_LOADER = { + 'DEFAULT': { + 'BUNDLE_DIR_NAME': 'bundles/', + 'STATS_FILE': os.path.join(SITE_ROOT, 'webpack-stats.json'), + } +} +########## END WEBPACK CONFIGURATION + ########## CDN CONFIGURATION CDN_DOMAIN = None # production will not use a CDN for static assets if this is set to a falsy value ########## END CDN CONFIGURATION diff --git a/analytics_dashboard/settings/production.py b/analytics_dashboard/settings/production.py index 71aac0025..9f9563afb 100644 --- a/analytics_dashboard/settings/production.py +++ b/analytics_dashboard/settings/production.py @@ -4,19 +4,6 @@ from analytics_dashboard.settings.yaml_config import * from analytics_dashboard.settings.logger import get_logger_config -if not DEBUG: - # Enable offline compression of CSS/JS - COMPRESS_ENABLED = True - COMPRESS_OFFLINE = True - - # Use r.js to combine RequireJS files - RJS_OPTIMIZATION_ENABLED = True - -# Minify CSS -COMPRESS_CSS_FILTERS += [ - 'compressor.filters.cssmin.CSSMinFilter', -] - LOGGING = get_logger_config() diff --git a/analytics_dashboard/settings/test.py b/analytics_dashboard/settings/test.py index 90c7c3de0..e921986d7 100644 --- a/analytics_dashboard/settings/test.py +++ b/analytics_dashboard/settings/test.py @@ -33,8 +33,3 @@ DATA_API_URL = 'http://data-api-host/api/v0' LOGGING = get_logger_config(debug=DEBUG, dev_env=True, local_loglevel='DEBUG') - -# Use production settings for asset compression so that asset compilation can be tested on the CI server. -COMPRESS_ENABLED = True -COMPRESS_OFFLINE = True -RJS_OPTIMIZATION_ENABLED = True diff --git a/analytics_dashboard/static/apps/components/alert/views/alert-view.js b/analytics_dashboard/static/apps/components/alert/views/alert-view.js index da49b88db..66f64be41 100644 --- a/analytics_dashboard/static/apps/components/alert/views/alert-view.js +++ b/analytics_dashboard/static/apps/components/alert/views/alert-view.js @@ -7,7 +7,7 @@ define(function(require) { var _ = require('underscore'), Marionette = require('marionette'), - alertTemplate = require('text!components/alert/templates/alert.underscore'), + alertTemplate = require('components/alert/templates/alert.underscore'), AlertView; diff --git a/analytics_dashboard/static/apps/components/download/views/download-data.js b/analytics_dashboard/static/apps/components/download/views/download-data.js index c6ad5aace..1d6992292 100644 --- a/analytics_dashboard/static/apps/components/download/views/download-data.js +++ b/analytics_dashboard/static/apps/components/download/views/download-data.js @@ -6,7 +6,7 @@ define(function(require) { Marionette = require('marionette'), Utils = require('utils/utils'), - downloadDataTemplate = require('text!components/download/templates/download-data.underscore'), + downloadDataTemplate = require('components/download/templates/download-data.underscore'), DownloadDataView; DownloadDataView = Marionette.ItemView.extend({ diff --git a/analytics_dashboard/static/apps/components/filter/views/checkbox-filter.js b/analytics_dashboard/static/apps/components/filter/views/checkbox-filter.js index 40040c991..c2c4a0a8f 100644 --- a/analytics_dashboard/static/apps/components/filter/views/checkbox-filter.js +++ b/analytics_dashboard/static/apps/components/filter/views/checkbox-filter.js @@ -7,7 +7,7 @@ define(function(require) { var $ = require('jquery'), _ = require('underscore'), - filterTemplate = require('text!components/filter/templates/checkbox-filter.underscore'), + filterTemplate = require('components/filter/templates/checkbox-filter.underscore'), Filter = require('components/filter/views/filter'); return Filter.extend({ diff --git a/analytics_dashboard/static/apps/components/filter/views/drop-down-filter.js b/analytics_dashboard/static/apps/components/filter/views/drop-down-filter.js index 588cb7c31..b04370926 100644 --- a/analytics_dashboard/static/apps/components/filter/views/drop-down-filter.js +++ b/analytics_dashboard/static/apps/components/filter/views/drop-down-filter.js @@ -7,7 +7,7 @@ define(function(require) { var $ = require('jquery'), _ = require('underscore'), - filterTemplate = require('text!components/filter/templates/drop-down-filter.underscore'), + filterTemplate = require('components/filter/templates/drop-down-filter.underscore'), Filter = require('components/filter/views/filter'); return Filter.extend({ diff --git a/analytics_dashboard/static/apps/components/generic-list/common/collections/collection.js b/analytics_dashboard/static/apps/components/generic-list/common/collections/collection.js index c7996c06a..526af89b4 100644 --- a/analytics_dashboard/static/apps/components/generic-list/common/collections/collection.js +++ b/analytics_dashboard/static/apps/components/generic-list/common/collections/collection.js @@ -1,7 +1,7 @@ define(function(require) { 'use strict'; - var PagingCollection = require('uitk/pagination/paging-collection'), + var PagingCollection = require('edx-ui-toolkit/src/js/pagination/paging-collection'), ListUtils = require('components/utils/utils'), Utils = require('utils/utils'), _ = require('underscore'), @@ -93,13 +93,11 @@ define(function(require) { sortKey = val; } else if (key === 'order') { order = val === 'desc' ? 1 : -1; - } else { - if (key in this.filterableFields || key === 'text_search') { - if (val !== this.getFilterFieldValue(key)) { - this.isStale = true; - } - this.setFilterField(key, val); + } else if (key in this.filterableFields || key === 'text_search') { + if (val !== this.getFilterFieldValue(key)) { + this.isStale = true; } + this.setFilterField(key, val); } }, this); diff --git a/analytics_dashboard/static/apps/components/generic-list/list/views/active-filters.js b/analytics_dashboard/static/apps/components/generic-list/list/views/active-filters.js index 2b02c055d..1fa624b62 100644 --- a/analytics_dashboard/static/apps/components/generic-list/list/views/active-filters.js +++ b/analytics_dashboard/static/apps/components/generic-list/list/views/active-filters.js @@ -8,7 +8,7 @@ define(function(require) { ActiveFiltersView, - activeFiltersTemplate = require('text!components/generic-list/list/templates/active-filters.underscore'); + activeFiltersTemplate = require('components/generic-list/list/templates/active-filters.underscore'); ActiveFiltersView = ParentView.extend({ events: { diff --git a/analytics_dashboard/static/apps/components/generic-list/list/views/base-header-cell.js b/analytics_dashboard/static/apps/components/generic-list/list/views/base-header-cell.js index 7f61baa29..7e807b77a 100644 --- a/analytics_dashboard/static/apps/components/generic-list/list/views/base-header-cell.js +++ b/analytics_dashboard/static/apps/components/generic-list/list/views/base-header-cell.js @@ -7,7 +7,7 @@ define(function(require) { var _ = require('underscore'), Backgrid = require('backgrid'), - baseHeaderCellTemplate = require('text!components/generic-list/list/templates/base-header-cell.underscore'), + baseHeaderCellTemplate = require('components/generic-list/list/templates/base-header-cell.underscore'), BaseHeaderCell; diff --git a/analytics_dashboard/static/apps/components/generic-list/list/views/list.js b/analytics_dashboard/static/apps/components/generic-list/list/views/list.js index 9a4d934d7..c4aec9404 100644 --- a/analytics_dashboard/static/apps/components/generic-list/list/views/list.js +++ b/analytics_dashboard/static/apps/components/generic-list/list/views/list.js @@ -15,8 +15,6 @@ define(function(require) { // Load modules without exports require('backgrid-filter'); - require('bootstrap'); - require('bootstrap_accessibility'); // adds the aria-describedby to tooltips require('components/skip-link/behaviors/skip-target-behavior'); /** @@ -45,7 +43,6 @@ define(function(require) { sync: ListUtils.EventTransformers.syncToClearError }; ListUtils.mapEvents(this.options.collection, eventTransformers, this); - ListUtils.mapEvents(this.options.courseMetadata, eventTransformers, this); }, templateHelpers: function() { diff --git a/analytics_dashboard/static/apps/components/generic-list/list/views/num-results.js b/analytics_dashboard/static/apps/components/generic-list/list/views/num-results.js index 5ec23401f..ccda4391c 100644 --- a/analytics_dashboard/static/apps/components/generic-list/list/views/num-results.js +++ b/analytics_dashboard/static/apps/components/generic-list/list/views/num-results.js @@ -7,7 +7,7 @@ define(function(require) { NumResultsView, - numResultsTemplate = require('text!components/generic-list/list/templates/num-results.underscore'); + numResultsTemplate = require('components/generic-list/list/templates/num-results.underscore'); NumResultsView = Marionette.ItemView.extend({ template: _.template(numResultsTemplate), @@ -35,9 +35,7 @@ define(function(require) { }, renderIfNotDestroyed: function() { - if (this.isDestroyed) { - return; - } else { + if (!this.isDestroyed) { this.render(); } } diff --git a/analytics_dashboard/static/apps/components/generic-list/list/views/paging-footer.js b/analytics_dashboard/static/apps/components/generic-list/list/views/paging-footer.js index 4f88de2fc..71ec9987d 100644 --- a/analytics_dashboard/static/apps/components/generic-list/list/views/paging-footer.js +++ b/analytics_dashboard/static/apps/components/generic-list/list/views/paging-footer.js @@ -9,7 +9,7 @@ define(function(require) { _ = require('underscore'), Backgrid = require('backgrid'), - pageHandleTemplate = require('text!components/generic-list/list/templates/page-handle.underscore'), + pageHandleTemplate = require('components/generic-list/list/templates/page-handle.underscore'), PagingFooter; diff --git a/analytics_dashboard/static/apps/components/generic-list/list/views/table.js b/analytics_dashboard/static/apps/components/generic-list/list/views/table.js index 70c9e1c98..e20288f0a 100644 --- a/analytics_dashboard/static/apps/components/generic-list/list/views/table.js +++ b/analytics_dashboard/static/apps/components/generic-list/list/views/table.js @@ -10,7 +10,7 @@ define(function(require) { BaseHeaderCell = require('./base-header-cell'), PagingFooter = require('./paging-footer'), - listTableTemplate = require('text!components/generic-list/list/templates/table.underscore'), + listTableTemplate = require('components/generic-list/list/templates/table.underscore'), ListTableView; diff --git a/analytics_dashboard/static/apps/components/header/views/header.js b/analytics_dashboard/static/apps/components/header/views/header.js index ddc8ee415..b0d13c8a0 100644 --- a/analytics_dashboard/static/apps/components/header/views/header.js +++ b/analytics_dashboard/static/apps/components/header/views/header.js @@ -7,7 +7,7 @@ define(function(require) { var _ = require('underscore'), Marionette = require('marionette'), - headerTemplate = require('text!components/header/templates/header.underscore'), + headerTemplate = require('components/header/templates/header.underscore'), HeaderView; diff --git a/analytics_dashboard/static/apps/components/root/views/root.js b/analytics_dashboard/static/apps/components/root/views/root.js index 58fe78e66..e7e5f85db 100644 --- a/analytics_dashboard/static/apps/components/root/views/root.js +++ b/analytics_dashboard/static/apps/components/root/views/root.js @@ -13,7 +13,7 @@ define(function(require) { AlertView = require('components/alert/views/alert-view'), HeaderView = require('components/header/views/header'), - rootTemplate = require('text!components/root/templates/root.underscore'), + rootTemplate = require('components/root/templates/root.underscore'), RootView; diff --git a/analytics_dashboard/static/apps/components/utils/utils.js b/analytics_dashboard/static/apps/components/utils/utils.js index 974814cc9..35466cef3 100644 --- a/analytics_dashboard/static/apps/components/utils/utils.js +++ b/analytics_dashboard/static/apps/components/utils/utils.js @@ -1,4 +1,5 @@ -define(function(require) { // eslint-disable-line no-unused-vars +// eslint-disable-next-line no-unused-vars +define(function(require) { 'use strict'; var utils = { diff --git a/analytics_dashboard/static/apps/course-list/app/.eslintrc.json b/analytics_dashboard/static/apps/course-list/app/.eslintrc.json new file mode 120000 index 000000000..4a216c39b --- /dev/null +++ b/analytics_dashboard/static/apps/course-list/app/.eslintrc.json @@ -0,0 +1 @@ +../../../../../.es6-eslintrc.json \ No newline at end of file diff --git a/analytics_dashboard/static/apps/course-list/app/app.js b/analytics_dashboard/static/apps/course-list/app/app.js index 6e4619f84..d2b6d8d98 100644 --- a/analytics_dashboard/static/apps/course-list/app/app.js +++ b/analytics_dashboard/static/apps/course-list/app/app.js @@ -1,84 +1,76 @@ -define(function(require) { - 'use strict'; +import $ from 'jquery'; +import Backbone from 'backbone'; +import Marionette from 'marionette'; +import _ from 'underscore'; - var $ = require('jquery'), - Backbone = require('backbone'), - Marionette = require('marionette'), - _ = require('underscore'), +import initModels from 'load/init-page'; - initModels = require('load/init-page'), +import CourseListCollection from 'course-list/common/collections/course-list'; +import ProgramsCollection from 'course-list/common/collections/programs'; +import CourseListController from 'course-list/app/controller'; +import RootView from 'components/root/views/root'; +import CourseListRouter from 'course-list/app/router'; +import PageModel from 'components/generic-list/common/models/page'; +import SkipLinkView from 'components/skip-link/views/skip-link-view'; - CourseListCollection = require('course-list/common/collections/course-list'), - ProgramsCollection = require('course-list/common/collections/programs'), - CourseListController = require('course-list/app/controller'), - RootView = require('components/root/views/root'), - CourseListRouter = require('course-list/app/router'), - PageModel = require('components/generic-list/common/models/page'), - SkipLinkView = require('components/skip-link/views/skip-link-view'), +export default class CourseListApp extends Marionette.Application { + /** + * Initializes the course-list analytics app. + */ + constructor(options) { + super(); + this.options = options || {}; + } - CourseListApp; + onStart() { + const pageModel = new PageModel(); - CourseListApp = Marionette.Application.extend({ - /** - * Initializes the course-list analytics app. - */ - initialize: function(options) { - this.options = options || {}; - }, - - onStart: function() { - var pageModel = new PageModel(), - courseListCollection, - programsCollection, - rootView; - - new SkipLinkView({ - el: 'body' - }).render(); - - programsCollection = new ProgramsCollection(this.options.programsJson); + new SkipLinkView({ + el: 'body', + }).render(); - courseListCollection = new CourseListCollection(this.options.courseListJson, { - downloadUrl: this.options.courseListDownloadUrl, - filterNameToDisplay: { - pacing_type: { - instructor_paced: gettext('Instructor-Paced'), - self_paced: gettext('Self-Paced') - }, - availability: { - Upcoming: gettext('Upcoming'), - Current: gettext('Current'), - Archived: gettext('Archived'), - unknown: gettext('Unknown') - }, - // Will be filled in dynamically by the initialize() function from the programsCollection models: - program_ids: {} - }, - programsCollection: programsCollection, - passingUsersEnabled: this.options.passingUsersEnabled - }); + const programsCollection = new ProgramsCollection(this.options.programsJson); - rootView = new RootView({ - el: $(this.options.containerSelector), - pageModel: pageModel, - appClass: 'course-list', - displayHeader: false - }).render(); + const courseListCollection = new CourseListCollection(this.options.courseListJson, { + downloadUrl: this.options.courseListDownloadUrl, + filterNameToDisplay: { + pacing_type: { + instructor_paced: gettext('Instructor-Paced'), + self_paced: gettext('Self-Paced'), + }, + availability: { + Upcoming: gettext('Upcoming'), + Current: gettext('Current'), + Archived: gettext('Archived'), + unknown: gettext('Unknown'), + }, + // Will be filled in dynamically by the initialize() function from the + // programsCollection models: + program_ids: {}, + }, + programsCollection, + passingUsersEnabled: this.options.passingUsersEnabled, + }); - new CourseListRouter({ // eslint-disable-line no-new - controller: new CourseListController({ - courseListCollection: courseListCollection, - hasData: _.isObject(this.options.courseListJson), - pageModel: pageModel, - rootView: rootView, - trackingModel: initModels.models.trackingModel, - filteringEnabled: this.options.filteringEnabled - }) - }); + const rootView = new RootView({ + el: $(this.options.containerSelector), + pageModel, + appClass: 'course-list', + displayHeader: false, + }).render(); - Backbone.history.start(); - } + // eslint-disable-next-line no-new + new CourseListRouter({ + controller: new CourseListController({ + courseListCollection, + hasData: _.isObject(this.options.courseListJson), + pageModel, + rootView, + trackingModel: initModels.models.trackingModel, + filteringEnabled: this.options.filteringEnabled, + }), }); - return CourseListApp; -}); + Backbone.history.start(); + } +} diff --git a/analytics_dashboard/static/apps/course-list/app/controller.js b/analytics_dashboard/static/apps/course-list/app/controller.js index bdd8cbb7c..18efe6710 100644 --- a/analytics_dashboard/static/apps/course-list/app/controller.js +++ b/analytics_dashboard/static/apps/course-list/app/controller.js @@ -6,145 +6,140 @@ * - CourseListCollection: A `CourseListCollection` instance. * - rootView: A `CourseListRootView` instance. */ -define(function(require) { - 'use strict'; - - var Backbone = require('backbone'), - Marionette = require('marionette'), - - CourseListView = require('course-list/list/views/course-list'), - - CourseListController; - - CourseListController = Marionette.Object.extend({ - initialize: function(options) { - this.options = options || {}; - this.listenTo(this.options.courseListCollection, 'sync', this.onCourseListCollectionUpdated); - this.onCourseListCollectionUpdated(this.options.courseListCollection); - }, - - /** - * Event handler for the 'showPage' event. Called by the - * router whenever a route method beginning with "show" has - * been triggered. Executes before the route method does. - */ - onShowPage: function() { - // Clear any existing alert - this.options.rootView.triggerMethod('clearError'); - }, - - onCourseListCollectionUpdated: function(collection) { - // Note that we currently assume that all the courses in - // the list were last updated at the same time. - if (collection.length) { - this.options.pageModel.set('lastUpdated', collection.at(0).get('last_updated')); - } - }, - - showCourseListPage: function(queryString) { - var listView = new CourseListView({ - collection: this.options.courseListCollection, - hasData: this.options.hasData, - tableName: gettext('Course List'), - trackSubject: 'course_list', - appClass: 'course-list', - trackingModel: this.options.trackingModel, - filteringEnabled: this.options.filteringEnabled - }), - collection = this.options.courseListCollection, - currentPage, - table; - - try { - collection.setStateFromQueryString(queryString); - this.options.rootView.showChildView('main', listView); - if (collection.isStale) { - // There was a querystring sort parameter that was different from the current collection state, so - // we have to sort and/or search the table. - - // We don't just do collection.fullCollection.sort() here because we've attached custom sortValue - // options to the columns via Backgrid to handle null values and we must call the sort function on - // the Backgrid table object for those custom sortValues to have an effect. - // Also, for some unknown reason, the Backgrid sort overwrites the currentPage, so we will save and - // restore the currentPage after the sort completes. - currentPage = collection.state.currentPage; - - if (collection.getSearchString() !== '') { - listView.getRegion('controls').currentView - .getRegion('search').currentView - .search(); - } - - table = listView.getRegion('results').currentView - .getRegion('main').currentView.table; - - // `table` will be undefined if the search resulted in an error or no results alert instead of a - // table of results. - if (table !== undefined) { - table.currentView.sort(collection.state.sortKey, - collection.state.order === 1 ? 'descending' : 'ascending'); - } - - collection.setPage(currentPage); - - collection.isStale = false; - } - } catch (e) { - // These JS errors occur when trying to parse invalid URL parameters - // FIXME: they also catch a whole lot of other kinds of errors where the alert message doesn't make much - // sense. - if (e instanceof RangeError || e instanceof TypeError) { - this.options.rootView.showAlert('error', gettext('Invalid Parameters'), - gettext('Sorry, we couldn\'t find any courses that matched that query.'), - {url: '#', text: gettext('Return to the Course List page.')}); - } else { - throw e; - } - } - - this.options.rootView.getRegion('navigation').empty(); - - this.options.pageModel.set('title', gettext('Course List')); - collection.trigger('loaded'); - - // track the "page" view - this.options.trackingModel.set('page', { - scope: 'insights', - lens: 'home', - report: '', - depth: '', - name: 'insights_home' - }); - this.options.trackingModel.trigger('segment:page'); - - return listView; - }, - - showNotFoundPage: function() { - var message = gettext("Sorry, we couldn't find the page you're looking for."), - notFoundView; - - this.options.pageModel.set('title', gettext('Page Not Found')); - - notFoundView = new (Backbone.View.extend({ - render: function() { - this.$el.text(message); - return this; - } - }))(); - this.options.rootView.showChildView('main', notFoundView); - - // track the "page" view - this.options.trackingModel.set('page', { - scope: 'insights', - lens: 'home', - report: 'not_found', - depth: '', - name: 'insights_home_not_found' - }); - this.options.trackingModel.trigger('segment:page'); - } +import Backbone from 'backbone'; +import Marionette from 'marionette'; + +import CourseListView from 'course-list/list/views/course-list'; + +export default class CourseListController extends Marionette.Object { + constructor(options) { + super(); + this.options = options || {}; + this.listenTo(this.options.courseListCollection, 'sync', this.onCourseListCollectionUpdated); + this.onCourseListCollectionUpdated(this.options.courseListCollection); + } + + /** + * Event handler for the 'showPage' event. Called by the + * router whenever a route method beginning with "show" has + * been triggered. Executes before the route method does. + */ + onShowPage() { + // Clear any existing alert + this.options.rootView.triggerMethod('clearError'); + } + + onCourseListCollectionUpdated(collection) { + // Note that we currently assume that all the courses in + // the list were last updated at the same time. + if (collection.length) { + this.options.pageModel.set('lastUpdated', collection.at(0).get('last_updated')); + } + } + + showCourseListPage(queryString) { + const listView = new CourseListView({ + collection: this.options.courseListCollection, + hasData: this.options.hasData, + tableName: gettext('Course List'), + trackSubject: 'course_list', + appClass: 'course-list', + trackingModel: this.options.trackingModel, + filteringEnabled: this.options.filteringEnabled, }); + const collection = this.options.courseListCollection; + let currentPage; + let table; + + try { + collection.setStateFromQueryString(queryString); + this.options.rootView.showChildView('main', listView); + if (collection.isStale) { + // There was a querystring sort parameter that was different from the current collection + // state, so we have to sort and/or search the table. + + // We don't just do collection.fullCollection.sort() here because we've attached custom + // sortValue options to the columns via Backgrid to handle null values and we must call the + // sort function on the Backgrid table object for those custom sortValues to have an effect. + // Also, for some unknown reason, the Backgrid sort overwrites the currentPage, so we will + // save and + // restore the currentPage after the sort completes. + currentPage = collection.state.currentPage; + + if (collection.getSearchString() !== '') { + listView.getRegion('controls').currentView.getRegion('search').currentView.search(); + } - return CourseListController; -}); + table = listView.getRegion('results').currentView.getRegion('main').currentView.table; + + // `table` will be undefined if the search resulted in an error or no results alert instead + // of a table of results. + if (table !== undefined) { + table.currentView.sort( + collection.state.sortKey, + collection.state.order === 1 ? 'descending' : 'ascending', + ); + } + + collection.setPage(currentPage); + + collection.isStale = false; + } + } catch (e) { + // These JS errors occur when trying to parse invalid URL parameters + // FIXME: they also catch a whole lot of other kinds of errors where the alert message doesn't + // make much sense. + if (e instanceof RangeError || e instanceof TypeError) { + this.options.rootView.showAlert( + 'error', + gettext('Invalid Parameters'), + gettext("Sorry, we couldn't find any courses that matched that query."), + { url: '#', text: gettext('Return to the Course List page.') }, + ); + } else { + throw e; + } + } + + this.options.rootView.getRegion('navigation').empty(); + + this.options.pageModel.set('title', gettext('Course List')); + collection.trigger('loaded'); + + // track the "page" view + this.options.trackingModel.set('page', { + scope: 'insights', + lens: 'home', + report: '', + depth: '', + name: 'insights_home', + }); + this.options.trackingModel.trigger('segment:page'); + + return listView; + } + + showNotFoundPage() { + const message = gettext("Sorry, we couldn't find the page you're looking for."); + + this.options.pageModel.set('title', gettext('Page Not Found')); + + const notFoundView = new (Backbone.View.extend({ + render() { + this.$el.text(message); + return this; + }, + }))(); + this.options.rootView.showChildView('main', notFoundView); + + // track the "page" view + this.options.trackingModel.set('page', { + scope: 'insights', + lens: 'home', + report: 'not_found', + depth: '', + name: 'insights_home_not_found', + }); + this.options.trackingModel.trigger('segment:page'); + } +} diff --git a/analytics_dashboard/static/apps/course-list/app/course-list-main.js b/analytics_dashboard/static/apps/course-list/app/course-list-main.js index e3fbe7afc..877dea3df 100644 --- a/analytics_dashboard/static/apps/course-list/app/course-list-main.js +++ b/analytics_dashboard/static/apps/course-list/app/course-list-main.js @@ -1,15 +1,18 @@ -require(['vendor/domReady!', 'jquery', 'load/init-page', - 'apps/course-list/app/app'], function(doc, $, page, CourseListApp) { - 'use strict'; - var modelData = page.models.courseModel, - app = new CourseListApp({ - containerSelector: '.course-list-app-container', - courseListJson: modelData.get('course_list_json'), - programsJson: modelData.get('programs_json'), - courseListDownloadUrl: modelData.get('course_list_download_url'), - filteringEnabled: modelData.get('enable_course_filters'), - passingUsersEnabled: modelData.get('enable_passing_users') - }); +import 'backgrid-paginator/backgrid-paginator.min.css'; - app.start(); +Promise.all([ + import('load/init-page'), + import('apps/course-list/app/app'), +]).then(([page, { default: CourseListApp }]) => { + const modelData = page.models.courseModel; + const app = new CourseListApp({ + containerSelector: '.course-list-app-container', + courseListJson: modelData.get('course_list_json'), + programsJson: modelData.get('programs_json'), + courseListDownloadUrl: modelData.get('course_list_download_url'), + filteringEnabled: modelData.get('enable_course_filters'), + passingUsersEnabled: modelData.get('enable_passing_users'), + }); + + app.start(); }); diff --git a/analytics_dashboard/static/apps/course-list/app/router.js b/analytics_dashboard/static/apps/course-list/app/router.js index 20f598800..99aaa5916 100644 --- a/analytics_dashboard/static/apps/course-list/app/router.js +++ b/analytics_dashboard/static/apps/course-list/app/router.js @@ -1,42 +1,41 @@ -define(function(require) { - 'use strict'; +import Marionette from 'marionette'; - var Marionette = require('marionette'), +class CourseListRouter extends Marionette.AppRouter { + // Routes intended to show a page in the app should map to method names + // beginning with "show", e.g. 'showCourseListPage'. + constructor(options) { + super(options); + this.options = options || {}; + this.courseListCollection = options.controller.options.courseListCollection; + this.listenTo(this.courseListCollection, 'loaded', this.updateUrl); + this.listenTo(this.courseListCollection, 'backgrid:refresh', this.updateUrl); + // Marionette.AppRouter.prototype.initialize.call(this, options); + } - CourseListRouter; + // This method is run before the route methods are run. + execute(callback, args, name) { + if (name.indexOf('show') === 0) { + this.options.controller.triggerMethod('showPage'); + } + if (callback) { + callback.apply(this, args); + } + } - CourseListRouter = Marionette.AppRouter.extend({ - // Routes intended to show a page in the app should map to method names - // beginning with "show", e.g. 'showCourseListPage'. - appRoutes: { - '(/)(?*queryString)': 'showCourseListPage', - '*notFound': 'showNotFoundPage' - }, - - // This method is run before the route methods are run. - execute: function(callback, args, name) { - if (name.indexOf('show') === 0) { - this.options.controller.triggerMethod('showPage'); - } - if (callback) { - callback.apply(this, args); - } - }, - - initialize: function(options) { - this.options = options || {}; - this.courseListCollection = options.controller.options.courseListCollection; - this.listenTo(this.courseListCollection, 'loaded', this.updateUrl); - this.listenTo(this.courseListCollection, 'backgrid:refresh', this.updateUrl); - Marionette.AppRouter.prototype.initialize.call(this, options); - }, - - // Called on CourseListCollection update. Converts the state of the collection (including any filters, - // searchers, sorts, or page numbers) into a url and then navigates the router to that url. - updateUrl: function() { - this.navigate(this.courseListCollection.getQueryString(), {replace: true, trigger: false}); - } + // Called on CourseListCollection update. Converts the state of the collection (including any + // filters, searchers, sorts, or page numbers) into a url and then navigates the router to that + // url. + updateUrl() { + this.navigate(this.courseListCollection.getQueryString(), { + replace: true, + trigger: false, }); + } +} + +CourseListRouter.prototype.appRoutes = { + '(/)(?*queryString)': 'showCourseListPage', + '*notFound': 'showNotFoundPage', +}; - return CourseListRouter; -}); +export default CourseListRouter; diff --git a/analytics_dashboard/static/apps/course-list/app/spec/controller-spec.js b/analytics_dashboard/static/apps/course-list/app/spec/controller-spec.js index a2802baf0..9703b97d9 100644 --- a/analytics_dashboard/static/apps/course-list/app/spec/controller-spec.js +++ b/analytics_dashboard/static/apps/course-list/app/spec/controller-spec.js @@ -1,115 +1,111 @@ -define(function(require) { - 'use strict'; +import CourseListCollection from 'course-list/common/collections/course-list'; +import CourseListController from 'course-list/app/controller'; +import RootView from 'components/root/views/root'; +import PageModel from 'components/generic-list/common/models/page'; +import TrackingModel from 'models/tracking-model'; - var CourseListCollection = require('course-list/common/collections/course-list'), - CourseListController = require('course-list/app/controller'), - RootView = require('components/root/views/root'), - PageModel = require('components/generic-list/common/models/page'), - TrackingModel = require('models/tracking-model'), +describe('CourseListController', () => { + // convenience method for asserting that we are on the course list page + function expectCourseListPage(controller) { + expect(controller.options.rootView.$('.course-list')).toBeInDOM(); + expect(controller.options.rootView.$('.course-list-header-region').html()).toContainText( + 'Course List', + ); + } - expectCourseListPage, - fakeCourse; + function fakeCourse(id, name) { + const count = Math.floor(Math.random() * 150) + 50; // rand int from 50 - 200 - describe('CourseListController', function() { - // convenience method for asserting that we are on the course list page - expectCourseListPage = function(controller) { - expect(controller.options.rootView.$('.course-list')).toBeInDOM(); - expect(controller.options.rootView.$('.course-list-header-region').html()).toContainText('Course List'); - }; + return { + course_id: id, + catalog_course_title: name, + catalog_course: name, + start_date: '2017-01-01', + end_date: '2017-04-01', + pacing_type: Math.random() > 0.5 ? 'instructor_paced' : 'self_paced', + count, + cumulative_count: Math.floor(count / 2) + count, + passing_users: 0, + enrollment_modes: { + audit: { + count: 0, + cumulative_count: 0, + count_change_7_days: 0, + }, + credit: { + count, + cumulative_count: Math.floor(count / 2) + count, + count_change_7_days: 5, + }, + verified: { + count: 0, + cumulative_count: 0, + count_change_7_days: 0, + }, + honor: { + count: 0, + cumulative_count: 0, + count_change_7_days: 0, + }, + professional: { + count: 0, + cumulative_count: 0, + count_change_7_days: 0, + }, + }, + created: '', + availability: 'unknown', + count_change_7_days: 0, + verified_enrollment: 0, + program_ids: [], + }; + } - fakeCourse = function(id, name) { - var count = parseInt(Math.random() * (150) + 50, 10); + beforeEach(() => { + const pageModel = new PageModel(); - return { - course_id: id, - catalog_course_title: name, - catalog_course: name, - start_date: '2017-01-01', - end_date: '2017-04-01', - pacing_type: Math.random() > 0.5 ? 'instructor_paced' : 'self_paced', - count: count, - cumulative_count: parseInt(count + (count / 2), 10), - passing_users: 0, - enrollment_modes: { - audit: { - count: 0, - cumulative_count: 0, - count_change_7_days: 0 - }, - credit: { - count: count, - cumulative_count: parseInt(count + (count / 2), 10), - count_change_7_days: 5 - }, - verified: { - count: 0, - cumulative_count: 0, - count_change_7_days: 0 - }, - honor: { - count: 0, - cumulative_count: 0, - count_change_7_days: 0 - }, - professional: { - count: 0, - cumulative_count: 0, - count_change_7_days: 0 - } - }, - created: '', - availability: 'unknown', - count_change_7_days: 0, - verified_enrollment: 0, - program_ids: [] - }; - }; - - beforeEach(function() { - var pageModel = new PageModel(); - - setFixtures('
'); - this.rootView = new RootView({ - el: '.root-view', - pageModel: pageModel, - appClass: 'course-list' - }); - this.rootView.render(); - this.course = fakeCourse('course1', 'Course'); - this.collection = new CourseListCollection([this.course]); - this.controller = new CourseListController({ - rootView: this.rootView, - courseListCollection: this.collection, - hasData: true, - pageModel: pageModel, - trackingModel: new TrackingModel() - }); - }); - - it('should show the course list page', function() { - this.controller.showCourseListPage(); - expectCourseListPage(this.controller); - }); + setFixtures('
'); + this.rootView = new RootView({ + el: '.root-view', + pageModel, + appClass: 'course-list', + }); + this.rootView.render(); + this.course = fakeCourse('course1', 'Course'); + this.collection = new CourseListCollection([this.course]); + this.controller = new CourseListController({ + rootView: this.rootView, + courseListCollection: this.collection, + hasData: true, + pageModel, + trackingModel: new TrackingModel(), + }); + }); - it('should show invalid parameters alert with invalid URL parameters', function() { - this.controller.showCourseListPage('text_search=foo='); - expect(this.controller.options.rootView.$('.course-list-alert-region').html()).toContainText( - 'Invalid Parameters' - ); - expect(this.controller.options.rootView.$('.course-list-main-region').html()).toBe(''); - }); + it('should show the course list page', () => { + this.controller.showCourseListPage(); + expectCourseListPage(this.controller); + }); - it('should show the not found page', function() { - this.controller.showNotFoundPage(); - // eslint-disable-next-line max-len - expect(this.rootView.$el.html()).toContainText("Sorry, we couldn't find the page you're looking for."); - }); + it('should show invalid parameters alert with invalid URL parameters', () => { + this.controller.showCourseListPage('text_search=foo='); + expect(this.controller.options.rootView.$('.course-list-alert-region').html()).toContainText( + 'Invalid Parameters', + ); + expect(this.controller.options.rootView.$('.course-list-main-region').html()).toBe(''); + }); - it('should sort the list with sort parameters', function() { - var secondCourse = fakeCourse('course2', 'X Course'); - this.collection.add(secondCourse); - this.controller.showCourseListPage('sortKey=catalog_course_title&order=desc'); - expect(this.collection.at(0).toJSON()).toEqual(secondCourse); - }); - }); + it('should show the not found page', () => { + this.controller.showNotFoundPage(); + // eslint-disable-next-line max-len + expect(this.rootView.$el.html()).toContainText( + "Sorry, we couldn't find the page you're looking for.", + ); + }); + it('should sort the list with sort parameters', () => { + const secondCourse = fakeCourse('course2', 'X Course'); + this.collection.add(secondCourse); + this.controller.showCourseListPage('sortKey=catalog_course_title&order=desc'); + expect(this.collection.at(0).toJSON()).toEqual(secondCourse); + }); }); diff --git a/analytics_dashboard/static/apps/course-list/app/spec/router-spec.js b/analytics_dashboard/static/apps/course-list/app/spec/router-spec.js index 7a887e5f9..5d4be5d46 100644 --- a/analytics_dashboard/static/apps/course-list/app/spec/router-spec.js +++ b/analytics_dashboard/static/apps/course-list/app/spec/router-spec.js @@ -1,90 +1,86 @@ -define(function(require) { - 'use strict'; +import Backbone from 'backbone'; +import CourseListCollection from 'course-list/common/collections/course-list'; +import CourseListController from 'course-list/app/controller'; +import CourseListRouter from 'course-list/app/router'; +import PageModel from 'components/generic-list/common/models/page'; - var Backbone = require('backbone'), - CourseListCollection = require('course-list/common/collections/course-list'), - CourseListController = require('course-list/app/controller'), - CourseListRouter = require('course-list/app/router'), - PageModel = require('components/generic-list/common/models/page'); - - describe('CourseListRouter', function() { - beforeEach(function() { - Backbone.history.start({silent: true}); - this.course = { - last_updated: new Date(2016, 1, 28) - }; - this.collection = new CourseListCollection([this.course]); - this.controller = new CourseListController({ - courseListCollection: this.collection, - pageModel: new PageModel() - }); - spyOn(this.controller, 'showCourseListPage').and.stub(); - spyOn(this.controller, 'showNotFoundPage').and.stub(); - spyOn(this.controller, 'onShowPage').and.stub(); - this.router = new CourseListRouter({ - controller: this.controller - }); - }); +describe('CourseListRouter', () => { + beforeEach(() => { + Backbone.history.start({ silent: true }); + this.course = { + last_updated: new Date(2016, 1, 28), + }; + this.collection = new CourseListCollection([this.course]); + this.controller = new CourseListController({ + courseListCollection: this.collection, + pageModel: new PageModel(), + }); + spyOn(this.controller, 'showCourseListPage').and.stub(); + spyOn(this.controller, 'showNotFoundPage').and.stub(); + spyOn(this.controller, 'onShowPage').and.stub(); + this.router = new CourseListRouter({ + controller: this.controller, + }); + }); - afterEach(function() { - // Clear previous route - this.router.navigate(''); - Backbone.history.stop(); - }); + afterEach(() => { + // Clear previous route + this.router.navigate(''); + Backbone.history.stop(); + }); - it('triggers a showPage event for pages beginning with "show"', function() { - this.router.navigate('foo', {trigger: true}); - expect(this.controller.onShowPage).toHaveBeenCalled(); - this.router.navigate('/', {trigger: true}); - expect(this.controller.onShowPage).toHaveBeenCalled(); - }); + it('triggers a showPage event for pages beginning with "show"', () => { + this.router.navigate('foo', { trigger: true }); + expect(this.controller.onShowPage).toHaveBeenCalled(); + this.router.navigate('/', { trigger: true }); + expect(this.controller.onShowPage).toHaveBeenCalled(); + }); - describe('showCourseListPage', function() { - beforeEach(function() { - // Backbone won't trigger a route unless we were on a previous url - this.router.navigate('initial-fragment', {trigger: false}); - }); + describe('showCourseListPage', () => { + beforeEach(() => { + // Backbone won't trigger a route unless we were on a previous url + this.router.navigate('initial-fragment', { trigger: false }); + }); - it('should trigger on an empty URL fragment', function() { - this.router.navigate('', {trigger: true}); - expect(this.controller.showCourseListPage).toHaveBeenCalled(); - }); + it('should trigger on an empty URL fragment', () => { + this.router.navigate('', { trigger: true }); + expect(this.controller.showCourseListPage).toHaveBeenCalled(); + }); - it('should trigger on a single forward slash', function() { - this.router.navigate('/', {trigger: true}); - expect(this.controller.showCourseListPage).toHaveBeenCalled(); - }); + it('should trigger on a single forward slash', () => { + this.router.navigate('/', { trigger: true }); + expect(this.controller.showCourseListPage).toHaveBeenCalled(); + }); - it('should trigger on a URL fragment with a querystring', function() { - var querystring = 'text_search=some_course'; - this.router.navigate('?' + querystring, {trigger: true}); - expect(this.controller.showCourseListPage).toHaveBeenCalledWith(querystring, null); - }); - }); + it('should trigger on a URL fragment with a querystring', () => { + const querystring = 'text_search=some_course'; + this.router.navigate(`?${querystring}`, { trigger: true }); + expect(this.controller.showCourseListPage).toHaveBeenCalledWith(querystring, null); + }); + }); - describe('showNotFoundPage', function() { - it('should trigger on unmatched URLs', function() { - this.router.navigate('this/does/not/match', {trigger: true}); - expect(this.controller.showNotFoundPage).toHaveBeenCalledWith('this/does/not/match', null); - }); - }); + describe('showNotFoundPage', () => { + it('should trigger on unmatched URLs', () => { + this.router.navigate('this/does/not/match', { trigger: true }); + expect(this.controller.showNotFoundPage).toHaveBeenCalledWith('this/does/not/match', null); + }); + }); - it('URL fragment is updated on CourseListCollection loaded', function(done) { - this.collection.state.currentPage = 2; - this.collection.once('loaded', function() { - expect(Backbone.history.getFragment()).toBe('?sortKey=catalog_course_title&order=asc&page=2'); - done(); - }); - this.collection.trigger('loaded'); - }); + it('URL fragment is updated on CourseListCollection loaded', (done) => { + this.collection.state.currentPage = 2; + this.collection.once('loaded', () => { + expect(Backbone.history.getFragment()).toBe('?sortKey=catalog_course_title&order=asc&page=2'); + done(); + }); + this.collection.trigger('loaded'); + }); - it('URL fragment is updated on CourseListCollection refresh', function(done) { - this.collection.state.currentPage = 2; - this.collection.once('backgrid:refresh', function() { - expect(Backbone.history.getFragment()).toBe('?sortKey=catalog_course_title&order=asc&page=2'); - done(); - }); - this.collection.trigger('backgrid:refresh'); - }); + it('URL fragment is updated on CourseListCollection refresh', (done) => { + this.collection.state.currentPage = 2; + this.collection.once('backgrid:refresh', () => { + expect(Backbone.history.getFragment()).toBe('?sortKey=catalog_course_title&order=asc&page=2'); + done(); }); + this.collection.trigger('backgrid:refresh'); + }); }); diff --git a/analytics_dashboard/static/apps/course-list/list/views/controls.js b/analytics_dashboard/static/apps/course-list/list/views/controls.js index bab591641..c286a8b69 100644 --- a/analytics_dashboard/static/apps/course-list/list/views/controls.js +++ b/analytics_dashboard/static/apps/course-list/list/views/controls.js @@ -9,7 +9,7 @@ define(function(require) { CheckboxFilter = require('components/filter/views/checkbox-filter'), CourseListSearch = require('course-list/list/views/search'), - courseListControlsTemplate = require('text!course-list/list/templates/controls.underscore'), + courseListControlsTemplate = require('course-list/list/templates/controls.underscore'), CourseListControlsView; diff --git a/analytics_dashboard/static/apps/course-list/list/views/course-id-and-name-cell.js b/analytics_dashboard/static/apps/course-list/list/views/course-id-and-name-cell.js index 5c09ed302..c450d38e2 100644 --- a/analytics_dashboard/static/apps/course-list/list/views/course-id-and-name-cell.js +++ b/analytics_dashboard/static/apps/course-list/list/views/course-id-and-name-cell.js @@ -8,7 +8,7 @@ define(function(require) { var _ = require('underscore'), RowHeaderCell = require('components/generic-list/list/views/row-header-cell'), - courseIdAndNameCellTemplate = require('text!course-list/list/templates/course-id-and-name-cell.underscore'), + courseIdAndNameCellTemplate = require('course-list/list/templates/course-id-and-name-cell.underscore'), CourseIdAndNameCell; diff --git a/analytics_dashboard/static/apps/course-list/list/views/course-list.js b/analytics_dashboard/static/apps/course-list/list/views/course-list.js index f1677c562..0b977d12e 100644 --- a/analytics_dashboard/static/apps/course-list/list/views/course-list.js +++ b/analytics_dashboard/static/apps/course-list/list/views/course-list.js @@ -16,7 +16,7 @@ define(function(require) { ListView = require('components/generic-list/list/views/list'), NumResultsView = require('components/generic-list/list/views/num-results'), - listTemplate = require('text!course-list/list/templates/list.underscore'), + listTemplate = require('course-list/list/templates/list.underscore'), CourseListView; diff --git a/analytics_dashboard/static/apps/course-list/list/views/search.js b/analytics_dashboard/static/apps/course-list/list/views/search.js index c2d1fb388..c29e58955 100644 --- a/analytics_dashboard/static/apps/course-list/list/views/search.js +++ b/analytics_dashboard/static/apps/course-list/list/views/search.js @@ -14,7 +14,7 @@ define(function(require) { _ = require('underscore'), Backgrid = require('backgrid'), - listSearchTemplate = require('text!components/generic-list/list/templates/search.underscore'), + listSearchTemplate = require('components/generic-list/list/templates/search.underscore'), CourseListSearch; diff --git a/analytics_dashboard/static/apps/course-list/list/views/table.js b/analytics_dashboard/static/apps/course-list/list/views/table.js index a73c2d047..9df0d362f 100644 --- a/analytics_dashboard/static/apps/course-list/list/views/table.js +++ b/analytics_dashboard/static/apps/course-list/list/views/table.js @@ -11,8 +11,8 @@ define(function(require) { CourseListBaseHeaderCell = require('course-list/list/views/base-header-cell'), CourseIdAndNameCell = require('course-list/list/views/course-id-and-name-cell'), - courseListTableTemplate = require('text!course-list/list/templates/table.underscore'), - notAvailableTemplate = require('text!course-list/list/templates/table-data-not-available.underscore'), + courseListTableTemplate = require('course-list/list/templates/table.underscore'), + notAvailableTemplate = require('course-list/list/templates/table-data-not-available.underscore'), Utils = require('utils/utils'), INTEGER_COLUMNS = ['count', 'cumulative_count', 'count_change_7_days', 'verified_enrollment', 'passing_users'], diff --git a/analytics_dashboard/static/apps/learners/app/controller.js b/analytics_dashboard/static/apps/learners/app/controller.js index 0d42c63ee..a87738a6e 100644 --- a/analytics_dashboard/static/apps/learners/app/controller.js +++ b/analytics_dashboard/static/apps/learners/app/controller.js @@ -21,7 +21,7 @@ define(function(require) { LoadingView = require('components/loading/views/loading-view'), ReturnLinkView = require('learners/detail/views/learner-return'), - rosterLoadingTemplate = require('text!components/loading/templates/plain-loading.underscore'), + rosterLoadingTemplate = require('components/loading/templates/plain-loading.underscore'), LearnersController; @@ -128,6 +128,7 @@ define(function(require) { }), learnerModel = this.options.learnerCollection.get(username) || new LearnerModel(), detailView; + this.options.rootView.showChildView('navigation', new ReturnLinkView({ queryString: this.options.learnerCollection.getQueryString() })); diff --git a/analytics_dashboard/static/apps/learners/app/learners-main.js b/analytics_dashboard/static/apps/learners/app/learners-main.js index bfa0e010f..82c1b0175 100644 --- a/analytics_dashboard/static/apps/learners/app/learners-main.js +++ b/analytics_dashboard/static/apps/learners/app/learners-main.js @@ -1,5 +1,9 @@ -require(['vendor/domReady!', 'jquery', 'load/init-page', 'apps/learners/app/app'], function(doc, $, page, LearnersApp) { +require('backgrid-paginator/backgrid-paginator.min.css'); +require('nprogress/nprogress.css'); + +require(['jquery', 'load/init-page', 'apps/learners/app/app'], function($, page, LearnersApp) { 'use strict'; + var modelData = page.models.courseModel, app = new LearnersApp({ courseId: modelData.get('courseId'), diff --git a/analytics_dashboard/static/apps/learners/detail/views/engagement-table.js b/analytics_dashboard/static/apps/learners/detail/views/engagement-table.js index 1c1624290..286d2cd48 100644 --- a/analytics_dashboard/static/apps/learners/detail/views/engagement-table.js +++ b/analytics_dashboard/static/apps/learners/detail/views/engagement-table.js @@ -7,7 +7,7 @@ define(function(require) { LearnerEngagementTableView; LearnerEngagementTableView = Marionette.LayoutView.extend({ - template: _.template(require('text!learners/detail/templates/engagement-table.underscore')), + template: _.template(require('learners/detail/templates/engagement-table.underscore')), regions: { main: '.learner-engagement-table.analytics-table' }, diff --git a/analytics_dashboard/static/apps/learners/detail/views/engagement-timeline.js b/analytics_dashboard/static/apps/learners/detail/views/engagement-timeline.js index 7b7829b40..e8059d36b 100644 --- a/analytics_dashboard/static/apps/learners/detail/views/engagement-timeline.js +++ b/analytics_dashboard/static/apps/learners/detail/views/engagement-timeline.js @@ -5,7 +5,7 @@ define(function(require) { Marionette = require('marionette'), TrendsView = require('views/trends-view'), - engagementTimelineTemplate = require('text!learners/detail/templates/engagement-timeline.underscore'), + engagementTimelineTemplate = require('learners/detail/templates/engagement-timeline.underscore'), LearnerEngagementTimelineView; diff --git a/analytics_dashboard/static/apps/learners/detail/views/learner-detail.js b/analytics_dashboard/static/apps/learners/detail/views/learner-detail.js index 1f46bc70e..a0433b28c 100644 --- a/analytics_dashboard/static/apps/learners/detail/views/learner-detail.js +++ b/analytics_dashboard/static/apps/learners/detail/views/learner-detail.js @@ -13,9 +13,9 @@ define(function(require) { LearnerNameView = require('learners/detail/views/learner-names'), LearnerSummaryFieldView = require('learners/detail/views/learner-summary-field'), LoadingView = require('components/loading/views/loading-view'), - chartLoadingTemplate = require('text!components/loading/templates/chart-loading.underscore'), - tableLoadingTemplate = require('text!components/loading/templates/table-loading.underscore'), - learnerDetailTemplate = require('text!learners/detail/templates/learner-detail.underscore'); + chartLoadingTemplate = require('components/loading/templates/chart-loading.underscore'), + tableLoadingTemplate = require('components/loading/templates/table-loading.underscore'), + learnerDetailTemplate = require('learners/detail/templates/learner-detail.underscore'); return Marionette.LayoutView.extend({ className: 'learner-detail-container', diff --git a/analytics_dashboard/static/apps/learners/detail/views/learner-names.js b/analytics_dashboard/static/apps/learners/detail/views/learner-names.js index ad959c3f0..8c0f5b023 100644 --- a/analytics_dashboard/static/apps/learners/detail/views/learner-names.js +++ b/analytics_dashboard/static/apps/learners/detail/views/learner-names.js @@ -5,7 +5,7 @@ define(function(require) { Marionette = require('marionette'); return Marionette.ItemView.extend({ - template: _.template(require('text!learners/detail/templates/learner-names.underscore')), + template: _.template(require('learners/detail/templates/learner-names.underscore')), events: { 'click .learner-email a': 'onEmailClick' }, diff --git a/analytics_dashboard/static/apps/learners/detail/views/learner-return.js b/analytics_dashboard/static/apps/learners/detail/views/learner-return.js index 799f0702e..0547428fc 100644 --- a/analytics_dashboard/static/apps/learners/detail/views/learner-return.js +++ b/analytics_dashboard/static/apps/learners/detail/views/learner-return.js @@ -5,7 +5,7 @@ define(function(require) { Marionette = require('marionette'); return Marionette.ItemView.extend({ - template: _.template(require('text!learners/detail/templates/return-to-learners.underscore')), + template: _.template(require('learners/detail/templates/return-to-learners.underscore')), templateHelpers: function() { return { diff --git a/analytics_dashboard/static/apps/learners/detail/views/learner-summary-field.js b/analytics_dashboard/static/apps/learners/detail/views/learner-summary-field.js index bd32d6b69..f2e54b2ec 100644 --- a/analytics_dashboard/static/apps/learners/detail/views/learner-summary-field.js +++ b/analytics_dashboard/static/apps/learners/detail/views/learner-summary-field.js @@ -4,7 +4,7 @@ define(function(require) { var _ = require('underscore'), Marionette = require('marionette'), - template = require('text!learners/detail/templates/learner-summary-field.underscore'), + template = require('learners/detail/templates/learner-summary-field.underscore'), LearnerSummaryFieldView; diff --git a/analytics_dashboard/static/apps/learners/roster/views/active-filters.js b/analytics_dashboard/static/apps/learners/roster/views/active-filters.js index 53f6dc4ed..91fdae410 100644 --- a/analytics_dashboard/static/apps/learners/roster/views/active-filters.js +++ b/analytics_dashboard/static/apps/learners/roster/views/active-filters.js @@ -5,7 +5,7 @@ define(function(require) { ActiveFiltersView = require('components/generic-list/list/views/active-filters'), LearnerCollection = require('learners/common/collections/learners'), - learnersActiveFiltersTemplate = require('text!learners/roster/templates/active-filters.underscore'), + learnersActiveFiltersTemplate = require('learners/roster/templates/active-filters.underscore'), LearnersActiveFiltersView; diff --git a/analytics_dashboard/static/apps/learners/roster/views/controls.js b/analytics_dashboard/static/apps/learners/roster/views/controls.js index c3a99a443..e518fb58f 100644 --- a/analytics_dashboard/static/apps/learners/roster/views/controls.js +++ b/analytics_dashboard/static/apps/learners/roster/views/controls.js @@ -10,7 +10,7 @@ define(function(require) { CheckboxFilter = require('components/filter/views/checkbox-filter'), DropDownFilter = require('components/filter/views/drop-down-filter'), LearnerSearch = require('learners/roster/views/search'), - rosterControlsTemplate = require('text!learners/roster/templates/controls.underscore'), + rosterControlsTemplate = require('learners/roster/templates/controls.underscore'), RosterControlsView; diff --git a/analytics_dashboard/static/apps/learners/roster/views/name-username-cell.js b/analytics_dashboard/static/apps/learners/roster/views/name-username-cell.js index b4b7d3a76..e9e1a0726 100644 --- a/analytics_dashboard/static/apps/learners/roster/views/name-username-cell.js +++ b/analytics_dashboard/static/apps/learners/roster/views/name-username-cell.js @@ -8,7 +8,7 @@ define(function(require) { var _ = require('underscore'), RowHeaderCell = require('components/generic-list/list/views/row-header-cell'), - nameUsernameCellTemplate = require('text!learners/roster/templates/name-username-cell.underscore'), + nameUsernameCellTemplate = require('learners/roster/templates/name-username-cell.underscore'), NameAndUsernameCell; diff --git a/analytics_dashboard/static/apps/learners/roster/views/roster.js b/analytics_dashboard/static/apps/learners/roster/views/roster.js index 7fad41a26..4844938a2 100644 --- a/analytics_dashboard/static/apps/learners/roster/views/roster.js +++ b/analytics_dashboard/static/apps/learners/roster/views/roster.js @@ -10,6 +10,7 @@ define(function(require) { var _ = require('underscore'), + ListUtils = require('components/utils/utils'), ListView = require('components/generic-list/list/views/list'), ActiveDateRangeView = require('learners/roster/views/activity-date-range'), ActiveFiltersView = require('learners/roster/views/active-filters'), @@ -17,7 +18,7 @@ define(function(require) { LearnerResultsView = require('learners/roster/views/results'), RosterControlsView = require('learners/roster/views/controls'), NumResultsView = require('components/generic-list/list/views/num-results'), - rosterTemplate = require('text!learners/roster/templates/roster.underscore'), + rosterTemplate = require('learners/roster/templates/roster.underscore'), LearnerRosterView; @@ -36,8 +37,17 @@ define(function(require) { }, initialize: function(options) { + var eventTransformers; + ListView.prototype.initialize.call(this, options); + eventTransformers = { + serverError: ListUtils.EventTransformers.serverErrorToAppError, + networkError: ListUtils.EventTransformers.networkErrorToAppError, + sync: ListUtils.EventTransformers.syncToClearError + }; + ListUtils.mapEvents(this.options.courseMetadata, eventTransformers, this); + this.childViews = [ { region: 'activeFilters', diff --git a/analytics_dashboard/static/apps/learners/roster/views/search.js b/analytics_dashboard/static/apps/learners/roster/views/search.js index d79707014..34981e110 100644 --- a/analytics_dashboard/static/apps/learners/roster/views/search.js +++ b/analytics_dashboard/static/apps/learners/roster/views/search.js @@ -14,7 +14,7 @@ define(function(require) { _ = require('underscore'), Backgrid = require('backgrid'), - listSearchTemplate = require('text!components/generic-list/list/templates/search.underscore'), + listSearchTemplate = require('components/generic-list/list/templates/search.underscore'), LearnerSearch; diff --git a/analytics_dashboard/static/apps/learners/roster/views/spec/roster-spec.js b/analytics_dashboard/static/apps/learners/roster/views/spec/roster-spec.js index 66cb88630..2f05c11dd 100644 --- a/analytics_dashboard/static/apps/learners/roster/views/spec/roster-spec.js +++ b/analytics_dashboard/static/apps/learners/roster/views/spec/roster-spec.js @@ -37,7 +37,7 @@ define(function(require) { var results, page = pageNum || 1; if (numPages) { - results = _.range(perPage * (page - 1), perPage * (page - 1) + perPage).map(function(index) { + results = _.range(perPage * (page - 1), (perPage * (page - 1)) + perPage).map(function(index) { return {name: 'user ' + index, username: 'user_' + index}; }); } else { @@ -620,7 +620,7 @@ define(function(require) { expectedRequestSubset[filterKey] = filterValue; expect(getLastRequestParams()).toEqual(jasmine.objectContaining(expectedRequestSubset)); } else { - expect(getLastRequestParams().hasOwnProperty(filterKey)).toBe(false); + expect(Object.prototype.hasOwnProperty.call(getLastRequestParams(), 'filterKey')).toBe(false); } getLastRequest().respond(200, {}, JSON.stringify(getResponseBody(1, 1))); expect($('option[value="' + filterValue + '"]')).toBeSelected(); diff --git a/analytics_dashboard/static/apps/learners/roster/views/table.js b/analytics_dashboard/static/apps/learners/roster/views/table.js index 2a06c141a..60ed626d9 100644 --- a/analytics_dashboard/static/apps/learners/roster/views/table.js +++ b/analytics_dashboard/static/apps/learners/roster/views/table.js @@ -11,7 +11,7 @@ define(function(require) { BaseHeaderCell = require('learners/roster/views/base-header-cell'), NameAndUsernameCell = require('learners/roster/views/name-username-cell'), Utils = require('utils/utils'), - learnerTableTemplate = require('text!learners/roster/templates/table.underscore'), + learnerTableTemplate = require('learners/roster/templates/table.underscore'), createEngagementCell, EngagementHeaderCell, diff --git a/analytics_dashboard/static/js/application-main.js b/analytics_dashboard/static/js/application-main.js index eb6698078..e0243aed1 100644 --- a/analytics_dashboard/static/js/application-main.js +++ b/analytics_dashboard/static/js/application-main.js @@ -1,13 +1,14 @@ /** * Load scripts needed across the application. */ +require('sass/style-application.scss'); +require('sass/themes/open-edx.scss'); -require(['bootstrap', - 'bootstrap_accessibility', - 'vendor/domReady!', 'load/init-page', - 'views/data-table-view', - 'views/announcement-view'], - function(bootstrap, bootstrapAccessibility, doc, page, DataTableView, AnnouncementView) { +require(['views/data-table-view', + 'views/announcement-view', + 'bootstrap-sass/assets/javascripts/bootstrap.js', + 'bootstrap-accessibility-plugin/plugins/js/bootstrap-accessibility'], + function(DataTableView, AnnouncementView) { 'use strict'; // Instantiate the announcement view(s) diff --git a/analytics_dashboard/static/js/common.js b/analytics_dashboard/static/js/common.js index 07b85d34d..1cb1a05c2 100644 --- a/analytics_dashboard/static/js/common.js +++ b/analytics_dashboard/static/js/common.js @@ -1,10 +1,12 @@ +require('sass/style-application.scss'); +require('sass/themes/open-edx.scss'); + require([ 'jquery', 'underscore', 'backbone', - 'bootstrap', - 'bootstrap_accessibility', - 'vendor/domReady!', + 'bootstrap-sass', + 'bootstrap-accessibility-plugin', 'load/init-page', 'js/application-main' ], function($) { diff --git a/analytics_dashboard/static/js/config.js b/analytics_dashboard/static/js/config.js deleted file mode 100644 index a0899250c..000000000 --- a/analytics_dashboard/static/js/config.js +++ /dev/null @@ -1,125 +0,0 @@ -/** - * This defines our libraries across the application. Each page - * should load this file. - */ - -require.config({ - baseUrl: '/static/', - waitSeconds: 60, - paths: { - jquery: 'bower_components/jquery/dist/jquery', - underscore: 'bower_components/underscore/underscore', - backbone: 'bower_components/backbone/backbone', - 'backbone.paginator': 'bower_components/backbone.paginator/lib/backbone.paginator.min', - 'backbone.wreqr': 'bower_components/backbone.wreqr/lib/backbone.wreqr.min', - 'backbone.babysitter': 'bower_components/backbone.babysitter/lib/backbone.babysitter.min', - backgrid: 'bower_components/backgrid/lib/backgrid', - 'backgrid-filter': 'bower_components/backgrid-filter/backgrid-filter.min', - 'backgrid-paginator': 'bower_components/backgrid-paginator/backgrid-paginator.min', - 'backgrid-moment-cell': 'bower_components/backgrid-moment-cell/backgrid-moment-cell.min', - bootstrap: 'bower_components/bootstrap-sass-official/assets/javascripts/bootstrap', - bootstrap_accessibility: 'bower_components/bootstrapaccessibilityplugin/plugins/js/bootstrap-accessibility', - models: 'js/models', - collections: 'js/collections', - views: 'js/views', - utils: 'js/utils', - load: 'js/load', - datatables: 'bower_components/datatables/media/js/jquery.dataTables', - dataTablesBootstrap: 'vendor/dataTables/dataTables.bootstrap', - naturalSort: 'bower_components/natural-sort/naturalSort', - d3: 'bower_components/d3/d3', - nvd3: 'bower_components/nvd3/build/nv.d3', - topojson: 'bower_components/topojson/topojson', - datamaps: 'bower_components/datamaps/dist/datamaps.world', - moment: 'bower_components/moment/min/moment-with-locales.min', - text: 'bower_components/requirejs-plugins/lib/text', - json: 'bower_components/requirejs-plugins/src/json', - cldr: 'bower_components/cldrjs/dist/cldr', - 'cldr-data': 'bower_components/cldr-data', - globalize: 'bower_components/globalize/dist/globalize', - globalization: 'js/utils/globalization', - marionette: 'bower_components/marionette/lib/core/backbone.marionette.min', - uitk: 'bower_components/edx-ui-toolkit/src/js', - // URI and its dependencies - URI: 'bower_components/uri.js/src/URI', - IPv6: 'bower_components/uri.js/src/IPv6', - punycode: 'bower_components/uri.js/src/punycode', - SecondLevelDomains: 'bower_components/uri.js/src/SecondLevelDomains', - learners: 'apps/learners', - 'course-list': 'apps/course-list', - components: 'apps/components', - 'axe-core': 'bower_components/axe-core/axe.min', - sinon: 'bower_components/sinon/lib/sinon', - nprogress: 'bower_components/nprogress/nprogress' - }, - wrapShim: true, - shim: { - bootstrap: { - deps: ['jquery'] - }, - bootstrap_accessibility: { - deps: ['bootstrap'] - }, - underscore: { - exports: '_' - }, - backbone: { - deps: ['underscore', 'jquery'], - exports: 'Backbone', - /* eslint-disable no-undef */ - init: function(_, $) { - 'use strict'; - Backbone.$ = $; - return Backbone; - } - /* eslint-enable no-undef */ - }, - backgrid: { - deps: ['backbone', 'underscore', 'jquery'], - exports: 'Backgrid' - }, - 'backgrid-filter': { - deps: ['backbone', 'underscore', 'backgrid'] - }, - 'backgrid-paginator': { - deps: ['backbone', 'underscore', 'jquery', 'backgrid'] - }, - 'backgrid-moment-cell': { - deps: ['backbone', 'underscore', 'moment', 'backgrid'] - }, - dataTablesBootstrap: { - deps: ['jquery', 'datatables'] - }, - naturalSort: { - exports: 'naturalSort' - }, - d3: { - exports: 'd3' - }, - nvd3: { - deps: ['d3'], - exports: 'nv' - }, - datamaps: { - deps: ['topojson', 'd3'], - exports: 'datamap' - }, - moment: { - noGlobal: true - }, - json: { - deps: ['text'] - }, - globalize: { - deps: ['jquery', 'cldr'], - exports: 'Globalize' - }, - globalization: { - deps: ['globalize'], - exports: 'Globalize' - }, - 'axe-core': { - exports: 'axe' - } - } -}); diff --git a/analytics_dashboard/static/js/engagement-content-main.js b/analytics_dashboard/static/js/engagement-content-main.js index 7c4b35ab5..eaa54225e 100644 --- a/analytics_dashboard/static/js/engagement-content-main.js +++ b/analytics_dashboard/static/js/engagement-content-main.js @@ -3,7 +3,7 @@ * the libraries and kicks off the application. */ -require(['vendor/domReady!', 'load/init-page'], function(doc, page) { +require(['load/init-page'], function(page) { 'use strict'; require(['underscore', 'views/data-table-view', 'views/trends-view'], function(_, DataTableView, TrendsView) { diff --git a/analytics_dashboard/static/js/engagement-video-content-main.js b/analytics_dashboard/static/js/engagement-video-content-main.js index 9e97895c7..950fe7568 100644 --- a/analytics_dashboard/static/js/engagement-video-content-main.js +++ b/analytics_dashboard/static/js/engagement-video-content-main.js @@ -1,7 +1,7 @@ /** * Called for displaying aggregate video charts and tables. Each bar is a collection of video views. */ -require(['vendor/domReady!', 'load/init-page'], function(doc, page) { +require(['load/init-page'], function(page) { 'use strict'; require(['d3', 'underscore', 'views/data-table-view', 'views/stacked-bar-view'], diff --git a/analytics_dashboard/static/js/engagement-video-timeline-main.js b/analytics_dashboard/static/js/engagement-video-timeline-main.js index cc4b4b489..7f9178242 100644 --- a/analytics_dashboard/static/js/engagement-video-timeline-main.js +++ b/analytics_dashboard/static/js/engagement-video-timeline-main.js @@ -2,11 +2,11 @@ * This is the first script called by the video timeline page and displays a * video timeline chart and data table. */ -require(['vendor/domReady!', 'load/init-page'], function(doc, page) { +require(['load/init-page'], function(page) { 'use strict'; require([ - 'uitk/disclosure/disclosure-view', + 'edx-ui-toolkit/src/js/disclosure/disclosure-view', 'underscore', 'views/data-table-view', 'views/iframe-view', diff --git a/analytics_dashboard/static/js/engagement-videos-main.js b/analytics_dashboard/static/js/engagement-videos-main.js index b92ef76ec..d8f1ce020 100644 --- a/analytics_dashboard/static/js/engagement-videos-main.js +++ b/analytics_dashboard/static/js/engagement-videos-main.js @@ -1,7 +1,7 @@ /** * Called for displaying a collection of video charts and tables. Each bar represents a single video. */ -require(['vendor/domReady!', 'load/init-page'], function(doc, page) { +require(['load/init-page'], function(page) { 'use strict'; require(['d3', 'underscore', 'views/data-table-view', 'views/stacked-bar-view'], diff --git a/analytics_dashboard/static/js/enrollment-activity-main.js b/analytics_dashboard/static/js/enrollment-activity-main.js index 0880cc044..87bf3c437 100644 --- a/analytics_dashboard/static/js/enrollment-activity-main.js +++ b/analytics_dashboard/static/js/enrollment-activity-main.js @@ -3,13 +3,13 @@ * the libraries and kicks off the application. */ -require(['vendor/domReady!', 'load/init-page'], function(doc, page) { +require(['load/init-page'], function(page) { 'use strict'; // this is your page specific code require(['underscore', - 'views/data-table-view', - 'views/stacked-trends-view'], + 'views/data-table-view', + 'views/stacked-trends-view'], function(_, DataTableView, StackedTrendsView) { var colors = ['#4BB4FB', '#898C8F', '#009CD3', '#B72667', '#442255', '#1E8142'], numericColumn = { diff --git a/analytics_dashboard/static/js/enrollment-demographics-age-main.js b/analytics_dashboard/static/js/enrollment-demographics-age-main.js index b517a5d28..0acc79057 100644 --- a/analytics_dashboard/static/js/enrollment-demographics-age-main.js +++ b/analytics_dashboard/static/js/enrollment-demographics-age-main.js @@ -3,7 +3,7 @@ * the libraries and kicks off the application. */ -require(['vendor/domReady!', 'load/init-page'], function(doc, page) { +require(['load/init-page'], function(page) { 'use strict'; require(['underscore', 'views/data-table-view', 'views/histogram-view'], diff --git a/analytics_dashboard/static/js/enrollment-demographics-education-main.js b/analytics_dashboard/static/js/enrollment-demographics-education-main.js index 71fd98866..c9e1d16b2 100644 --- a/analytics_dashboard/static/js/enrollment-demographics-education-main.js +++ b/analytics_dashboard/static/js/enrollment-demographics-education-main.js @@ -3,7 +3,7 @@ * the libraries and kicks off the application. */ -require(['vendor/domReady!', 'load/init-page'], function(doc, page) { +require(['load/init-page'], function(page) { 'use strict'; require(['underscore', 'views/data-table-view', 'views/discrete-bar-view'], diff --git a/analytics_dashboard/static/js/enrollment-demographics-gender-main.js b/analytics_dashboard/static/js/enrollment-demographics-gender-main.js index 5cdc735ac..ccddd3e1b 100644 --- a/analytics_dashboard/static/js/enrollment-demographics-gender-main.js +++ b/analytics_dashboard/static/js/enrollment-demographics-gender-main.js @@ -3,7 +3,7 @@ * the libraries and kicks off the application. */ -require(['vendor/domReady!', 'load/init-page'], function(doc, page) { +require(['load/init-page'], function(page) { 'use strict'; require(['underscore', 'views/data-table-view', 'views/discrete-bar-view'], diff --git a/analytics_dashboard/static/js/enrollment-geography-main.js b/analytics_dashboard/static/js/enrollment-geography-main.js index 3b3b44499..02d5eb9b0 100644 --- a/analytics_dashboard/static/js/enrollment-geography-main.js +++ b/analytics_dashboard/static/js/enrollment-geography-main.js @@ -3,7 +3,7 @@ * the libraries and kicks off the application. */ -require(['vendor/domReady!', 'load/init-page'], function(doc, page) { +require(['load/init-page'], function(page) { 'use strict'; // this is your page specific code diff --git a/analytics_dashboard/static/js/load/init-models.js b/analytics_dashboard/static/js/load/init-models.js index 36768a128..c2abe74e6 100644 --- a/analytics_dashboard/static/js/load/init-models.js +++ b/analytics_dashboard/static/js/load/init-models.js @@ -6,6 +6,7 @@ define(['jquery', 'models/course-model', 'models/tracking-model', 'models/user-model'], function($, CourseModel, TrackingModel, UserModel) { 'use strict'; + var courseModel = new CourseModel(), trackingModel = new TrackingModel(), userModel = new UserModel(); diff --git a/analytics_dashboard/static/js/load/init-tooltips.js b/analytics_dashboard/static/js/load/init-tooltips.js index 494c5cb0d..89c18ad2f 100644 --- a/analytics_dashboard/static/js/load/init-tooltips.js +++ b/analytics_dashboard/static/js/load/init-tooltips.js @@ -1,6 +1,7 @@ -define(['bootstrap'], +define(['bootstrap-sass/assets/javascripts/bootstrap/tooltip'], function() { 'use strict'; + // initializes the bootstrap style tooltips $('.has-tooltip').tooltip(); } diff --git a/analytics_dashboard/static/js/load/init-tracking.js b/analytics_dashboard/static/js/load/init-tracking.js index 2bf2fb0a2..603b04fbc 100644 --- a/analytics_dashboard/static/js/load/init-tracking.js +++ b/analytics_dashboard/static/js/load/init-tracking.js @@ -8,6 +8,7 @@ define(['jquery', 'underscore', 'views/clickable-view', 'views/hoverable-view', 'views/tracking-view', 'utils/utils'], function($, _, ClickableView, HoverableView, TrackingView, Utils) { 'use strict'; + var instrumentEvents = function(eventType, trackingViewClass, models) { _($('[data-track-type="' + eventType + '"]')).each(function(track) { // get the properties that we want to send back for with diff --git a/analytics_dashboard/static/js/performance-answer-distribution-main.js b/analytics_dashboard/static/js/performance-answer-distribution-main.js index 446d89640..8e3c0ec75 100644 --- a/analytics_dashboard/static/js/performance-answer-distribution-main.js +++ b/analytics_dashboard/static/js/performance-answer-distribution-main.js @@ -2,7 +2,7 @@ * This is the first script called by the performance answer distribution page. */ -require(['vendor/domReady!', 'load/init-page'], function(doc, page) { +require(['load/init-page'], function(page) { 'use strict'; require(['underscore', 'views/data-table-view', 'views/discrete-bar-view'], diff --git a/analytics_dashboard/static/js/performance-content-main.js b/analytics_dashboard/static/js/performance-content-main.js index 202a5a463..a58aab30d 100644 --- a/analytics_dashboard/static/js/performance-content-main.js +++ b/analytics_dashboard/static/js/performance-content-main.js @@ -1,4 +1,4 @@ -require(['vendor/domReady!', 'load/init-page'], function(doc, page) { +require(['load/init-page'], function(page) { 'use strict'; require(['d3', 'underscore', 'views/data-table-view', 'views/stacked-bar-view'], diff --git a/analytics_dashboard/static/js/performance-learning-outcomes-content-main.js b/analytics_dashboard/static/js/performance-learning-outcomes-content-main.js index b5665caa5..c51aba686 100644 --- a/analytics_dashboard/static/js/performance-learning-outcomes-content-main.js +++ b/analytics_dashboard/static/js/performance-learning-outcomes-content-main.js @@ -1,4 +1,4 @@ -require(['vendor/domReady!', 'load/init-page'], function(doc, page) { +require(['load/init-page'], function(page) { 'use strict'; require(['d3', 'underscore', 'views/data-table-view', 'views/stacked-bar-view'], diff --git a/analytics_dashboard/static/js/performance-learning-outcomes-section-main.js b/analytics_dashboard/static/js/performance-learning-outcomes-section-main.js index 107e927b7..d7e5e50d5 100644 --- a/analytics_dashboard/static/js/performance-learning-outcomes-section-main.js +++ b/analytics_dashboard/static/js/performance-learning-outcomes-section-main.js @@ -1,4 +1,4 @@ -require(['vendor/domReady!', 'load/init-page'], function(doc, page) { +require(['load/init-page'], function(page) { 'use strict'; require(['d3', 'underscore', 'views/data-table-view', 'views/stacked-bar-view'], diff --git a/analytics_dashboard/static/js/performance-problems-main.js b/analytics_dashboard/static/js/performance-problems-main.js index fa20b5ca4..fb8ec1e3f 100644 --- a/analytics_dashboard/static/js/performance-problems-main.js +++ b/analytics_dashboard/static/js/performance-problems-main.js @@ -1,4 +1,4 @@ -require(['vendor/domReady!', 'load/init-page'], function(doc, page) { +require(['load/init-page'], function(page) { 'use strict'; require(['d3', 'underscore', 'views/data-table-view', 'views/stacked-bar-view'], diff --git a/analytics_dashboard/static/js/test/browser-shims/gettext.js b/analytics_dashboard/static/js/test/browser-shims/gettext.js new file mode 100644 index 000000000..c5d5bd4c2 --- /dev/null +++ b/analytics_dashboard/static/js/test/browser-shims/gettext.js @@ -0,0 +1,5 @@ +module.exports = function(text) { + 'use strict'; + + return text; +}; diff --git a/analytics_dashboard/static/js/test/browser-shims/ngettext.js b/analytics_dashboard/static/js/test/browser-shims/ngettext.js new file mode 100644 index 000000000..6d642383a --- /dev/null +++ b/analytics_dashboard/static/js/test/browser-shims/ngettext.js @@ -0,0 +1,9 @@ +module.exports = function(singularString, pluralString, count) { + 'use strict'; + + if (count === 1) { + return singularString; + } else { + return pluralString; + } +}; diff --git a/analytics_dashboard/static/js/test/spec-runner.html b/analytics_dashboard/static/js/test/spec-runner.html deleted file mode 100644 index 8ec8dc3e8..000000000 --- a/analytics_dashboard/static/js/test/spec-runner.html +++ /dev/null @@ -1,15 +0,0 @@ - - - - - edX Analytics JS Tests - - - - - - - - - - diff --git a/analytics_dashboard/static/js/test/spec-runner.js b/analytics_dashboard/static/js/test/spec-runner.js deleted file mode 100644 index cb28b63b1..000000000 --- a/analytics_dashboard/static/js/test/spec-runner.js +++ /dev/null @@ -1,76 +0,0 @@ -/** - * This is where your tests go. It should happen automatically when you - * add files to the karma configuration. - */ -(function() { - 'use strict'; - - var isBrowser = window.__karma__ === undefined, // eslint-disable-line no-underscore-dangle - specs = [], - config = {}; - - // Two execution paths: browser or gulp - if (isBrowser) { - // The browser cannot read directories, so all files must be enumerated below. - specs = [ - config.baseUrl + 'js/spec/specs/attribute-view-spec.js', - config.baseUrl + 'js/spec/specs/course-model-spec.js', - config.baseUrl + 'js/spec/specs/data-table-spec.js', - config.baseUrl + 'js/spec/specs/tracking-model-spec.js', - config.baseUrl + 'js/spec/specs/trends-view-spec.js', - config.baseUrl + 'js/spec/specs/world-map-view-spec.js', - config.baseUrl + 'js/spec/specs/tracking-view-spec.js', - config.baseUrl + 'js/spec/specs/utils-spec.js', - config.baseUrl + 'js/spec/specs/globalization-spec.js', - config.baseUrl + 'js/spec/specs/announcement-view-spec.js' - ]; - } else { - // the Insights application loads gettext identity library via django, thus - // components reference gettext globally so a shim is added here to reflect - // the text so tests can be run if modules reference gettext - if (!window.gettext) { - window.gettext = function(text) { - return text; - }; - } - - if (!window.ngettext) { - window.ngettext = function(singularString, pluralString, count) { - if (count === 1) { - return singularString; - } else { - return pluralString; - } - }; - } - - // you can automatically get the test files using karma's configs - Object.keys(window.__karma__.files).forEach(function(file) { // eslint-disable-line no-underscore-dangle - if (/spec\.js$/.test(file)) { - specs.push(file); - } - }); - // This is where karma puts the files - config.baseUrl = '/base/analytics_dashboard/static/'; - - // Karma lets you list the test files here - config.deps = specs; - config.callback = window.__karma__.start; // eslint-disable-line no-underscore-dangle - } - - requirejs.config(config); - - // the browser needs to kick off jasmine. The gulp task does it through - // node - if (isBrowser) { - // jasmine 2.0 needs boot.js to run, which loads on a window load, so this is - // a hack - // http://stackoverflow.com/questions/19240302/does-jasmine-2-0-really-not-work-with-require-js - require(['boot'], function() { - require(specs, - function() { - window.onload(); - }); - }); - } -}()); diff --git a/analytics_dashboard/static/js/utils/globalization.js b/analytics_dashboard/static/js/utils/globalization.js index 86f2eb76a..d1268f105 100644 --- a/analytics_dashboard/static/js/utils/globalization.js +++ b/analytics_dashboard/static/js/utils/globalization.js @@ -9,10 +9,10 @@ if (window.language === undefined) { // should only occur in test environments */ define([ 'globalize', - 'json!cldr-data/supplemental/likelySubtags.json', - 'json!cldr-data/supplemental/numberingSystems.json', - 'json!cldr-data/main/' + window.language + '/numbers.json', // language fix already applied (e.g. en-gb is en-GB) - 'globalize/number' + 'cldr-data/supplemental/likelySubtags.json', + 'cldr-data/supplemental/numberingSystems.json', + 'cldr-data/main/' + window.language + '/numbers.json', // language fix already applied (e.g. en-gb is en-GB) + 'globalize/dist/globalize-runtime/number' ], function(Globalize, likelySubtags, numberingSystems, numbers) { 'use strict'; diff --git a/analytics_dashboard/static/js/views/data-table-view.js b/analytics_dashboard/static/js/views/data-table-view.js index 897a32782..24e436fee 100644 --- a/analytics_dashboard/static/js/views/data-table-view.js +++ b/analytics_dashboard/static/js/views/data-table-view.js @@ -1,4 +1,5 @@ -define(['dataTablesBootstrap', 'jquery', 'naturalSort', 'underscore', 'utils/utils', 'views/attribute-listener-view'], +define(['datatables-bootstrap3-plugin/media/js/datatables-bootstrap3', 'jquery', 'js-natural-sort/dist/naturalsort', + 'underscore', 'utils/utils', 'views/attribute-listener-view'], function(dt, $, naturalSort, _, Utils, AttributeListenerView) { 'use strict'; @@ -89,7 +90,7 @@ define(['dataTablesBootstrap', 'jquery', 'naturalSort', 'underscore', 'utils/uti } defs.push(def); - iColumn++; + iColumn += 1; }); return defs; }, @@ -153,7 +154,7 @@ define(['dataTablesBootstrap', 'jquery', 'naturalSort', 'underscore', 'utils/uti if (type === 'display' && !isNaN(value)) { display = Utils.localizeNumber(value); if (value >= maxNumber) { - display = display + '+'; + display += '+'; } } return display; diff --git a/analytics_dashboard/static/js/views/world-map-view.js b/analytics_dashboard/static/js/views/world-map-view.js index 37952e7bc..d570632c1 100644 --- a/analytics_dashboard/static/js/views/world-map-view.js +++ b/analytics_dashboard/static/js/views/world-map-view.js @@ -116,7 +116,8 @@ define(['jquery', 'd3', 'datamaps', 'underscore', 'utils/utils', 'views/attribut .attr('transform', function(d, i) { // move the legend color swatches to be arranged vertically var x = swatch.width, - y = canvasHeight - swatch.height * ranges.length + i * swatch.height - margins.bottom; + y = ((canvasHeight - (swatch.height * ranges.length)) + + (i * swatch.height)) - margins.bottom; return 'translate(' + [x, y].join(',') + ')'; }); diff --git a/analytics_dashboard/static/sass/_bootstrap-components.scss b/analytics_dashboard/static/sass/_bootstrap-components.scss index 82f5d2910..218221a2e 100644 --- a/analytics_dashboard/static/sass/_bootstrap-components.scss +++ b/analytics_dashboard/static/sass/_bootstrap-components.scss @@ -2,52 +2,52 @@ // we're commenting them out as they are phased out. // Core variables and mixins -@import "../bower_components/bootstrap-sass-official/assets/stylesheets/bootstrap/variables"; -@import "../bower_components/bootstrap-sass-official/assets/stylesheets/bootstrap/mixins"; +@import "../../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/variables"; +@import "../../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/mixins"; // Reset and dependencies -@import "../bower_components/bootstrap-sass-official/assets/stylesheets/bootstrap/normalize"; -@import "../bower_components/bootstrap-sass-official/assets/stylesheets/bootstrap/print"; -// @import "../bower_components/bootstrap-sass-official/assets/stylesheets/bootstrap/glyphicons"; +@import "../../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/normalize"; +@import "../../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/print"; +// @import "../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/glyphicons"; // Core CSS -// @import "../bower_components/bootstrap-sass-official/assets/stylesheets/bootstrap/scaffolding"; -@import "../bower_components/bootstrap-sass-official/assets/stylesheets/bootstrap/type"; -// @import "../bower_components/bootstrap-sass-official/assets/stylesheets/bootstrap/code"; -// @import "../bower_components/bootstrap-sass-official/assets/stylesheets/bootstrap/grid"; -@import "../bower_components/bootstrap-sass-official/assets/stylesheets/bootstrap/tables"; -@import "../bower_components/bootstrap-sass-official/assets/stylesheets/bootstrap/forms"; -@import "../bower_components/bootstrap-sass-official/assets/stylesheets/bootstrap/buttons"; +// @import "../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/scaffolding"; +@import "../../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/type"; +// @import "../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/code"; +// @import "../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/grid"; +@import "../../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/tables"; +@import "../../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/forms"; +@import "../../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/buttons"; // Components -// @import "../bower_components/bootstrap-sass-official/assets/stylesheets/bootstrap/component-animations"; -@import "../bower_components/bootstrap-sass-official/assets/stylesheets/bootstrap/dropdowns"; -// @import "../bower_components/bootstrap-sass-official/assets/stylesheets/bootstrap/button-groups"; -@import "../bower_components/bootstrap-sass-official/assets/stylesheets/bootstrap/input-groups"; -@import "../bower_components/bootstrap-sass-official/assets/stylesheets/bootstrap/navs"; -@import "../bower_components/bootstrap-sass-official/assets/stylesheets/bootstrap/navbar"; -// @import "../bower_components/bootstrap-sass-official/assets/stylesheets/bootstrap/breadcrumbs"; -@import "../bower_components/bootstrap-sass-official/assets/stylesheets/bootstrap/pagination"; -// @import "../bower_components/bootstrap-sass-official/assets/stylesheets/bootstrap/pager"; -// @import "../bower_components/bootstrap-sass-official/assets/stylesheets/bootstrap/labels"; -// @import "../bower_components/bootstrap-sass-official/assets/stylesheets/bootstrap/badges"; -// @import "../bower_components/bootstrap-sass-official/assets/stylesheets/bootstrap/jumbotron"; -// @import "../bower_components/bootstrap-sass-official/assets/stylesheets/bootstrap/thumbnails"; -//@import "../bower_components/bootstrap-sass-official/assets/stylesheets/bootstrap/alerts"; -// @import "../bower_components/bootstrap-sass-official/assets/stylesheets/bootstrap/progress-bars"; -// @import "../bower_components/bootstrap-sass-official/assets/stylesheets/bootstrap/media"; -// @import "../bower_components/bootstrap-sass-official/assets/stylesheets/bootstrap/list-group"; -// @import "../bower_components/bootstrap-sass-official/assets/stylesheets/bootstrap/panels"; -// @import "../bower_components/bootstrap-sass-official/assets/stylesheets/bootstrap/responsive-embed"; -// @import "../bower_components/bootstrap-sass-official/assets/stylesheets/bootstrap/wells"; -// @import "../bower_components/bootstrap-sass-official/assets/stylesheets/bootstrap/close"; +// @import "../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/component-animations"; +@import "../../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/dropdowns"; +// @import "../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/button-groups"; +@import "../../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/input-groups"; +@import "../../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/navs"; +@import "../../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/navbar"; +// @import "../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/breadcrumbs"; +@import "../../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/pagination"; +// @import "../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/pager"; +// @import "../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/labels"; +// @import "../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/badges"; +// @import "../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/jumbotron"; +// @import "../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/thumbnails"; +//@import "../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/alerts"; +// @import "../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/progress-bars"; +// @import "../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/media"; +// @import "../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/list-group"; +// @import "../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/panels"; +// @import "../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/responsive-embed"; +// @import "../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/wells"; +// @import "../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/close"; // Components w/ JavaScript -// @import "../bower_components/bootstrap-sass-official/assets/stylesheets/bootstrap/modals"; -@import "../bower_components/bootstrap-sass-official/assets/stylesheets/bootstrap/tooltip"; -// @import "../bower_components/bootstrap-sass-official/assets/stylesheets/bootstrap/popovers"; -// @import "../bower_components/bootstrap-sass-official/assets/stylesheets/bootstrap/carousel"; +// @import "../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/modals"; +@import "../../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/tooltip"; +// @import "../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/popovers"; +// @import "../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/carousel"; // Utility classes -@import "../bower_components/bootstrap-sass-official/assets/stylesheets/bootstrap/utilities"; -// @import "../bower_components/bootstrap-sass-official/assets/stylesheets/bootstrap/responsive-utilities"; +@import "../../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/utilities"; +// @import "../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/responsive-utilities"; diff --git a/analytics_dashboard/static/sass/_config-variables.scss b/analytics_dashboard/static/sass/_config-variables.scss index 94d06a841..72cd35700 100644 --- a/analytics_dashboard/static/sass/_config-variables.scss +++ b/analytics_dashboard/static/sass/_config-variables.scss @@ -279,6 +279,6 @@ $description-border-color: $gray-l2; $description-border: 1px solid $description-border-color; // FontAwesome font directory -$fa-font-path: "/static/bower_components/font-awesome/fonts"; +$fa-font-path: "/static/node_modules/font-awesome/fonts"; $navbar-padding: 30px; diff --git a/analytics_dashboard/static/sass/global/_fonts.scss b/analytics_dashboard/static/sass/global/_fonts.scss index 66b117a9f..51915ecc5 100644 --- a/analytics_dashboard/static/sass/global/_fonts.scss +++ b/analytics_dashboard/static/sass/global/_fonts.scss @@ -1,6 +1,6 @@ // Because the PL CSS is pre-compiled, the path to the actual location of the fonts // isn't set. Override the paths here. -$open-sans-font-path: '../bower_components/edx-pattern-library/pattern-library/fonts/OpenSans'; +$open-sans-font-path: '~edx-pattern-library/pattern-library/fonts/OpenSans'; @include font-face( 'Open Sans', '#{$open-sans-font-path}/OpenSans-Light-webfont', diff --git a/analytics_dashboard/static/sass/style-application.scss b/analytics_dashboard/static/sass/style-application.scss index 2119b3c56..b9e06f0cd 100644 --- a/analytics_dashboard/static/sass/style-application.scss +++ b/analytics_dashboard/static/sass/style-application.scss @@ -15,19 +15,18 @@ // vendor // -------------------- // Note: to avoid slow sass compilation (see FEDX-240), we pre-compile the pattern library to css (saved as a .scss -// file) in a bower post install script and import that here. -@import "../bower_components/edx-pattern-library/pattern-library/sass/edx-pattern-library-ltr-compiled"; +// file) in a npm post install script and import that here. +@import "~edx-pattern-library/pattern-library/sass/edx-pattern-library-ltr-compiled"; @import "bootstrap-components"; -@import "../bower_components/bootstrapaccessibilityplugin/plugins/css/bootstrap-accessibility"; -@import "../bower_components/font-awesome/scss/font-awesome"; -@import "../vendor/dataTables/dataTables.bootstrap"; -@import "../vendor/dataTables/dataTables.fontAwesome"; -@import "../bower_components/nvd3/build/nv.d3"; +@import "~bootstrap-accessibility-plugin/plugins/css/bootstrap-accessibility"; +@import "~font-awesome/scss/font-awesome-compiled"; +@import "~datatables-bootstrap3-plugin/media/css/datatables-bootstrap3.css"; +@import "~nvd3/build/nv.d3.scss"; // import edx pattern library variables so it's available to Insights styles -@import '../bower_components/bourbon/app/assets/stylesheets/bourbon'; -@import "../bower_components/edx-pattern-library/pattern-library/sass/global/settings"; +@import '~bourbon/app/assets/stylesheets/bourbon'; +@import "~edx-pattern-library/pattern-library/sass/global/settings"; // app styling // -------------------- diff --git a/analytics_dashboard/static/vendor/dataTables/dataTables.bootstrap.js b/analytics_dashboard/static/vendor/dataTables/dataTables.bootstrap.js deleted file mode 100644 index 6c1e70ed8..000000000 --- a/analytics_dashboard/static/vendor/dataTables/dataTables.bootstrap.js +++ /dev/null @@ -1,186 +0,0 @@ -/*! DataTables Bootstrap integration - * ©2011-2014 SpryMedia Ltd - datatables.net/license - */ - -/** - * DataTables integration for Bootstrap 3. This requires Bootstrap 3 and - * DataTables 1.10 or newer. - * - * This file sets the defaults and adds options to DataTables to style its - * controls using Bootstrap. See http://datatables.net/manual/styling/bootstrap - * for further information. - */ -(function(window, document, undefined){ - -var factory = function( $, DataTable ) { -"use strict"; - - -/* Set the defaults for DataTables initialisation */ -$.extend( true, DataTable.defaults, { - dom: - "<'row'<'col-xs-6'l><'col-xs-6'f>r>"+ - "t"+ - "<'row'<'col-xs-6'i><'col-xs-6'p>>", - renderer: 'bootstrap' -} ); - - -/* Default class modification */ -$.extend( DataTable.ext.classes, { - sWrapper: "dataTables_wrapper form-inline dt-bootstrap", - sFilterInput: "form-control input-sm", - sLengthSelect: "form-control input-sm" -} ); - - -/* Bootstrap paging button renderer */ -DataTable.ext.renderer.pageButton.bootstrap = function ( settings, host, idx, buttons, page, pages ) { - var api = new DataTable.Api( settings ); - var classes = settings.oClasses; - var lang = settings.oLanguage.oPaginate; - var btnDisplay, btnClass; - - var attach = function( container, buttons ) { - var i, ien, node, button; - var clickHandler = function ( e ) { - e.preventDefault(); - if ( e.data.action !== 'ellipsis' ) { - api.page( e.data.action ).draw( false ); - } - }; - - for ( i=0, ien=buttons.length ; i 0 ? - '' : ' disabled'); - break; - - case 'previous': - btnDisplay = lang.sPrevious; - btnClass = button + (page > 0 ? - '' : ' disabled'); - break; - - case 'next': - btnDisplay = lang.sNext; - btnClass = button + (page < pages-1 ? - '' : ' disabled'); - break; - - case 'last': - btnDisplay = lang.sLast; - btnClass = button + (page < pages-1 ? - '' : ' disabled'); - break; - - default: - btnDisplay = button + 1; - btnClass = page === button ? - 'active' : ''; - break; - } - - if ( btnDisplay ) { - node = $('
  • ', { - 'class': classes.sPageButton+' '+btnClass, - 'aria-controls': settings.sTableId, - 'tabindex': settings.iTabIndex, - 'id': idx === 0 && typeof button === 'string' ? - settings.sTableId +'_'+ button : - null - } ) - .append( $('', { - 'href': '#' - } ) - .html( btnDisplay ) - ) - .appendTo( container ); - - settings.oApi._fnBindAction( - node, {action: button}, clickHandler - ); - } - } - } - }; - - attach( - $(host).empty().html('
      ').children('ul'), - buttons - ); -}; - - -/* - * TableTools Bootstrap compatibility - * Required TableTools 2.1+ - */ -if ( DataTable.TableTools ) { - // Set the classes that TableTools uses to something suitable for Bootstrap - $.extend( true, DataTable.TableTools.classes, { - "container": "DTTT btn-group", - "buttons": { - "normal": "btn btn-default", - "disabled": "disabled" - }, - "collection": { - "container": "DTTT_dropdown dropdown-menu", - "buttons": { - "normal": "", - "disabled": "disabled" - } - }, - "print": { - "info": "DTTT_print_info modal" - }, - "select": { - "row": "active" - } - } ); - - // Have the collection use a bootstrap compatible drop down - $.extend( true, DataTable.TableTools.DEFAULTS.oTags, { - "collection": { - "container": "ul", - "button": "li", - "liner": "a" - } - } ); -} - -}; // /factory - - -// Define as an AMD module if possible -if ( typeof define === 'function' && define.amd ) { - define( ['jquery', 'datatables'], factory ); -} -else if ( typeof exports === 'object' ) { - // Node/CommonJS - factory( require('jquery'), require('datatables') ); -} -else if ( jQuery ) { - // Otherwise simply initialise as normal, stopping multiple evaluation - factory( jQuery, jQuery.fn.dataTable ); -} - - -})(window, document); - diff --git a/analytics_dashboard/static/vendor/dataTables/dataTables.bootstrap.scss b/analytics_dashboard/static/vendor/dataTables/dataTables.bootstrap.scss deleted file mode 100644 index d2546fac4..000000000 --- a/analytics_dashboard/static/vendor/dataTables/dataTables.bootstrap.scss +++ /dev/null @@ -1,283 +0,0 @@ -$dt-image-path: "../../vendor/dataTables/images" !default; - -div.dataTables_length label { - font-weight: normal; - float: left; - text-align: left; -} - -div.dataTables_length select { - width: 75px; -} - -div.dataTables_filter label { - font-weight: normal; - float: right; -} - -div.dataTables_filter input { - width: 16em; -} - -div.dataTables_info { - padding-top: 8px; -} - -div.dataTables_paginate { - float: right; - margin: 0; -} - -div.dataTables_paginate ul.pagination { - margin: 2px 0; - white-space: nowrap; -} - -table.dataTable td, -table.dataTable th { - -webkit-box-sizing: content-box; - -moz-box-sizing: content-box; - box-sizing: content-box; -} - - -table.dataTable { - clear: both; - margin-top: 6px !important; - margin-bottom: 6px !important; - max-width: none !important; -} - -table.dataTable thead .sorting, -table.dataTable thead .sorting_asc, -table.dataTable thead .sorting_desc, -table.dataTable thead .sorting_asc_disabled, -table.dataTable thead .sorting_desc_disabled { - cursor: pointer; -} - -table.dataTable thead .sorting { background: url('#{$dt-image-path}/sort_both.png') no-repeat center right; } -table.dataTable thead .sorting_asc { background: url('#{$dt-image-path}/sort_asc.png') no-repeat center right; } -table.dataTable thead .sorting_desc { background: url('#{$dt-image-path}/sort_desc.png') no-repeat center right; } - -table.dataTable thead .sorting_asc_disabled { background: url('#{$dt-image-path}/sort_asc_disabled.png') no-repeat center right; } -table.dataTable thead .sorting_desc_disabled { background: url('#{$dt-image-path}/sort_desc_disabled.png') no-repeat center right; } - -table.dataTable thead > tr > th { - padding-left: 18px; - padding-right: 18px; -} - -table.dataTable th:active { - outline: none; -} - -/* Scrolling */ -div.dataTables_scrollHead table { - margin-bottom: 0 !important; - border-bottom-left-radius: 0; - border-bottom-right-radius: 0; -} - -div.dataTables_scrollHead table thead tr:last-child th:first-child, -div.dataTables_scrollHead table thead tr:last-child td:first-child { - border-bottom-left-radius: 0 !important; - border-bottom-right-radius: 0 !important; -} - -div.dataTables_scrollBody table { - border-top: none; - margin-top: 0 !important; - margin-bottom: 0 !important; -} - -div.dataTables_scrollBody tbody tr:first-child th, -div.dataTables_scrollBody tbody tr:first-child td { - border-top: none; -} - -div.dataTables_scrollFoot table { - margin-top: 0 !important; - border-top: none; -} - -/* Frustratingly the border-collapse:collapse used by Bootstrap makes the column - width calculations when using scrolling impossible to align columns. We have - to use separate - */ -table.table-bordered.dataTable { - border-collapse: separate !important; -} -table.table-bordered thead th, -table.table-bordered thead td { - border-left-width: 0; - border-top-width: 0; -} -table.table-bordered tbody th, -table.table-bordered tbody td { - border-left-width: 0; - border-bottom-width: 0; -} -table.table-bordered th:last-child, -table.table-bordered td:last-child { - border-right-width: 0; -} -div.dataTables_scrollHead table.table-bordered { - border-bottom-width: 0; -} - - - - -/* - * TableTools styles - */ -.table tbody tr.active td, -.table tbody tr.active th { - background-color: #08C; - color: white; -} - -.table tbody tr.active:hover td, -.table tbody tr.active:hover th { - background-color: #0075b0 !important; -} - -.table tbody tr.active a { - color: white; -} - -.table-striped tbody tr.active:nth-child(odd) td, -.table-striped tbody tr.active:nth-child(odd) th { - background-color: #017ebc; -} - -table.DTTT_selectable tbody tr { - cursor: pointer; -} - -div.DTTT .btn { - color: #333 !important; - font-size: 12px; -} - -div.DTTT .btn:hover { - text-decoration: none !important; -} - -ul.DTTT_dropdown.dropdown-menu { - z-index: 2003; -} - -ul.DTTT_dropdown.dropdown-menu a { - color: #333 !important; /* needed only when demo_page.css is included */ -} - -ul.DTTT_dropdown.dropdown-menu li { - position: relative; -} - -ul.DTTT_dropdown.dropdown-menu li:hover a { - background-color: #0088cc; - color: white !important; -} - -div.DTTT_collection_background { - z-index: 2002; -} - -/* TableTools information display */ -div.DTTT_print_info.modal { - height: 150px; - margin-top: -75px; - text-align: center; -} - -div.DTTT_print_info h6 { - font-weight: normal; - font-size: 28px; - line-height: 28px; - margin: 1em; -} - -div.DTTT_print_info p { - font-size: 14px; - line-height: 20px; -} - -div.dataTables_processing { - position: absolute; - top: 50%; - left: 50%; - width: 100%; - height: 40px; - margin-left: -50%; - margin-top: -25px; - padding-top: 20px; - text-align: center; - font-size: 1.2em; - background-color: white; - background: -webkit-gradient(linear, left top, right top, color-stop(0%, rgba(255,255,255,0)), color-stop(25%, rgba(255,255,255,0.9)), color-stop(75%, rgba(255,255,255,0.9)), color-stop(100%, rgba(255,255,255,0))); - background: -webkit-linear-gradient(left, rgba(255,255,255,0) 0%, rgba(255,255,255,0.9) 25%, rgba(255,255,255,0.9) 75%, rgba(255,255,255,0) 100%); - background: -moz-linear-gradient(left, rgba(255,255,255,0) 0%, rgba(255,255,255,0.9) 25%, rgba(255,255,255,0.9) 75%, rgba(255,255,255,0) 100%); - background: -ms-linear-gradient(left, rgba(255,255,255,0) 0%, rgba(255,255,255,0.9) 25%, rgba(255,255,255,0.9) 75%, rgba(255,255,255,0) 100%); - background: -o-linear-gradient(left, rgba(255,255,255,0) 0%, rgba(255,255,255,0.9) 25%, rgba(255,255,255,0.9) 75%, rgba(255,255,255,0) 100%); - background: linear-gradient(to right, rgba(255,255,255,0) 0%, rgba(255,255,255,0.9) 25%, rgba(255,255,255,0.9) 75%, rgba(255,255,255,0) 100%); -} - - - -/* - * FixedColumns styles - */ -div.DTFC_LeftHeadWrapper table, -div.DTFC_LeftFootWrapper table, -div.DTFC_RightHeadWrapper table, -div.DTFC_RightFootWrapper table, -table.DTFC_Cloned tr.even { - background-color: white; - margin-bottom: 0; -} - -div.DTFC_RightHeadWrapper table , -div.DTFC_LeftHeadWrapper table { - margin-bottom: 0 !important; - border-top-right-radius: 0 !important; - border-bottom-left-radius: 0 !important; - border-bottom-right-radius: 0 !important; -} - -div.DTFC_RightHeadWrapper table thead tr:last-child th:first-child, -div.DTFC_RightHeadWrapper table thead tr:last-child td:first-child, -div.DTFC_LeftHeadWrapper table thead tr:last-child th:first-child, -div.DTFC_LeftHeadWrapper table thead tr:last-child td:first-child { - border-bottom-left-radius: 0 !important; - border-bottom-right-radius: 0 !important; -} - -div.DTFC_RightBodyWrapper table, -div.DTFC_LeftBodyWrapper table { - border-top: none; - margin: 0 !important; -} - -div.DTFC_RightBodyWrapper tbody tr:first-child th, -div.DTFC_RightBodyWrapper tbody tr:first-child td, -div.DTFC_LeftBodyWrapper tbody tr:first-child th, -div.DTFC_LeftBodyWrapper tbody tr:first-child td { - border-top: none; -} - -div.DTFC_RightFootWrapper table, -div.DTFC_LeftFootWrapper table { - border-top: none; -} - - -/* - * FixedHeader styles - */ -div.FixedHeader_Cloned table { - margin: 0 !important -} - diff --git a/analytics_dashboard/static/vendor/dataTables/dataTables.fontAwesome.scss b/analytics_dashboard/static/vendor/dataTables/dataTables.fontAwesome.scss deleted file mode 100644 index 509187bba..000000000 --- a/analytics_dashboard/static/vendor/dataTables/dataTables.fontAwesome.scss +++ /dev/null @@ -1,156 +0,0 @@ -/*! - * DataTables + Font Awesome integration - * License: MIT - http://datatables.net/license - */ - -/* - * Sort styling - */ -table.dataTable thead th { - position: relative; - background-image: none !important; /* Remove the DataTables bootstrap integration styling */ -} - -table.dataTable thead th.sorting:after, -table.dataTable thead th.sorting_asc:after, -table.dataTable thead th.sorting_desc:after { - position: absolute; - top: 12px; - right: 8px; - display: block; - font-family: FontAwesome; -} - -table.dataTable thead th.sorting:after { - content: "\f0dc"; - color: #ddd; - font-size: 0.8em; - padding-top: 0.12em; -} -table.dataTable thead th.sorting_asc:after { - content: "\f0de"; -} -table.dataTable thead th.sorting_desc:after { - content: "\f0dd"; -} - -div.dataTables_scrollBody table.dataTable thead th.sorting:after, -div.dataTables_scrollBody table.dataTable thead th.sorting_asc:after, -div.dataTables_scrollBody table.dataTable thead th.sorting_desc:after { - content: ""; -} - -/* In Bootstrap and Foundation the padding top is a little different from the DataTables stylesheet */ -table.table thead th.sorting:after, -table.table thead th.sorting_asc:after, -table.table thead th.sorting_desc:after { - top: 8px; -} - - -/* - * DataTables style pagination controls - */ -div.dataTables_paginate a.paginate_button.first, -div.dataTables_paginate a.paginate_button.previous { - position: relative; - padding-left: 24px; -} - -div.dataTables_paginate a.paginate_button.next, -div.dataTables_paginate a.paginate_button.last { - position: relative; - padding-right: 24px; -} - -div.dataTables_paginate a.first:before, -div.dataTables_paginate a.previous:before { - position: absolute; - top: 8px; - left: 10px; - display: block; - font-family: FontAwesome; -} - -div.dataTables_paginate a.next:after, -div.dataTables_paginate a.last:after { - position: absolute; - top: 8px; - right: 10px; - display: block; - font-family: FontAwesome; -} - -div.dataTables_paginate a.first:before { - content: "\f100"; -} - -div.dataTables_paginate a.previous:before { - content: "\f104"; -} - -div.dataTables_paginate a.next:after { - content: "\f105"; -} - -div.dataTables_paginate a.last:after { - content: "\f101"; -} - - -/* - * Bootstrap and foundation style pagination controls - */ -div.dataTables_paginate li.first > a, -div.dataTables_paginate li.previous > a { - position: relative; - padding-left: 24px; -} - -div.dataTables_paginate li.next > a, -div.dataTables_paginate li.last > a { - position: relative; - padding-right: 24px; -} - -div.dataTables_paginate li.first a:before, -div.dataTables_paginate li.previous a:before { - position: absolute; - top: 6px; - left: 10px; - display: block; - font-family: FontAwesome; -} - -div.dataTables_paginate li.next a:after, -div.dataTables_paginate li.last a:after { - position: absolute; - top: 6px; - right: 10px; - display: block; - font-family: FontAwesome; -} - -div.dataTables_paginate li.first a:before { - content: "\f100"; -} - -div.dataTables_paginate li.previous a:before { - content: "\f104"; -} - -div.dataTables_paginate li.next a:after { - content: "\f105"; -} - -div.dataTables_paginate li.last a:after { - content: "\f101"; -} - -/* In Foundation we don't want the padding like in bootstrap */ -div.columns div.dataTables_paginate li.first a:before, -div.columns div.dataTables_paginate li.previous a:before, -div.columns div.dataTables_paginate li.next a:after, -div.columns div.dataTables_paginate li.last a:after { - top: 0; -} diff --git a/analytics_dashboard/static/vendor/domReady.js b/analytics_dashboard/static/vendor/domReady.js deleted file mode 100644 index 2b5412209..000000000 --- a/analytics_dashboard/static/vendor/domReady.js +++ /dev/null @@ -1,129 +0,0 @@ -/** - * @license RequireJS domReady 2.0.1 Copyright (c) 2010-2012, The Dojo Foundation All Rights Reserved. - * Available via the MIT or new BSD license. - * see: http://github.com/requirejs/domReady for details - */ -/*jslint */ -/*global require: false, define: false, requirejs: false, - window: false, clearInterval: false, document: false, - self: false, setInterval: false */ - - -define(function () { - 'use strict'; - - var isTop, testDiv, scrollIntervalId, - isBrowser = typeof window !== "undefined" && window.document, - isPageLoaded = !isBrowser, - doc = isBrowser ? document : null, - readyCalls = []; - - function runCallbacks(callbacks) { - var i; - for (i = 0; i < callbacks.length; i += 1) { - callbacks[i](doc); - } - } - - function callReady() { - var callbacks = readyCalls; - - if (isPageLoaded) { - //Call the DOM ready callbacks - if (callbacks.length) { - readyCalls = []; - runCallbacks(callbacks); - } - } - } - - /** - * Sets the page as loaded. - */ - function pageLoaded() { - if (!isPageLoaded) { - isPageLoaded = true; - if (scrollIntervalId) { - clearInterval(scrollIntervalId); - } - - callReady(); - } - } - - if (isBrowser) { - if (document.addEventListener) { - //Standards. Hooray! Assumption here that if standards based, - //it knows about DOMContentLoaded. - document.addEventListener("DOMContentLoaded", pageLoaded, false); - window.addEventListener("load", pageLoaded, false); - } else if (window.attachEvent) { - window.attachEvent("onload", pageLoaded); - - testDiv = document.createElement('div'); - try { - isTop = window.frameElement === null; - } catch (e) {} - - //DOMContentLoaded approximation that uses a doScroll, as found by - //Diego Perini: http://javascript.nwbox.com/IEContentLoaded/, - //but modified by other contributors, including jdalton - if (testDiv.doScroll && isTop && window.external) { - scrollIntervalId = setInterval(function () { - try { - testDiv.doScroll(); - pageLoaded(); - } catch (e) {} - }, 30); - } - } - - //Check if document already complete, and if so, just trigger page load - //listeners. Latest webkit browsers also use "interactive", and - //will fire the onDOMContentLoaded before "interactive" but not after - //entering "interactive" or "complete". More details: - //http://dev.w3.org/html5/spec/the-end.html#the-end - //http://stackoverflow.com/questions/3665561/document-readystate-of-interactive-vs-ondomcontentloaded - //Hmm, this is more complicated on further use, see "firing too early" - //bug: https://github.com/requirejs/domReady/issues/1 - //so removing the || document.readyState === "interactive" test. - //There is still a window.onload binding that should get fired if - //DOMContentLoaded is missed. - if (document.readyState === "complete") { - pageLoaded(); - } - } - - /** START OF PUBLIC API **/ - - /** - * Registers a callback for DOM ready. If DOM is already ready, the - * callback is called immediately. - * @param {Function} callback - */ - function domReady(callback) { - if (isPageLoaded) { - callback(doc); - } else { - readyCalls.push(callback); - } - return domReady; - } - - domReady.version = '2.0.1'; - - /** - * Loader Plugin API method - */ - domReady.load = function (name, req, onLoad, config) { - if (config.isBuild) { - onLoad(null); - } else { - domReady(onLoad); - } - }; - - /** END OF PUBLIC API **/ - - return domReady; -}); diff --git a/analytics_dashboard/templates/base.html b/analytics_dashboard/templates/base.html index d3d263b4c..54373829c 100644 --- a/analytics_dashboard/templates/base.html +++ b/analytics_dashboard/templates/base.html @@ -2,9 +2,9 @@ Base template pages. {% endcomment %} -{% load staticfiles %} -{% load compress %} {% load dashboard_extras %} +{% load get_files from webpack_loader %} +{% load render_bundle from webpack_loader %} @@ -21,14 +21,12 @@ adding additional stylesheets in your page when extending the base. {% endcomment %} + {% get_files 'application-main' 'css' as common_css %} + {% for css_file in common_css %} + + {% endfor %} + {% block stylesheets %} - {% compress css %} - - {% captureas theme_scss %} - {% settings_value 'THEME_SCSS' %} - {% endcaptureas %} - - {% endcompress %} {% endblock %} @@ -39,4 +37,11 @@ {% block body %}{% endblock %} + +{% block javascript %} + {# webpack loading #} + {% render_bundle 'manifest' %} + {% render_bundle 'globalization' %} + {% render_bundle 'application-main' %} +{% endblock javascript %} diff --git a/analytics_dashboard/templates/base_dashboard.html b/analytics_dashboard/templates/base_dashboard.html index e383c1ba8..3c2bc0bfb 100644 --- a/analytics_dashboard/templates/base_dashboard.html +++ b/analytics_dashboard/templates/base_dashboard.html @@ -3,9 +3,7 @@ {% load i18n %} {% load staticfiles %} -{% load compress %} {% load dashboard_extras %} -{% load rjs %} {% block body %} @@ -72,24 +70,6 @@

      {{ page_title }}

      {% endif %} - {% compress js %} - - - - {# Note: django-compressor does not recognize the data-main attribute. Load the main script separately. #} - - {% endcompress %} - - - {# Note: These blocks are purposely separated from the one above so that browsers cache the common JS instead of downloading a single, large file for each page. #} - {# The 'uncompressed_javascript' block should be used for small scripts that do not need to block the page load. It's useful for factory javascript used to dynamically pass server data to the front via django templating, which does not work with offline compression. #} - {% block uncompressed_javascript %} - {% endblock uncompressed_javascript %} - {% compress js %} - {% block javascript %} - {% endblock javascript %} - {% endcompress %} - {% block footer %} diff --git a/bower-post-install.sh b/bower-post-install.sh deleted file mode 100755 index 2b55dd132..000000000 --- a/bower-post-install.sh +++ /dev/null @@ -1,33 +0,0 @@ -#!/usr/bin/env bash - -BOWER_COMPONENTS_PATH="analytics_dashboard/static/bower_components" - -function CSS2SCSS() { - filename=$1 - css_filename=${filename}.css - - if [ -f ${css_filename} ]; then - mv ${css_filename} ${filename}.scss - fi -} - -# "Convert" the CSS to SCSS since SASS can only import .scss files. -CSS2SCSS ${BOWER_COMPONENTS_PATH}/bootstrapaccessibilityplugin/plugins/css/bootstrap-accessibility -CSS2SCSS ${BOWER_COMPONENTS_PATH}/nvd3/build/nv.d3 - -# Download the CLDR data for all locales -CLDR_DATA_PATH=${BOWER_COMPONENTS_PATH}/cldr-data -node ./node_modules/cldr-data-downloader/bin/download.js -i ${CLDR_DATA_PATH}/index.json -o ${CLDR_DATA_PATH} - -# edX Pattern Library expects certain packages to be available to it -PATTERN_LIBRARY_SASS_PATH="${BOWER_COMPONENTS_PATH}/edx-pattern-library/pattern-library/sass" -PATTERN_LIBRARY_LIB_PATH="${PATTERN_LIBRARY_SASS_PATH}/global" -PATTERN_LIBRARY_LIBS=('bi-app-sass' 'bourbon' 'breakpoint-sass' 'susy') -for lib in "${PATTERN_LIBRARY_LIBS[@]}" -do - cp -rf ${BOWER_COMPONENTS_PATH}/$lib $PATTERN_LIBRARY_LIB_PATH/ -done - -# pre-compile the pattern library to improve sass compilation performance -echo "Pre-compiling edX pattern library..." -sassc ${PATTERN_LIBRARY_SASS_PATH}/edx-pattern-library-ltr.scss ${PATTERN_LIBRARY_SASS_PATH}/edx-pattern-library-ltr-compiled.scss diff --git a/bower.json b/bower.json deleted file mode 100644 index d63b83030..000000000 --- a/bower.json +++ /dev/null @@ -1,51 +0,0 @@ -{ - "name": "edx-analytics-dashboard", - "homepage": "https://github.com/edx/edx-analytics-dashboard", - "private": true, - "ignore": [ - "**/.*", - "node_modules", - "bower_components", - "test", - "tests" - ], - "dependencies": { - "globalize": "1.1.1", - "requirejs-plugins": "~1.0.3", - "bootstrap-sass-official": "v3.3.0", - "jquery": "~1.11.1", - "bootstrapaccessibilityplugin": "~1.0.3", - "underscore": "~1.8.2", - "backbone": "~1.1.2", - "d3": "~3.5.3", - "requirejs": "~2.1.15", - "nvd3": "~1.8.4", - "jasmine": "~2.0.4", - "moment": "~2.17.1", - "topojson": "~1.4.3", - "datamaps": "~0.3.4", - "datatables": "~1.10.2", - "font-awesome": "~4.6.3", - "natural-sort": "overset/javascript-natural-sort#dbf4ca259b327a488bd1d7897fd46d80c414a7e0", - "cldr-data": "29.0.0", - "edx-ui-toolkit": "edx/edx-ui-toolkit#29759050aff2f4f3cb6921432855ad057bd69bb1", - "marionette": "~2.4.4", - "uri.js": "1.17", - "backgrid": "0.3.8", - "backgrid-moment-cell": "^0.3.8", - "backgrid-paginator": "^0.3.9", - "backgrid-filter": "^0.3.7", - "text": "^2.0.14", - "axe-core": "^1.1.1", - "nprogress": "^0.2.0", - "edx-pattern-library": "0.16.6" - }, - "resolutions": { - "backbone": "~1.2.3", - "jquery": "~1.11.1", - "underscore": "~1.8.2", - "d3": "~3.5.3", - "cldrjs": "~0.4.4", - "moment": "~2.17.1" - } -} diff --git a/build.js b/build.js deleted file mode 100644 index 1da8876aa..000000000 --- a/build.js +++ /dev/null @@ -1,88 +0,0 @@ -/* eslint-disable no-unused-expressions */ -({ - mainConfigFile: 'analytics_dashboard/static/js/config.js', - baseUrl: 'analytics_dashboard/static', - dir: 'analytics_dashboard/static/dist', - removeCombined: true, - findNestedDependencies: true, - - // Disable all optimization. django-compressor will handle that for us. - optimizeCss: false, - optimize: 'none', - normalizeDirDefines: 'all', - skipDirOptimize: true, - - preserveLicenseComments: true, - modules: [ - { - name: 'js/common' - }, - { - name: 'js/config' - }, - { - name: 'js/engagement-content-main', - exclude: ['js/common'] - }, - { - name: 'js/engagement-video-content-main', - exclude: ['js/common'] - }, - { - name: 'js/engagement-videos-main', - exclude: ['js/common'] - }, - { - name: 'js/engagement-video-timeline-main', - exclude: ['js/common'] - }, - { - name: 'js/enrollment-activity-main', - exclude: ['js/common'] - }, - { - name: 'js/enrollment-geography-main', - exclude: ['js/common'] - }, - { - name: 'js/enrollment-demographics-age-main', - exclude: ['js/common'] - }, - { - name: 'js/enrollment-demographics-education-main', - exclude: ['js/common'] - }, - { - name: 'js/enrollment-demographics-gender-main', - exclude: ['js/common'] - }, - { - name: 'js/performance-content-main', - exclude: ['js/common'] - }, - { - name: 'js/performance-problems-main', - exclude: ['js/common'] - }, - { - name: 'js/performance-answer-distribution-main', - exclude: ['js/common'] - }, - { - name: 'apps/learners/app/learners-main', - exclude: ['js/common'] - }, - { - name: 'js/performance-learning-outcomes-content-main', - exclude: ['js/common'] - }, - { - name: 'js/performance-learning-outcomes-section-main', - exclude: ['js/common'] - }, - { - name: 'apps/course-list/app/course-list-main', - exclude: ['js/common'] - } - ] -}); diff --git a/gulpfile.js b/gulpfile.js index a9073d338..d3bb6a8fe 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -1,11 +1,9 @@ (function() { 'use strict'; - var eslint = require('gulp-eslint'), - gulp = require('gulp'), + var gulp = require('gulp'), Server = require('karma').Server, path = require('path'), - browserSync = require('browser-sync'), extend = require('util')._extend, // eslint-disable-line no-underscore-dangle paths = { spec: [ @@ -13,13 +11,6 @@ 'analytics_dashboard/static/js/test/**/*.js', 'analytics_dashboard/static/apps/**/*.js' ], - lint: [ - 'build.js', - 'gulpfile.js', - 'analytics_dashboard/static/js/**/*.js', - 'analytics_dashboard/static/js/test/**/*.js', - 'analytics_dashboard/static/apps/**/*.js' - ], templates: [ 'analytics_dashboard/analytics_dashboard/templates/analytics_dashboard/*.html', 'analytics_dashboard/courses/templates/courses/*.html', @@ -39,13 +30,6 @@ new Server(extend(defaultOptions, options), cb).start(); } - gulp.task('lint', function() { - return gulp.src(paths.lint) - .pipe(eslint()) - .pipe(eslint.format()) - .pipe(eslint.failAfterError()); - }); - // this task runs the tests. It doesn't give you very detailed results, // so you may need to run the jasmine test page directly: // http://127.0.0.1:8000/static/js/test/spec-runner.html @@ -63,25 +47,10 @@ }); // these are the default tasks when you run gulp - gulp.task('default', ['test', 'lint']); + gulp.task('default', ['test']); - // type 'gulp watch' to continuously run linting and tests + // type 'gulp watch' to continuously run tests gulp.task('watch', function() { - gulp.watch(paths.spec, ['test', 'lint']); - }); - - // Proxy to django server (assuming that we're using port 8000 and - // localhost) - gulp.task('browser-sync', function() { - // there is a little delay before reloading b/c the sass files need to - // recompile, but we can't necessarily watch the generated directory - // because django creates a new css file that browser-sync doesn't - // know of and I can't figure out how to make it watch an entire - // directory - browserSync.init(null, { - proxy: 'localhost:9000', - files: paths.lint.concat(paths.templates).concat(paths.sass), - reloadDelay: 1000 - }); + gulp.watch(paths.spec, ['test']); }); }()); diff --git a/karma.conf.js b/karma.conf.js index 8fda441db..0dc597ae8 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -1,54 +1,50 @@ // Karma configuration // Generated on Thu Jun 26 2014 17:49:39 GMT-0400 (EDT) +var path = require('path'), + webpack = require('webpack'); + module.exports = function(config) { 'use strict'; config.set({ - // base path that will be used to resolve all patterns (eg. files, exclude) basePath: '', - // frameworks to use // available frameworks: https://npmjs.org/browse/keyword/karma-adapter - frameworks: ['jasmine-jquery', 'jasmine', 'requirejs', 'sinon'], - + frameworks: ['jasmine-jquery', 'jasmine', 'sinon'], // list of files / patterns to load in the browser files: [ - {pattern: 'analytics_dashboard/static/vendor/**/*.js', included: false}, - {pattern: 'analytics_dashboard/static/bower_components/**/*.js', included: false}, - {pattern: 'analytics_dashboard/static/bower_components/**/*.underscore', included: false}, + {pattern: 'node_modules/**/*.js', included: false}, + {pattern: 'node_modules/**/*.underscore', included: false}, // limiting the cldr json files to load (we don't use the other ones and loading too many // throws errors on a mac) - {pattern: 'analytics_dashboard/static/bower_components/cldr-data/supplemental/*.json', included: false}, - {pattern: 'analytics_dashboard/static/bower_components/cldr-data/availableLocales.json', included: false}, - {pattern: 'analytics_dashboard/static/bower_components/cldr-data/**/numbers.json', included: false}, + {pattern: 'node_modules/cldr-data/supplemental/*.json', included: false}, + {pattern: 'node_modules/cldr-data/availableLocales.json', included: false}, + {pattern: 'node_modules/cldr-data/**/numbers.json', included: false}, {pattern: 'analytics_dashboard/static/js/load/*.js', included: false}, {pattern: 'analytics_dashboard/static/js/models/**/*.js', included: false}, {pattern: 'analytics_dashboard/static/js/views/**/*.js', included: false}, {pattern: 'analytics_dashboard/static/js/utils/**/*.js', included: false}, - {pattern: 'analytics_dashboard/static/js/test/specs/*.js', included: false}, {pattern: 'analytics_dashboard/static/apps/**/*.js', included: false}, {pattern: 'analytics_dashboard/static/apps/**/*.underscore', included: false}, - 'analytics_dashboard/static/js/config.js', - 'analytics_dashboard/static/js/test/spec-runner.js' + + // actual tests + {pattern: 'analytics_dashboard/static/js/test/specs/*.js', watched: false} ], exclude: [ // Don't run library unit tests - 'analytics_dashboard/static/bower_components/**/spec/**/*.js', - 'analytics_dashboard/static/bower_components/**/specs/**/*.js', - 'analytics_dashboard/static/bower_components/**/test/**/*.js' + 'node_modules/**/spec/**/*.js', + 'node_modules/**/specs/**/*.js', + 'node_modules/**/test/**/*.js' ], // preprocess matching files before serving them to the browser // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor preprocessors: { - 'analytics_dashboard/static/js/models/**/*.js': ['coverage'], - 'analytics_dashboard/static/js/views/**/*.js': ['coverage'], - 'analytics_dashboard/static/js/utils/**/*.js': ['coverage'], - 'analytics_dashboard/static/apps/**/*.js': ['coverage'] + 'analytics_dashboard/static/js/test/specs/*.js': ['webpack'] }, // plugins required for running the karma tests @@ -56,20 +52,119 @@ module.exports = function(config) { 'karma-jasmine', 'karma-jasmine-jquery', 'karma-jasmine-html-reporter', - 'karma-requirejs', 'karma-phantomjs-launcher', 'karma-coverage', 'karma-sinon', - 'karma-chrome-launcher' + 'karma-chrome-launcher', + 'karma-webpack' ], + webpack: { + // For detailed comments about webpack configuration, see webpack.config.js or webpack.prod.config.js + // This is just slimmed down version of webpack.config.js just for running karma tests + context: __dirname, + resolve: { + modules: [ + 'node_modules', + 'analytics_dashboard/static', + 'analytics_dashboard/static/js', + 'analytics_dashboard/static/apps' + ], + alias: { + marionette: 'backbone.marionette', + cldr: 'cldrjs/dist/cldr', + + // Aliases used in tests + uitk: 'edx-ui-toolkit/src/js', + URI: 'urijs/src/URI', + + moment: path.resolve('./node_modules/moment'), + jquery: path.resolve('./node_modules/jquery'), + backbone: path.resolve('./node_modules/backbone') + } + }, + + output: { + path: path.resolve('./assets/bundles/'), + filename: '[name]-[hash].js' + }, + + module: { + rules: [ + { + test: /\.underscore$/, + use: 'raw-loader', + include: path.join(__dirname, 'analytics_dashboard/static'), + exclude: /node_modules/ + }, + { + test: /\.(png|woff|woff2|eot|ttf|svg)$/, + use: 'file-loader?name=fonts/[name].[ext]', + include: [ + path.join(__dirname, 'analytics_dashboard/static'), + path.join(__dirname, 'node_modules') + ] + }, + { + test: /\.scss$/, + use: [ + { + loader: 'style-loader' // creates style nodes from JS strings + }, + { + loader: 'css-loader' // translates CSS into CommonJS + }, + { + loader: 'fast-sass-loader' // compiles Sass to CSS. If this breaks, just use sass-loader + } + ], + exclude: /node_modules/ + }, + { + test: /\.css$/, + use: ['style-loader', 'css-loader'], + include: [ + path.join(__dirname, 'analytics_dashboard/static'), + path.join(__dirname, 'node_modules') + ] + } + ], + noParse: [/cldr-data|underscore/] + }, + + plugins: [ + new webpack.ProvidePlugin({ + $: 'jquery', + jQuery: 'jquery', + // the Insights application loads gettext identity library via django, thus + // components reference gettext globally so a shim is added here to reflect + // the text so tests can be run if modules reference gettext + gettext: 'test/browser-shims/gettext', + ngettext: 'test/browser-shims/ngettext' + }) + ] + }, + + webpackMiddleware: { + // silences webpack log output + noInfo: true, + stats: { + assets: false, + chunks: false, + hash: false, + timings: false, + version: false + } + }, + // test results reporter to use // possible values: 'dots', 'progress' // available reporters: https://npmjs.org/browse/keyword/karma-reporter reporters: ['progress', 'coverage'], coverageReporter: { - dir: 'build', subdir: 'coverage-js', + dir: 'build', + subdir: 'coverage-js', reporters: [ {type: 'html', subdir: 'coverage-js/html'}, {type: 'cobertura', file: 'coverage.xml'}, @@ -80,21 +175,17 @@ module.exports = function(config) { // web server port port: 9876, - // enable / disable colors in the output (reporters and logs) colors: true, - // level of logging // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN // || config.LOG_INFO || config.LOG_DEBUG logLevel: config.LOG_INFO, - // enable / disable watching file and executing tests whenever any file changes autoWatch: true, - // start these browsers // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher // you can also add Chrome or other browsers too @@ -107,3 +198,4 @@ module.exports = function(config) { singleRun: false }); }; + diff --git a/npm-post-install.sh b/npm-post-install.sh new file mode 100755 index 000000000..bb5b82eaa --- /dev/null +++ b/npm-post-install.sh @@ -0,0 +1,64 @@ +#!/usr/bin/env bash + +NPM_PATH="node_modules" +NPM_BIN="${NPM_PATH}/.bin" + +function CSS2SCSS() { + filename=$1 + css_filename=${filename}.css + + if [ -f ${css_filename} ]; then + mv ${css_filename} ${filename}.scss + fi +} + +# Rename the CSS to SCSS since SASS can only import .scss files. +CSS2SCSS ${NPM_PATH}/bootstrap-accessibility-plugin/plugins/css/bootstrap-accessibility +CSS2SCSS ${NPM_PATH}/nvd3/build/nv.d3 + +# Download the CLDR data for all locales +CLDR_DATA_PATH=${NPM_PATH}/cldr-data +node ./node_modules/cldr-data-downloader/bin/download.js -i ${CLDR_DATA_PATH}/urls.json -o ${CLDR_DATA_PATH} + +# edX Pattern Library expects certain packages to be available to it +PATTERN_LIBRARY_PATH="${NPM_PATH}/edx-pattern-library" +PATTERN_LIBRARY_SASS_PATH="${PATTERN_LIBRARY_PATH}/pattern-library/sass" +PATTERN_LIBRARY_LIB_PATH="${PATTERN_LIBRARY_SASS_PATH}/global" +PATTERN_LIBRARY_LIBS=('bi-app-sass' 'bourbon' 'breakpoint-sass' 'susy') +for lib in "${PATTERN_LIBRARY_LIBS[@]}" +do + cp -rf ${NPM_PATH}/$lib $PATTERN_LIBRARY_LIB_PATH/ +done + +# pre-compile the pattern library to improve sass compilation performance +if [ ! -f ${PATTERN_LIBRARY_SASS_PATH}/edx-pattern-library-ltr-compiled.scss ]; then + echo "Pre-compiling edX pattern library..." + ${NPM_BIN}/node-sass ${PATTERN_LIBRARY_SASS_PATH}/edx-pattern-library-ltr.scss ${PATTERN_LIBRARY_SASS_PATH}/edx-pattern-library-ltr-compiled.scss + echo "Done compiling." +else + echo "edX pattern library already compiled." +fi + +# pre-compile the font-awesome sass because that's the only way I could get fonts working in webpack... +if [ ! -f ${NPM_PATH}/font-awesome/scss/font-awesome-compiled.scss ]; then + echo "Pre-compiling font-awesome..." + ${NPM_BIN}/node-sass ${NPM_PATH}/font-awesome/scss/font-awesome.scss ${NPM_PATH}/font-awesome/scss/font-awesome-compiled.scss + echo "Done compiling." +else + echo "font-awesome already compiled." +fi +# copy font files to insights static files (they are not included in the compiled sass) +DJANGO_STATIC_PATH=analytics_dashboard/static +STATIC_FONTS_PATH=analytics_dashboard/static/fonts +PATTERN_LIBRARY_FONTS_PATH="${NPM_PATH}/edx-pattern-library/pattern-library/fonts" +FONT_AWESOME_FONTS_PATH="${NPM_PATH}/font-awesome/fonts" +if [ ! -f ${STATIC_FONTS_PATH}/OpenSans ]; then + echo "Copying font files to Django static fonts directory..." + mkdir -p ${STATIC_FONTS_PATH} + mkdir -p ${STATIC_FONTS_PATH}/OpenSans + cp -rf ${PATTERN_LIBRARY_FONTS_PATH}/OpenSans/* ${STATIC_FONTS_PATH}/OpenSans/ + cp -rf ${FONT_AWESOME_FONTS_PATH}/* ${STATIC_FONTS_PATH}/ + echo "Done copying." +else + echo "Font files already copied." +fi diff --git a/package.json b/package.json index 03cc7ff33..166ef5332 100644 --- a/package.json +++ b/package.json @@ -5,17 +5,70 @@ "url": "git://github.com/edx/edx-analytics-dashboard" }, "dependencies": { - "bower": "1.7.9", - "cldr-data-downloader": "0.2.5", - "requirejs": "^2.1.14" + "babel-core": "^6.25.0", + "babel-loader": "^7.0.0", + "babel-plugin-dynamic-import-node": "^1.0.2", + "babel-plugin-syntax-dynamic-import": "^6.18.0", + "babel-plugin-transform-runtime": "^6.23.0", + "babel-preset-env": "^1.5.2", + "babel-preset-es2015": "^6.24.1", + "babel-runtime": "^6.23.0", + "backbone": "^1.2.3", + "backbone.marionette": "~2.4.4", + "backgrid": "^0.3.5", + "backgrid-filter": "^0.3.5", + "backgrid-moment-cell": "^0.3.8", + "backgrid-paginator": "^0.3.5", + "bootstrap-accessibility-plugin": "~1.0.2", + "bootstrap-sass": "~3.3.7", + "cldr-data": "31.0.2", + "cldrjs": "0.4.8", + "css-loader": "^0.26.1", + "d3": "~3.5.3", + "datamaps": "~0.5.8", + "datatables": "~1.10.2", + "datatables-bootstrap3-plugin": "^0.5.0", + "edx-pattern-library": "0.16.6", + "edx-ui-toolkit": "git://github.com/edx/edx-ui-toolkit#29759050aff2f4f3cb6921432855ad057bd69bb1", + "extract-text-webpack-plugin": "^2.0.0-rc.2", + "file-loader": "^0.10.0", + "font-awesome": "~4.6.3", + "globalize": "1.1.1", + "install": "^0.8.8", + "jquery": "~1.11.1", + "js-natural-sort": "~0.8.1", + "json-loader": "^0.5.4", + "moment": "~2.8.3", + "node-bourbon": "^4.2.8", + "node-sass": "^4.4.0", + "npm": "^5.0.3", + "nprogress": "^0.2.0", + "nvd3": "~1.8.4", + "raw-loader": "^0.5.1", + "sass-loader": "^4.1.1", + "style-loader": "^0.13.1", + "topojson": "~1.4.3", + "underscore": "~1.8.2", + "urijs": "1.17", + "url-loader": "^0.5.7", + "webpack": "^2.2.1", + "webpack-bundle-analyzer": "^2.8.1", + "webpack-bundle-tracker": "^0.2.0", + "webpack-merge": "^4.1.0" }, "devDependencies": { - "browser-sync": "^1.1.2", + "axe-core": "^1.1.1", + "babel-eslint": "^7.2.3", "edx-custom-a11y-rules": "edx/edx-custom-a11y-rules#ff77f203ef5d9534c6200cc141e9d150155d0e12", - "eslint": "~2.13.1", - "eslint-config-edx": "^1.2.0", + "eslint": "^3.19.0", + "eslint-config-edx": "^2.0.1", + "eslint-config-edx-es5": "^3.0.0", + "eslint-import-resolver-webpack": "^0.8.1", + "eslint-loader": "^1.8.0", + "eslint-plugin-import": "^2.7.0", + "fast-sass-loader": "^1.2.4", "gulp": "^3.8.8", - "gulp-eslint": "^2.0.0", + "jasmine": "~2.1.0", "jasmine-core": "^2.5.2", "jscs": "^1.10.0", "karma": "^1.5.0", @@ -25,12 +78,20 @@ "karma-jasmine-html-reporter": "^0.2.2", "karma-jasmine-jquery": "^0.1.1", "karma-phantomjs-launcher": "^1.0.2", - "karma-requirejs": "^1.1.0", "karma-sinon": "^1.0.5", + "karma-webpack": "^2.0.3", "phantomjs-prebuilt": "^2.1.14", - "sinon": "1.17.7" + "prettier": "^1.5.2", + "prettier-eslint": "^6.4.2", + "prettier-eslint-cli": "^4.1.1", + "sinon": "1.17.7", + "webpack-dev-server": "^2.4.5" }, - "eslintConfig": { - "extends": "eslint-config-edx" + "scripts": { + "postinstall": "./npm-post-install.sh", + "start": "node_modules/.bin/webpack-dev-server --hot --inline", + "build": "node_modules/.bin/webpack --config webpack.prod.config.js", + "lint": "node_modules/.bin/eslint analytics_dashboard/static/", + "format": "node_modules/.bin/prettier-eslint --write analytics_dashboard/static/apps/course-list/app/*.js analytics_dashboard/static/apps/course-list/app/**/*.js" } } diff --git a/requirements/base.txt b/requirements/base.txt index fdba33604..65ccb1c75 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -3,10 +3,9 @@ bpython==0.16 # MIT License Django==1.11.2 # BSD License django-appconf==1.0.2 django-braces==1.11.0 # BSD -django_compressor==2.1.1 # MIT django-countries==4.5 # MIT -django-libsass==0.7 # BSD django-model-utils==2.6.1 # BSD +django-webpack-loader==0.4.1 # MIT djangorestframework==3.6.3 # BSD djangorestframework-csv==2.0.0 # BSD # Dependency of djangorestframework diff --git a/setup.cfg b/setup.cfg index f83eccb47..8cf33db9b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [pep8] max_line_length=120 -exclude=settings,migrations,analytics_dashboard/static,bower_components,analytics_dashboard/wsgi.py +exclude=settings,migrations,analytics_dashboard/static,node_modules,analytics_dashboard/wsgi.py diff --git a/webpack.common.config.js b/webpack.common.config.js new file mode 100644 index 000000000..8008e4be3 --- /dev/null +++ b/webpack.common.config.js @@ -0,0 +1,139 @@ +// Bundles static assets for loading in development environments. +// Optimizes for fast re-build time at the expense of a large bundle size. +var path = require('path'), + webpack = require('webpack'), + BundleTracker = require('webpack-bundle-tracker'), + BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; + +module.exports = { + context: __dirname, + entry: { + // Application entry points + 'application-main': './analytics_dashboard/static/js/application-main', + 'engagement-content-main': './analytics_dashboard/static/js/engagement-content-main', + 'engagement-video-content-main': './analytics_dashboard/static/js/engagement-video-content-main', + 'engagement-videos-main': './analytics_dashboard/static/js/engagement-videos-main', + 'engagement-video-timeline-main': './analytics_dashboard/static/js/engagement-video-timeline-main', + 'enrollment-activity-main': './analytics_dashboard/static/js/enrollment-activity-main', + 'enrollment-geography-main': './analytics_dashboard/static/js/enrollment-geography-main', + 'enrollment-demographics-age-main': './analytics_dashboard/static/js/enrollment-demographics-age-main', + 'enrollment-demographics-education-main': './analytics_dashboard/static/js/enrollment-demographics-education-main', + 'enrollment-demographics-gender-main': './analytics_dashboard/static/js/enrollment-demographics-gender-main', + 'performance-content-main': './analytics_dashboard/static/js/performance-content-main', + 'performance-problems-main': './analytics_dashboard/static/js/performance-problems-main', + 'performance-answer-distribution-main': './analytics_dashboard/static/js/performance-answer-distribution-main', + 'learners-main': './analytics_dashboard/static/apps/learners/app/learners-main', + 'performance-learning-outcomes-content-main': './analytics_dashboard/static/js/performance-learning-outcomes-content-main', + 'performance-learning-outcomes-section-main': './analytics_dashboard/static/js/performance-learning-outcomes-section-main', + 'course-list-main': './analytics_dashboard/static/apps/course-list/app/course-list-main', + + // Entry point for bundling large amount of cldr-data json files separately from other vendor code + globalization: './analytics_dashboard/static/js/utils/globalization' + }, + + resolve: { + // For legacy reasons (when we used require.js), we need to add extra module resolving directories for some + // `requires` in our javascript that use relative paths. + modules: [ + 'node_modules', + 'analytics_dashboard/static', + 'analytics_dashboard/static/js', + 'analytics_dashboard/static/apps' + ], + alias: { + // Marionette in bower was called 'marionette'. In npm it's 'backbone.marionette'. + marionette: 'backbone.marionette', + // Internal Globalize.js code seems to expect 'cldr' to refer to this file in cldrjs. + cldr: 'cldrjs/dist/cldr', + + // Aliases used in tests + uitk: 'edx-ui-toolkit/src/js', + URI: 'urijs/src/URI', + + // Dedupe copies of modules in bundles by forcing all dependencies to use our copy of the module they need: + moment: path.resolve('./node_modules/moment'), + jquery: path.resolve('./node_modules/jquery'), + backbone: path.resolve('./node_modules/backbone') + } + }, + + output: { + // Output bundles directly to the Django static assets directory + path: path.resolve('./analytics_dashboard/static/bundles/'), + // Tells clients to load bundles from the static route (uses CDN in production) + publicPath: '/static/bundles/', + // Bundle names will not change between builds if the content stays identical + filename: '[name]-[chunkhash].js' + }, + + module: { + // Specify file-by-file rules to Webpack. Some file-types need a particular kind of loader. + rules: [ + // raw-loader just inlines files as a string in the javascript bundles + { + test: /\.underscore$/, + use: 'raw-loader', + include: path.join(__dirname, 'analytics_dashboard/static'), + exclude: /node_modules/ + }, + // Webpack, by default, uses the url-loader for images and fonts that are required/included by files it + // processes, which just base64 encodes them and inlines them in the javascript bundles. This makes the + // javascript bundles ginormous and defeats caching so we will use the file-loader instead to copy the files + // directly to the output directory (the Django assets folder). + { + test: /\.(png|woff|woff2|eot|ttf|svg)$/, + use: 'file-loader?name=fonts/[name].[ext]', + include: [path.join(__dirname, 'analytics_dashboard/static'), path.join(__dirname, 'node_modules')] + } + ], + // This option can increase build speed slightly by forcing Webpack to skip processing for some large + // dependencies. Only add dependencies here that do not contain any `require`s or `include`s. + noParse: [/cldr-data|underscore/] + }, + + // Specify additional processing or side-effects done on the Webpack output bundles as a whole. + plugins: [ + // Logs all bundle information to a JSON file which is needed by Django as a reference for finding the actual + // filenames on disk, which it doesn't know at runtime, from the bundle names, which it does know. + new BundleTracker({ + filename: './webpack-stats.json' + }), + // We reference jquery as a global variable a lot. This plugin inlines a reference to the module in place of the + // free variable. + new webpack.ProvidePlugin({ + $: 'jquery', + jQuery: 'jquery' + }), + // AggressiveMergingPlugin in conjunction with these CommonChunkPlugins turns many GBs worth of individual + // chunks into one or two large chunks that entry chunks reference. It reduces output bundle size a lot. + new webpack.optimize.AggressiveMergingPlugin({minSizeReduce: 1.1}), + new webpack.optimize.CommonsChunkPlugin({ + // Extracts code and json files for globalize.js into a separate bundle for efficient caching. + names: 'globalization', + minChunks: Infinity + }), + new webpack.optimize.CommonsChunkPlugin({ + // Extracts every 3rd-party module common among all bundles into one chunk (excluding the modules in the + // globalization bundle) + name: 'manifest', + minChunks(module, count) { + return ( + module.context && + module.context.indexOf('node_modules') !== -1 && + module.context.indexOf('cldr') === -1 && + module.context.indexOf('globalize') === -1 + ); + } + }), + new webpack.optimize.CommonsChunkPlugin({ + // This chunk should only include the webpack runtime code. The runtime code changes on every webpack + // compile. We extract this so that the hash on the above vendor chunk does not change on every webpack + // compile (we don't want its hash to change without vendor lib changes, because that would bust the cache). + name: 'manifest' + }), + // Enable this plugin to see a pretty tree map of modules in each bundle and how much size they take up. + // new BundleAnalyzerPlugin({ + // analyzerMode: 'static' + // }) + ] +}; diff --git a/webpack.config.js b/webpack.config.js new file mode 100644 index 000000000..28781aa80 --- /dev/null +++ b/webpack.config.js @@ -0,0 +1,97 @@ +// Bundles static assets for loading in development environments. +// Optimizes for fast re-build time at the expense of a large bundle size. +var Merge = require('webpack-merge'), + path = require('path'), + commonConfig = require('./webpack.common.config.js'), + djangoDevServer = process.env.DJANGO_DEV_SERVER || 'http://localhost:8110'; + +module.exports = Merge.smart(commonConfig, { + entry: { + // Bundle the client for the webpack-dev-server and specify url to the server + devServer: 'webpack-dev-server/client?http://localhost:8080', + // Bundle the client for hot reloading. 'only-' means to only hot reload for successful updates. + hotReload: 'webpack/hot/only-dev-server' + }, + + output: { + // Tells clients to load bundles from the dev-server + publicPath: 'http://localhost:8080/static/bundles/', + // Bundle names will change every build. [hash] is faster than [chunkhash]. + filename: '[name]-[hash].js' + }, + + module: { + // Specify file-by-file rules to Webpack. Some file-types need a particular kind of loader. + rules: [ + // The babel-loader transforms newer ES2015 syntax to older ES5 for legacy browsers. + { + test: /\.js$/, + exclude: /(node_modules|bower_components)/, + use: { + loader: 'babel-loader', + options: { + presets: [ + ['env', { + targets: { + browsers: ['last 2 versions', 'ie >= 11'] + } + }] + ], + plugins: ['babel-plugin-syntax-dynamic-import'], + cacheDirectory: true + } + } + }, + // We are not extracting CSS from the javascript bundles in development because extracting prevents + // hot-reloading from working, it increases build time, and we don't care about flash-of-unstyled-content + // issues in development. + { + test: /\.scss$/, + use: [ + { + loader: 'style-loader' // creates style nodes from JS strings + }, + { + loader: 'css-loader', // translates CSS into CommonJS + options: { + sourceMap: true + } + }, + // fast-sass-loader might be slightly faster, but lacks useful source-map generation + { + loader: 'sass-loader', // compiles Sass to CSS. + options: { + sourceMap: true + } + } + ], + exclude: /node_modules/ + }, + // Not all of our dependencies use Sass. We need an additional rule for requiring or including CSS files. + { + test: /\.css$/, + use: ['style-loader', 'css-loader'], + include: [path.join(__dirname, 'analytics_dashboard/static'), path.join(__dirname, 'node_modules')] + } + ] + }, + + devServer: { + compress: true, + hot: true, // enable hot reloading on the server + // Since the dev-server runs on a different port, browsers will complain about CORS violations unless we + // explicitly tell them it's okay with this header. + headers: { + 'Access-Control-Allow-Origin': '*' + }, + // Webpack does not process all images in Insights, so proxy all image requests through to django static files + proxy: { + // This assumes that the developer is running the django dev server on the default host and port + '/static/images': djangoDevServer + } + }, + + // Source-map generation method. 'eval' is the fastest, but shouldn't be used in production (it increases file sizes + // a lot). If source-maps are desired in production, 'source-map' should be used (slowest, but highest quality). + devtool: 'eval' +}); diff --git a/webpack.prod.config.js b/webpack.prod.config.js new file mode 100644 index 000000000..d8877d70b --- /dev/null +++ b/webpack.prod.config.js @@ -0,0 +1,95 @@ +// Bundles static assets for loading in production environments. +// Optimizes for small resulting bundle size at the expense of a long build time. +var Merge = require('webpack-merge'), + path = require('path'), + webpack = require('webpack'), + ExtractTextPlugin = require('extract-text-webpack-plugin'), + extractCSS = new ExtractTextPlugin('styles-css.css'), + extractSCSS = new ExtractTextPlugin('styles-scss.css'), + commonConfig = require('./webpack.common.config.js'); + +module.exports = Merge.smart(commonConfig, { + module: { + // Specify file-by-file rules to Webpack. Some file-types need a particular kind of loader. + rules: [ + // The babel-loader transforms newer ES2015 syntax to older ES5 for legacy browsers. + // The 'transform-runtime' plugin tells babel to require the runtime instead of inlining it (saves bundle + // size). + { + test: /\.js$/, + exclude: /(node_modules|bower_components)/, + use: { + loader: 'babel-loader', + options: { + presets: [ + ['env', { + targets: { + browsers: ['last 2 versions', 'ie >= 11'] + } + }] + ], + plugins: ['transform-runtime', 'babel-plugin-syntax-dynamic-import'] + } + } + }, + // Webpack, by default, includes all CSS in the javascript bundles. Unfortunately, that means: + // a) The CSS won't be cached by browsers separately (a javascript change will force CSS re-download) + // b) Since CSS is applied asyncronously, it causes an ugly flash-of-unstyled-content. + // + // To avoid these problems, we extract the CSS from the bundles into separate CSS files that can be included + // as tags in the HTML manually by our Django tempaltes. + // + // We will not do this in development because it prevents hot-reloading from working and it increases build + // time. + { + test: /\.scss$/, + use: extractSCSS.extract({ + fallback: 'style-loader', + use: [ + { + loader: 'css-loader', // creates the style nodes from JS strings + options: { + minimize: true, + sourceMap: true + } + }, + // fast-sass-loader might be slightly faster, but lacks useful source-map generation + { + loader: 'sass-loader', // compiles Sass to CSS. + options: { + minimize: true, + sourceMap: true + } + } + ] + }), + exclude: /node_modules/ + }, + // Not all of our dependencies use Sass. We need an additional rule for requiring or including CSS files. + { + test: /\.css$/, + use: extractCSS.extract({ + fallback: 'style-loader', + use: { + loader: 'css-loader', + options: { + minimize: true, + sourceMap: true + } + } + }), + include: [path.join(__dirname, 'analytics_dashboard/static'), path.join(__dirname, 'node_modules')] + } + ] + }, + + // Specify additional processing or side-effects done on the Webpack output bundles as a whole. + plugins: [ + extractCSS, + extractSCSS, + new webpack.optimize.UglifyJsPlugin({sourceMap: true}) // aka. minify + ], + + // Source-map generation method. 'source-map' is the slowest, but also the highest quality. + devtool: 'source-map' +});