Commit 480a3ca6 by Brian Jacobel

Move course import JS to Webpack

parent 49b11cb6
......@@ -116,6 +116,7 @@ GITHUB_REPO_ROOT = ENV_TOKENS.get('GITHUB_REPO_ROOT', GITHUB_REPO_ROOT)
STATIC_ROOT_BASE = ENV_TOKENS.get('STATIC_ROOT_BASE', None)
if STATIC_ROOT_BASE:
STATIC_ROOT = path(STATIC_ROOT_BASE) / EDX_PLATFORM_REVISION
WEBPACK_LOADER['DEFAULT']['STATS_FILE'] = STATIC_ROOT / "webpack-stats.json"
EMAIL_BACKEND = ENV_TOKENS.get('EMAIL_BACKEND', EMAIL_BACKEND)
EMAIL_FILE_PATH = ENV_TOKENS.get('EMAIL_FILE_PATH', None)
......
......@@ -100,7 +100,6 @@
"SERVER_EMAIL": "devops@example.com",
"SESSION_COOKIE_DOMAIN": null,
"SITE_NAME": "localhost",
"STATIC_ROOT_BASE": "** OVERRIDDEN **",
"STATIC_URL_BASE": "/static/",
"SYSLOG_SERVER": "",
"TECH_SUPPORT_EMAIL": "technical@example.com",
......
......@@ -64,6 +64,8 @@ STATICFILES_DIRS = [
(TEST_ROOT / "staticfiles" / "cms").abspath(),
]
WEBPACK_LOADER['DEFAULT']['STATS_FILE'] = TEST_ROOT / "staticfiles" / "cms" / "webpack-stats.json"
# Silence noisy logs
import logging
LOG_OVERRIDES = [
......
......@@ -747,6 +747,15 @@ REQUIRE_EXCLUDE = ("build.txt",)
# returns a list with the command arguments to execute.
REQUIRE_ENVIRONMENT = "node"
########################## DJANGO WEBPACK LOADER ##############################
WEBPACK_LOADER = {
'DEFAULT': {
'BUNDLE_DIR_NAME': 'bundles/',
'STATS_FILE': os.path.join(STATIC_ROOT, 'webpack-stats.json')
}
}
################################# CELERY ######################################
# Message configuration
......@@ -881,6 +890,7 @@ INSTALLED_APPS = (
'pipeline',
'static_replace',
'require',
'webpack_loader',
# Theming
'openedx.core.djangoapps.theming',
......
......@@ -66,6 +66,7 @@ TEST_ROOT = path('test_root')
# Want static files in the same dir for running on jenkins.
STATIC_ROOT = TEST_ROOT / "staticfiles"
WEBPACK_LOADER['DEFAULT']['STATS_FILE'] = STATIC_ROOT / "webpack-stats.json"
GITHUB_REPO_ROOT = TEST_ROOT / "data"
DATA_DIR = TEST_ROOT / "data"
......
......@@ -40,6 +40,7 @@ LOG_DIR = (TEST_ROOT / "log").abspath()
# Store the static files under test root so that they don't overwrite existing static assets
STATIC_ROOT = (TEST_ROOT / "staticfiles" / "cms").abspath()
WEBPACK_LOADER['DEFAULT']['STATS_FILE'] = STATIC_ROOT / "webpack-stats.json"
# Disable uglify when tests are running (used by build.js).
# 1. Uglify is by far the slowest part of the build process
......
......@@ -41,7 +41,6 @@
'js/factories/export',
'js/factories/group_configurations',
'js/certificates/factories/certificates_page_factory',
'js/factories/import',
'js/factories/index',
'js/factories/library',
'js/factories/login',
......
......@@ -2,10 +2,15 @@
(function(AjaxPrefix) {
'use strict';
define(['domReady', 'jquery', 'underscore.string', 'backbone', 'gettext',
'common/js/components/views/feedback_notification', 'coffee/src/ajax_prefix',
'jquery.cookie'],
function(domReady, $, str, Backbone, gettext, NotificationView) {
define([
'domReady',
'jquery',
'underscore.string',
'backbone',
'gettext',
'../../../../common/static/common/js/components/views/feedback_notification',
'jquery.cookie'
], function(domReady, $, str, Backbone, gettext, NotificationView) {
var main, sendJSON;
main = function() {
AjaxPrefix.addAjaxPrefix(jQuery, function() {
......
define([
'domReady', 'js/views/import', 'jquery', 'gettext', 'jquery.fileupload', 'jquery.cookie'
'domReady',
'../views/import',
'jquery',
'gettext',
'jQuery-File-Upload/js/jquery.fileupload',
'jquery.cookie',
'../../../../cms/js/main'
], function(domReady, Import, $, gettext) {
'use strict';
return function(feedbackUrl, library) {
var dbError;
if (library) {
dbError = gettext('There was an error while importing the new library to our database.');
} else {
dbError = gettext('There was an error while importing the new course to our database.');
}
var bar = $('.progress-bar'),
fill = $('.progress-fill'),
submitBtn = $('.submit-button'),
chooseBtn = $('.view-import .choose-file-button'),
return {
Import: function(feedbackUrl, library) {
var dbError,
$bar = $('.progress-bar'),
$fill = $('.progress-fill'),
$submitBtn = $('.submit-button'),
$chooseBtn = $('.view-import .choose-file-button'),
defaults = [
gettext('There was an error during the upload process.') + '\n',
gettext('There was an error while unpacking the file.') + '\n',
......@@ -24,16 +24,39 @@ define([
],
unloading = false,
previousImport = Import.storedImport(),
file;
var onComplete = function() {
bar.hide();
chooseBtn
file,
onComplete = function() {
$bar.hide();
$chooseBtn
.find('.copy').text(gettext('Choose new file')).end()
.show();
},
showImportSubmit = function() {
var filepath = $(this).val(),
msg;
if (filepath.substr(filepath.length - 6, 6) === 'tar.gz') {
$('.error-block').hide();
$('.file-name').text($(this).val().replace('C:\\fakepath\\', ''));
$('.file-name-block').show();
$chooseBtn.hide();
$submitBtn.show();
$('.progress').show();
} else {
msg = gettext('File format not supported. Please upload a file with a {ext} extension.')
.replace('{ext}', '<code>tar.gz</code>');
$('.error-block').text(msg).show();
}
};
$(window).on('beforeunload', function(event) { unloading = true; });
if (library) {
dbError = gettext('There was an error while importing the new library to our database.');
} else {
dbError = gettext('There was an error while importing the new course to our database.');
}
$(window).on('beforeunload', function() { unloading = true; });
// Display the status of last file upload on page load
if (previousImport) {
......@@ -44,7 +67,7 @@ define([
.show();
if (previousImport.completed !== true) {
chooseBtn.hide();
$chooseBtn.hide();
}
Import.resume().then(onComplete);
......@@ -57,12 +80,12 @@ define([
autoUpload: false,
add: function(e, data) {
Import.reset();
submitBtn.unbind('click');
$submitBtn.unbind('click');
file = data.files[0];
if (file.name.match(/tar\.gz$/)) {
submitBtn.click(function(event) {
$submitBtn.click(function(event) {
event.preventDefault();
Import.start(
......@@ -70,14 +93,13 @@ define([
feedbackUrl.replace('fillerName', file.name)
).then(onComplete);
submitBtn.hide();
$submitBtn.hide();
data.submit().complete(function(result, textStatus, xhr) {
if (xhr.status !== 200) {
var serverMsg, errMsg, stage;
if (xhr.status !== 200) {
try {
serverMsg = $.parseJSON(result.responseText) || {};
} catch (e) {
} catch (err) {
return;
}
......@@ -86,10 +108,9 @@ define([
if (serverMsg.hasOwnProperty('Stage')) {
stage = Math.abs(serverMsg.Stage);
Import.cancel(defaults[stage] + errMsg, stage);
}
} else if (!unloading) {
// It could be that the user is simply refreshing the page
// so we need to be sure this is an actual error from the server
else if (!unloading) {
$(window).off('beforeunload.import');
Import.reset();
......@@ -101,7 +122,9 @@ define([
});
});
} else {
data.files = [];
// Can't fix this lint error without major structural changes, which I'm not comfortable
// doing given this file's test coverage
data.files = []; // eslint-disable-line no-param-reassign
}
},
......@@ -118,46 +141,29 @@ define([
doneAt = 99;
}
if (percentInt >= doneAt) {
bar.hide();
$bar.hide();
// Start feedback with delay so that current stage of
// import properly updates in session
setTimeout(function() { Import.pollStatus(); }, 3000);
} else {
bar.show();
fill.width(percentVal).text(percentVal);
$bar.show();
$fill.width(percentVal).text(percentVal);
}
},
sequentialUploads: true,
notifyOnError: false
});
var showImportSubmit = function(e) {
var filepath = $(this).val();
if (filepath.substr(filepath.length - 6, 6) === 'tar.gz') {
$('.error-block').hide();
$('.file-name').text($(this).val().replace('C:\\fakepath\\', ''));
$('.file-name-block').show();
chooseBtn.hide();
submitBtn.show();
$('.progress').show();
} else {
var msg = gettext('File format not supported. Please upload a file with a {file_extension} extension.')
.replace('{file_extension}', '<code>tar.gz</code>');
$('.error-block').text(msg).show();
}
};
domReady(function() {
// import form setup
$('.view-import .file-input').bind('change', showImportSubmit);
$('.view-import .choose-file-button, .view-import .choose-file-button-inline').bind('click', function(e) {
$('.view-import .choose-file-button, .view-import .choose-file-button-inline')
.bind('click', function(e) {
e.preventDefault();
$('.view-import .file-input').click();
});
});
}
};
});
......@@ -11,18 +11,18 @@ define(
var COOKIE_NAME = 'lastimportupload';
var STAGE = {
'UPLOADING': 0,
'UNPACKING': 1,
'VERIFYING': 2,
'UPDATING': 3,
'SUCCESS': 4
UPLOADING: 0,
UNPACKING: 1,
VERIFYING: 2,
UPDATING: 3,
SUCCESS: 4
};
var STATE = {
'READY': 1,
'IN_PROGRESS': 2,
'SUCCESS': 3,
'ERROR': 4
READY: 1,
IN_PROGRESS: 2,
SUCCESS: 3,
ERROR: 4
};
var current = {stage: 0, state: STATE.READY};
......@@ -35,6 +35,8 @@ define(
wrapper: $('div.wrapper-status')
};
var CourseImport;
/** ******** Private functions *****************************************/
/**
......@@ -55,32 +57,11 @@ define(
};
/**
* Sets the Import in the "error" status.
*
* Immediately stops any further polling from the server.
* Displays the error message at the list element that corresponds
* to the stage where the error occurred.
*
* @param {string} msg Error message to display.
* @param {int} [stage=current.stage] Stage of import process at which error occurred.
*/
var error = function(msg, stage) {
current.stage = Math.abs(stage || current.stage); // Could be negative
current.state = STATE.ERROR;
destroyEventListeners();
clearTimeout(timeout.id);
updateFeedbackList(msg);
deferred.resolve();
};
/**
* Initializes the event listeners
*
*/
var initEventListeners = function() {
$(window).on('beforeunload.import', function() {
$(window).on('beforeunload.import', function() { // eslint-disable-line consistent-return
if (current.stage < STAGE.UNPACKING) {
return gettext('Your import is in progress; navigating away will abort it.');
}
......@@ -101,25 +82,6 @@ define(
};
/**
* Sets the Import on the "success" status
*
* If it wasn't already, marks the stored import as "completed",
* and updates its date timestamp
*/
var success = function() {
current.state = STATE.SUCCESS;
if (CourseImport.storedImport().completed !== true) {
storeImport(true);
}
destroyEventListeners();
updateFeedbackList();
deferred.resolve();
};
/**
* Updates the Import feedback status list
*
* @param {string} [currStageMsg=''] The message to show on the
......@@ -158,8 +120,11 @@ define(
$(stage)
.removeClass('is-complete is-started has-error')
.addClass('is-not-started')
.find('p.error').remove().end()
.find('p.copy').show();
.find('p.error')
.remove()
.end()
.find('p.copy')
.show();
}
switch (current.state) {
......@@ -201,6 +166,9 @@ define(
errorStage($curr);
break;
default:
break;
}
if (current.state === STATE.SUCCESS) {
......@@ -210,9 +178,49 @@ define(
}
};
/**
* Sets the Import in the "error" status.
*
* Immediately stops any further polling from the server.
* Displays the error message at the list element that corresponds
* to the stage where the error occurred.
*
* @param {string} msg Error message to display.
* @param {int} [stage=current.stage] Stage of import process at which error occurred.
*/
var error = function(msg, stage) {
current.stage = Math.abs(stage || current.stage); // Could be negative
current.state = STATE.ERROR;
destroyEventListeners();
clearTimeout(timeout.id);
updateFeedbackList(msg);
deferred.resolve();
};
/**
* Sets the Import on the "success" status
*
* If it wasn't already, marks the stored import as "completed",
* and updates its date timestamp
*/
var success = function() {
current.state = STATE.SUCCESS;
if (CourseImport.storedImport().completed !== true) {
storeImport(true);
}
destroyEventListeners();
updateFeedbackList();
deferred.resolve();
};
/** ******** Public functions ******************************************/
var CourseImport = {
CourseImport = {
/**
* Cancels the import and sets the Object to the error state
......
......@@ -239,13 +239,8 @@ else:
%endif
</section>
</div>
<%static:webpack entry="Import">
Import('${import_status_url | n, js_escaped_string}', ${library | n, dump_js_escaped_json});
</%static:webpack>
</%block>
<%block name="requirejs">
require(["js/factories/import"], function(ImportFactory) {
ImportFactory(
"${import_status_url | n, js_escaped_string}",
${library | n, dump_js_escaped_json}
);
});
</%block>
(function(define) {
'use strict';
define(['jquery',
define(['jquery',
'underscore',
'underscore.string',
'backbone',
'text!common/templates/components/system-feedback.underscore'],
'text!../../../../common/templates/components/system-feedback.underscore'],
function($, _, str, Backbone, systemFeedbackTemplate) {
var tabbable_elements = [
"a[href]:not([tabindex='-1'])",
......@@ -197,4 +195,3 @@
});
return SystemFeedback;
});
}).call(this, define || RequireJS.define);
(function(define) {
'use strict';
define(['jquery', 'underscore', 'underscore.string', 'common/js/components/views/feedback'],
define(['jquery', 'underscore', 'underscore.string', '../../../../common/js/components/views/feedback'],
function($, _, str, SystemFeedbackView) {
var Notification = SystemFeedbackView.extend({
options: $.extend({}, SystemFeedbackView.prototype.options, {
......@@ -30,4 +28,3 @@
return Notification;
});
}).call(this, define || RequireJS.define);
module.exports = {
extends: 'eslint-config-edx',
root: true,
settings: {
'import/resolver': 'webpack',
},
};
/* globals Logger */
import { keys } from 'edx-ui-toolkit/src/js/utils/constants';
import { keys } from 'edx-ui-toolkit/js/utils/constants';
// @TODO: Figure out how to make webpack handle default exports when libraryTarget: 'window'
export class CourseOutline { // eslint-disable-line import/prefer-default-export
......
/* globals Logger, loadFixtures */
import { keys } from 'edx-ui-toolkit/src/js/utils/constants';
import { keys } from 'edx-ui-toolkit/js/utils/constants';
import { CourseOutline } from '../CourseOutline';
......
......@@ -7,16 +7,20 @@
"babel-preset-env": "^1.2.1",
"backbone": "~1.3.2",
"backbone.paginator": "~2.0.3",
"coffee-loader": "^0.7.3",
"coffee-script": "1.6.1",
"edx-pattern-library": "0.18.1",
"edx-ui-toolkit": "1.5.2",
"exports-loader": "^0.6.4",
"hls.js": "0.7.2",
"imports-loader": "^0.7.1",
"jquery": "~2.2.0",
"jquery-migrate": "^1.4.1",
"jquery.scrollto": "~2.1.2",
"moment": "^2.15.1",
"moment-timezone": "~0.5.5",
"picturefill": "~3.0.2",
"raw-loader": "^0.5.1",
"requirejs": "~2.3.2",
"uglify-js": "2.7.0",
"underscore": "~1.8.3",
......
......@@ -705,16 +705,26 @@ def execute_compile_sass(args):
def execute_webpack(prod, settings=None):
sh(cmd("NODE_ENV={node_env} STATIC_ROOT={static_root} $(npm bin)/webpack".format(
sh(
cmd(
"NODE_ENV={node_env} STATIC_ROOT_LMS={static_root_lms} STATIC_ROOT_CMS={static_root_cms} $(npm bin)/webpack"
.format(
node_env="production" if prod else "development",
static_root=Env.get_django_setting("STATIC_ROOT", "lms", settings=settings)
)))
static_root_lms=Env.get_django_setting("STATIC_ROOT", "lms", settings=settings),
static_root_cms=Env.get_django_setting("STATIC_ROOT", "cms", settings=settings)
)
)
)
def execute_webpack_watch(settings=None):
run_background_process("STATIC_ROOT={static_root} $(npm bin)/webpack --watch --watch-poll=200".format(
static_root=Env.get_django_setting("STATIC_ROOT", "lms", settings=settings)
))
run_background_process(
"STATIC_ROOT_LMS={static_root_lms} STATIC_ROOT_CMS={static_root_cms} $(npm bin)/webpack --watch --watch-poll=200"
.format(
static_root_lms=Env.get_django_setting("STATIC_ROOT", "lms", settings=settings),
static_root_cms=Env.get_django_setting("STATIC_ROOT", "cms", settings=settings)
)
)
def get_parsed_option(command_opts, opt_key, default=None):
......
......@@ -41,11 +41,12 @@ EXPECTED_RUN_SERVER_COMMAND = (
EXPECTED_INDEX_COURSE_COMMAND = (
u"python manage.py {system} --settings={settings} reindex_course --setup"
)
EXPECTED_PRINT_SETTINGS_COMMAND = (
u"python manage.py {system} --settings={settings} print_settings STATIC_ROOT --format=value 2>/dev/null"
)
EXPECTED_PRINT_SETTINGS_COMMAND = [
u"python manage.py lms --settings={settings} print_settings STATIC_ROOT --format=value 2>/dev/null",
u"python manage.py cms --settings={settings} print_settings STATIC_ROOT --format=value 2>/dev/null"
]
EXPECTED_WEBPACK_COMMAND = (
u"NODE_ENV={node_env} STATIC_ROOT={static_root} $(npm bin)/webpack"
u"NODE_ENV={node_env} STATIC_ROOT_LMS={static_root_lms} STATIC_ROOT_CMS={static_root_cms} $(npm bin)/webpack"
)
......@@ -240,13 +241,11 @@ class TestPaverServerTasks(PaverTestCase):
expected_messages.append(u"xmodule_assets common/static/xmodule")
expected_messages.append(u"install npm_assets")
expected_messages.append(EXPECTED_COFFEE_COMMAND.format(platform_root=self.platform_root))
expected_messages.append(EXPECTED_PRINT_SETTINGS_COMMAND.format(
system="lms",
settings=expected_asset_settings
))
expected_messages.extend([c.format(settings=expected_asset_settings) for c in EXPECTED_PRINT_SETTINGS_COMMAND])
expected_messages.append(EXPECTED_WEBPACK_COMMAND.format(
node_env="production" if expected_asset_settings != "devstack" else "development",
static_root=None
static_root_lms=None,
static_root_cms=None
))
expected_messages.extend(self.expected_sass_commands(system=system, asset_settings=expected_asset_settings))
if expected_collect_static:
......@@ -285,10 +284,11 @@ class TestPaverServerTasks(PaverTestCase):
expected_messages.append(u"xmodule_assets common/static/xmodule")
expected_messages.append(u"install npm_assets")
expected_messages.append(EXPECTED_COFFEE_COMMAND.format(platform_root=self.platform_root))
expected_messages.append(EXPECTED_PRINT_SETTINGS_COMMAND.format(system="lms", settings=expected_asset_settings))
expected_messages.extend([c.format(settings=expected_asset_settings) for c in EXPECTED_PRINT_SETTINGS_COMMAND])
expected_messages.append(EXPECTED_WEBPACK_COMMAND.format(
node_env="production" if expected_asset_settings != "devstack" else "development",
static_root=None
static_root_lms=None,
static_root_cms=None
))
expected_messages.extend(self.expected_sass_commands(asset_settings=expected_asset_settings))
if expected_collect_static:
......
......@@ -12,7 +12,8 @@ var wpconfig = {
context: __dirname,
entry: {
CourseOutline: './openedx/features/course_experience/static/course_experience/js/CourseOutline.js'
CourseOutline: './openedx/features/course_experience/static/course_experience/js/CourseOutline.js',
Import: './cms/static/js/features/import/factories/import.js'
},
output: {
......@@ -21,7 +22,7 @@ var wpconfig = {
libraryTarget: 'window'
},
devtool: isProd ? false : 'eval-source-map',
devtool: isProd ? false : 'source-map',
plugins: [
new webpack.NoEmitOnErrorsPlugin(),
......@@ -33,8 +34,18 @@ var wpconfig = {
debug: !isProd
}),
new BundleTracker({
path: process.env.STATIC_ROOT,
path: process.env.STATIC_ROOT_CMS,
filename: 'webpack-stats.json'
}),
new BundleTracker({
path: process.env.STATIC_ROOT_LMS,
filename: 'webpack-stats.json'
}),
new webpack.ProvidePlugin({
_: 'underscore',
$: 'jquery',
jQuery: 'jquery',
'window.jQuery': 'jquery'
})
],
......@@ -44,12 +55,51 @@ var wpconfig = {
test: /\.js$/,
exclude: /node_modules/,
use: 'babel-loader'
},
{
test: /\.coffee$/,
exclude: /node_modules/,
use: 'coffee-loader'
},
{
test: /\.underscore$/,
use: 'raw-loader'
},
{
// This file is used by both RequireJS and Webpack and depends on window globals
// This is a dirty hack and shouldn't be replicated for other files.
test: path.resolve(__dirname, 'cms/static/cms/js/main.js'),
use: {
loader: 'imports-loader',
options: {
AjaxPrefix: 'exports-loader?this.AjaxPrefix!../../../../common/static/coffee/src/ajax_prefix.coffee'
}
}
}
]
},
resolve: {
extensions: ['.js', '.json']
extensions: ['.js', '.json', '.coffee'],
alias: {
'edx-ui-toolkit': 'edx-ui-toolkit/src/', // @TODO: some paths in toolkit are not valid relative paths
'jquery.ui': 'jQuery-File-Upload/js/vendor/jquery.ui.widget.js',
jquery: 'jquery/src/jquery' // Use the non-dist form of jQuery for better debugging + optimization
},
modules: [
'node_modules',
'common/static/js/vendor/'
]
},
resolveLoader: {
alias: {
text: 'raw-loader' // Compatibility with RequireJSText's text! loader, uses raw-loader under the hood
}
},
externals: {
gettext: 'gettext'
},
watchOptions: {
......@@ -59,7 +109,7 @@ var wpconfig = {
if (isProd) {
wpconfig.plugins = wpconfig.plugins.concat([
new webpack.LoaderOptionsPlugin({
new webpack.LoaderOptionsPlugin({ // This may not be needed; legacy option for loaders written for webpack 1
minimize: true
}),
new webpack.optimize.UglifyJsPlugin()
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment