401 lines
13 KiB
JavaScript
401 lines
13 KiB
JavaScript
/**
|
|
* Course export-related js.
|
|
*/
|
|
define([
|
|
'jquery', 'underscore', 'gettext', 'moment', 'common/js/components/views/feedback_prompt',
|
|
'edx-ui-toolkit/js/utils/html-utils', 'jquery.cookie'
|
|
], function($, _, gettext, moment, PromptView, HtmlUtils) {
|
|
'use strict';
|
|
|
|
/** ******** Private properties ****************************************/
|
|
|
|
var COOKIE_NAME = 'lastexport';
|
|
|
|
var STAGE = {
|
|
PREPARING: 0,
|
|
EXPORTING: 1,
|
|
COMPRESSING: 2,
|
|
SUCCESS: 3
|
|
};
|
|
|
|
var STATE = {
|
|
READY: 1,
|
|
IN_PROGRESS: 2,
|
|
SUCCESS: 3,
|
|
ERROR: 4
|
|
};
|
|
|
|
var courselikeHomeUrl;
|
|
var current = {stage: 0, state: STATE.READY, downloadUrl: null};
|
|
var deferred = null;
|
|
var isLibrary = false;
|
|
var statusUrl = null;
|
|
var successUnixDate = null;
|
|
var timeout = {id: null, delay: 1000};
|
|
var $dom = {
|
|
downloadLink: $('#download-exported-button'),
|
|
stages: $('ol.status-progress').children(),
|
|
successStage: $('.item-progresspoint-success'),
|
|
wrapper: $('div.wrapper-status')
|
|
};
|
|
|
|
/** ******** Private functions *****************************************/
|
|
|
|
/**
|
|
* Makes Export feedback status list visible
|
|
*
|
|
*/
|
|
var displayFeedbackList = function() {
|
|
$dom.wrapper.removeClass('is-hidden');
|
|
};
|
|
|
|
/**
|
|
* Updates the Export feedback status list
|
|
*
|
|
* @param {string} [currStageMsg=''] The message to show on the
|
|
* current stage (for now only in case of error)
|
|
*/
|
|
var updateFeedbackList = function(currStageMsg) {
|
|
var $checkmark, $curr, $prev, $next;
|
|
var date, stageMsg, time;
|
|
|
|
$checkmark = $dom.successStage.find('.icon');
|
|
stageMsg = currStageMsg || '';
|
|
|
|
function completeStage(stage) {
|
|
$(stage)
|
|
.removeClass('is-not-started is-started')
|
|
.addClass('is-complete');
|
|
}
|
|
|
|
function errorStage(stage) {
|
|
if (!$(stage).hasClass('has-error')) {
|
|
stageMsg = HtmlUtils.joinHtml(
|
|
HtmlUtils.HTML('<p class="copy error">'),
|
|
stageMsg,
|
|
HtmlUtils.HTML('</p>')
|
|
);
|
|
$(stage)
|
|
.removeClass('is-started')
|
|
.addClass('has-error')
|
|
.find('p.copy')
|
|
.hide()
|
|
.after(HtmlUtils.ensureHtml(stageMsg).toString());
|
|
}
|
|
}
|
|
|
|
function resetStage(stage) {
|
|
$(stage)
|
|
.removeClass('is-complete is-started has-error')
|
|
.addClass('is-not-started')
|
|
.find('p.error')
|
|
.remove()
|
|
.end()
|
|
.find('p.copy')
|
|
.show();
|
|
}
|
|
|
|
switch (current.state) {
|
|
case STATE.READY:
|
|
_.map($dom.stages, resetStage);
|
|
|
|
break;
|
|
|
|
case STATE.IN_PROGRESS:
|
|
$prev = $dom.stages.slice(0, current.stage);
|
|
$curr = $dom.stages.eq(current.stage);
|
|
|
|
_.map($prev, completeStage);
|
|
$curr.removeClass('is-not-started').addClass('is-started');
|
|
|
|
break;
|
|
|
|
case STATE.SUCCESS:
|
|
date = moment(successUnixDate).utc().format('MM/DD/YYYY');
|
|
time = moment(successUnixDate).utc().format('HH:mm');
|
|
|
|
_.map($dom.stages, completeStage);
|
|
|
|
$dom.successStage
|
|
.find('.item-progresspoint-success-date')
|
|
.text('(' + date + ' at ' + time + ' UTC)');
|
|
|
|
break;
|
|
|
|
case STATE.ERROR:
|
|
// Make all stages up to, and including, the error stage 'complete'.
|
|
$prev = $dom.stages.slice(0, current.stage + 1);
|
|
$curr = $dom.stages.eq(current.stage);
|
|
$next = $dom.stages.slice(current.stage + 1);
|
|
|
|
_.map($prev, completeStage);
|
|
_.map($next, resetStage);
|
|
errorStage($curr);
|
|
|
|
break;
|
|
|
|
default:
|
|
// Invalid state, don't change anything
|
|
return;
|
|
}
|
|
|
|
if (current.state === STATE.SUCCESS) {
|
|
$checkmark.removeClass('fa-square-o').addClass('fa-check-square-o');
|
|
$dom.downloadLink.attr('href', current.downloadUrl);
|
|
} else {
|
|
$checkmark.removeClass('fa-check-square-o').addClass('fa-square-o');
|
|
$dom.downloadLink.attr('href', '#');
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Sets the Export 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 export process at which error occurred.
|
|
*/
|
|
var error = function(msg, stage) {
|
|
current.stage = Math.abs(stage || current.stage); // Could be negative
|
|
current.state = STATE.ERROR;
|
|
|
|
clearTimeout(timeout.id);
|
|
updateFeedbackList(msg);
|
|
|
|
deferred.resolve();
|
|
};
|
|
|
|
/**
|
|
* Stores in a cookie the current export data
|
|
*
|
|
* @param {boolean} [completed=false] If the export has been completed or not
|
|
*/
|
|
var storeExport = function(completed) {
|
|
$.cookie(COOKIE_NAME, JSON.stringify({
|
|
statusUrl: statusUrl,
|
|
date: moment().valueOf(),
|
|
completed: completed || false
|
|
}), {path: window.location.pathname});
|
|
};
|
|
|
|
/** ******** Public functions ******************************************/
|
|
|
|
var CourseExport = {
|
|
/**
|
|
* Fetches the previous stored export
|
|
*
|
|
* @param {string} contentHomeUrl the full URL to the course or library being exported
|
|
* @return {JSON} the data of the previous export
|
|
*/
|
|
storedExport: function(contentHomeUrl) {
|
|
var storedData = JSON.parse($.cookie(COOKIE_NAME));
|
|
if (storedData) {
|
|
successUnixDate = storedData.date;
|
|
}
|
|
if (contentHomeUrl) {
|
|
courselikeHomeUrl = contentHomeUrl;
|
|
}
|
|
return storedData;
|
|
},
|
|
|
|
/**
|
|
* Sets the Export on the "success" status
|
|
*
|
|
* If it wasn't already, marks the stored export as "completed",
|
|
* and updates its date timestamp
|
|
*/
|
|
success: function() {
|
|
current.state = STATE.SUCCESS;
|
|
|
|
if (this.storedExport().completed !== true) {
|
|
storeExport(true);
|
|
}
|
|
|
|
updateFeedbackList();
|
|
|
|
deferred.resolve();
|
|
},
|
|
|
|
/**
|
|
* Entry point for server feedback
|
|
*
|
|
* Checks for export status updates every `timeout` milliseconds,
|
|
* and updates the page accordingly.
|
|
*
|
|
* @param {int} [stage=0] Starting stage.
|
|
*/
|
|
pollStatus: function(data) {
|
|
var editUnitUrl = null,
|
|
msg = data;
|
|
if (current.state !== STATE.IN_PROGRESS) {
|
|
return;
|
|
}
|
|
|
|
current.stage = data.ExportStatus || STAGE.PREPARING;
|
|
|
|
if (current.stage === STAGE.SUCCESS) {
|
|
current.downloadUrl = data.ExportOutput;
|
|
this.success();
|
|
} else if (current.stage < STAGE.PREPARING) { // Failed
|
|
if (data.ExportError) {
|
|
msg = data.ExportError;
|
|
}
|
|
if (msg.raw_error_msg) {
|
|
editUnitUrl = msg.edit_unit_url;
|
|
msg = msg.raw_error_msg;
|
|
}
|
|
error(msg);
|
|
this.showError(editUnitUrl, msg);
|
|
} else { // In progress
|
|
updateFeedbackList();
|
|
|
|
$.getJSON(statusUrl, function(result) {
|
|
timeout.id = setTimeout(function() {
|
|
this.pollStatus(result);
|
|
}.bind(this), timeout.delay);
|
|
}.bind(this));
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Resets the Export internally and visually
|
|
*
|
|
*/
|
|
reset: function(library) {
|
|
current.stage = STAGE.PREPARING;
|
|
current.state = STATE.READY;
|
|
current.downloadUrl = null;
|
|
isLibrary = library;
|
|
|
|
clearTimeout(timeout.id);
|
|
updateFeedbackList();
|
|
},
|
|
|
|
/**
|
|
* Show last export status from server and start sending requests
|
|
* to the server for status updates
|
|
*
|
|
* @return {jQuery promise}
|
|
*/
|
|
resume: function(library) {
|
|
deferred = $.Deferred();
|
|
isLibrary = library;
|
|
statusUrl = this.storedExport().statusUrl;
|
|
|
|
$.getJSON(statusUrl, function(data) {
|
|
current.stage = data.ExportStatus;
|
|
current.downloadUrl = data.ExportOutput;
|
|
|
|
displayFeedbackList();
|
|
current.state = STATE.IN_PROGRESS;
|
|
this.pollStatus(data);
|
|
}.bind(this));
|
|
|
|
return deferred.promise();
|
|
},
|
|
|
|
/**
|
|
* Show a dialog giving further information about the details of an export error.
|
|
*
|
|
* @param {string} editUnitUrl URL of the unit in which the error occurred, if known
|
|
* @param {string} errMsg Detailed error message
|
|
*/
|
|
showError: function(editUnitUrl, errMsg) {
|
|
var action,
|
|
dialog,
|
|
msg = '';
|
|
if (editUnitUrl) {
|
|
dialog = new PromptView({
|
|
title: gettext('There has been an error while exporting.'),
|
|
message: gettext('There has been a failure to export to XML at least one component. ' +
|
|
'It is recommended that you go to the edit page and repair the error before attempting ' +
|
|
'another export. Please check that all components on the page are valid and do not display ' +
|
|
'any error messages.'),
|
|
intent: 'error',
|
|
actions: {
|
|
primary: {
|
|
text: gettext('Correct failed component'),
|
|
click: function(view) {
|
|
view.hide();
|
|
document.location = editUnitUrl;
|
|
}
|
|
},
|
|
secondary: {
|
|
text: gettext('Return to Export'),
|
|
click: function(view) {
|
|
view.hide();
|
|
}
|
|
}
|
|
}
|
|
});
|
|
} else {
|
|
if (isLibrary) {
|
|
msg += gettext('Your library could not be exported to XML. There is not enough information to ' +
|
|
'identify the failed component. Inspect your library to identify any problematic components ' +
|
|
'and try again.');
|
|
action = gettext('Take me to the main library page');
|
|
} else {
|
|
msg += gettext('Your course could not be exported to XML. There is not enough information to ' +
|
|
'identify the failed component. Inspect your course to identify any problematic components ' +
|
|
'and try again.');
|
|
action = gettext('Take me to the main course page');
|
|
}
|
|
msg += ' ' + gettext('The raw error message is:') + ' ' + errMsg;
|
|
dialog = new PromptView({
|
|
title: gettext('There has been an error with your export.'),
|
|
message: msg,
|
|
intent: 'error',
|
|
actions: {
|
|
primary: {
|
|
text: action,
|
|
click: function(view) {
|
|
view.hide();
|
|
document.location = courselikeHomeUrl;
|
|
}
|
|
},
|
|
secondary: {
|
|
text: gettext('Cancel'),
|
|
click: function(view) {
|
|
view.hide();
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// The CSS animation for the dialog relies on the 'js' class
|
|
// being on the body. This happens after this JavaScript is executed,
|
|
// causing a 'bouncing' of the dialog after it is initially shown.
|
|
// As a workaround, add this class first.
|
|
$('body').addClass('js');
|
|
dialog.show();
|
|
},
|
|
|
|
/**
|
|
* Starts the exporting process.
|
|
* Makes status list visible and starts showing export progress.
|
|
*
|
|
* @param {string} url The full URL to use to query the server
|
|
* about the export status
|
|
* @return {jQuery promise}
|
|
*/
|
|
start: function(url) {
|
|
current.state = STATE.IN_PROGRESS;
|
|
deferred = $.Deferred();
|
|
|
|
statusUrl = url;
|
|
|
|
storeExport();
|
|
displayFeedbackList();
|
|
updateFeedbackList();
|
|
|
|
return deferred.promise();
|
|
}
|
|
};
|
|
|
|
return CourseExport;
|
|
});
|