tao-test/app/taoTaskQueue/views/js/component/manager/manager.js

419 lines
15 KiB
JavaScript

/**
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; under version 2
* of the License (non-upgradable).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* Copyright (c) 2017 (original work) Open Assessment Technologies SA ;
*/
define([
'jquery',
'lodash',
'i18n',
'layout/loading-bar',
'ui/hider',
'ui/component',
'ui/badge/badge',
'ui/component/alignable',
'ui/animable/absorbable/absorbable',
'ui/animable/pulsable/pulsable',
'taoTaskQueue/component/listing/element',
'taoTaskQueue/component/listing/report',
'taoTaskQueue/component/listing/list',
'tpl!taoTaskQueue/component/manager/tpl/manager',
'css!taoTaskQueue/component/manager/css/manager'
], function ($, _, __, loadingBar, hider, component, badgeFactory, makeAlignable, makeAbsorbable, makePulsable, listElementFactory, reportElementFactory, taskListFactory, managerTpl) {
'use strict';
/**
* Transform the task log summary into a configuration set for the badge
* @param {Object} tasksStatuses - the task log summary
* @returns {Object} the new badge data to be displayed following the format {type, loading, value}
*/
var getBadgeDataFromStatus = function getBadgeDataFromStatus(tasksStatuses){
var total = 0;
var data = {loading : false};
if(tasksStatuses){
if(tasksStatuses.numberOfTasksInProgress){
total += parseInt(tasksStatuses.numberOfTasksInProgress, 10);
data.type = 'info';
data.loading = (tasksStatuses.numberOfTasksInProgress > 0);
}
if(tasksStatuses.numberOfTasksCompleted){
total += parseInt(tasksStatuses.numberOfTasksCompleted, 10);
data.type = 'success';
}
if(tasksStatuses.numberOfTasksFailed){
total += parseInt(tasksStatuses.numberOfTasksFailed, 10);
if(data.type === 'success'){
data.type = 'warning';//if there are both success and failures, the status should be a warning
}else{
data.type = 'error';
}
}
data.value = total;
return data;
}
};
/**
* Transform the internal list of elements into a configuration set for the badge
* @param {Object} elements - internal collection of task elements
* @returns {Object} the new badge data to be displayed following the format {type, loading, value}
*/
var getBadgeDataFromElements = function getBadgeDataFromElements(elements){
var statusMap = {
in_progress: 'numberOfTasksInProgress',
created: 'numberOfTasksInProgress',
failed: 'numberOfTasksFailed',
completed: 'numberOfTasksCompleted',
};
var stats = {
numberOfTasksFailed : 0,
numberOfTasksCompleted : 0,
numberOfTasksInProgress : 0
};
_.forEach(elements, function(element){
var status = element.getStatus();
if(statusMap[status]){
//it is a know state, so add to the stats array
stats[statusMap[status]]++;
}
});
return getBadgeDataFromStatus(stats);
};
/**
* The collection of config used for component alignments
* the values have been adjusted to make it work well with the list's css
* @type {Object}
* @private
*/
var _alignementConfig = {
initial : {
hPos: 'center',
hOrigin: 'center',
vPos: 'bottom',
vOrigin: 'top',
hOffset: -156
},
showDetail : {
hPos: 'center',
hOrigin: 'center',
vPos: 'bottom',
vOrigin: 'top',
hOffset: -156-121
}
};
var taskQueue = {
/**
* Get the list of task elements
* @returns {taskQueueManager} - self for chaining
*/
getTaskElements : function getTaskElements(){
return this.taskElements;
},
/**
* Show the details associated to a task
* @param {Object} tasksData - a single task data to view the report form
* @returns {taskQueueManager} - self for chaining
*/
showDetail : function showDetail(taskData){
var $component = this.getElement();
var list = this.list;
var reportElement = reportElementFactory({replace:true}, taskData)
.on('close', function(){
list.hideDetail();
list.alignWith($component, _alignementConfig.initial);
this.destroy();
});
list.setDetail(reportElement, true);
list.alignWith($component, _alignementConfig.showDetail);
return this;
},
/**
* Add a new task
* @param {Object} tasksData - a single task data to be added to the list
* @param {Boolean} [animate=false] - tells if the new task addition should be made through a smooth transition effect
* @returns {taskQueueManager} - self for chaining
*/
addNewTask : function addNewTask(taskData, animate){
var self = this;
var taskId = taskData.id;
var listElement = listElementFactory({}, taskData)
.on('remove', function(){
delete self.taskElements[taskId];
self.list.removeElement(listElement);
self.trigger('remove', taskId);
self.trigger('listchange');
})
.on('report', function(){
self.trigger('report', taskId);
})
.on('download', function(){
self.trigger('download', taskId);
})
.on('redirect', function(){
loadingBar.start();
self.hideList();
self.trigger('redirect', taskId);
});
if(animate){
if(this.list.is('hidden')){
this.list.show();
}
this.list.scrollToTop();
}
this.list.insertElement(listElement);
this.taskElements[taskId] = listElement;
this.trigger('listchange');
if(animate){
this.list.animateInsertion(listElement);
}
return this;
},
/**
* Update the badge display according to the current status of the tasks in the list
* @returns {taskQueueManager} - self for chaining
*/
selfUpdateBadge : function selfUpdateBadge(){
var badgeData = getBadgeDataFromElements(this.getTaskElements());
if(!this.badge){
this.badge = makePulsable(badgeFactory(badgeData)).render(this.getElement());
}else{
this.badge.update(badgeData);
}
return this;
},
/**
* Load the the array of task element data requested form the server REST API
* @param {Array} tasksData - the task data to be loaded from the server REST API call
* @returns {taskQueueManager} - self for chaining
*/
loadData : function loadData(tasksData){
var self = this;
var found = [];
if (tasksData.length) {
tasksData = tasksData.reverse();
}
_.forEach(tasksData, function(entry){
var id = entry.id;
if(self.taskElements[id]){
//update
self.taskElements[id].update(entry);
if(self.taskElements[id].getStatus() !== entry.status){
//highlight status change only
self.taskElements[id].highlight();
}
}else{
//create
self.addNewTask(entry);
}
found.push(id);
});
//remove any element from the list that are not found in the new data collection (task has been archived server side)
_.forEach(_.difference(_.keys(self.taskElements), found), function(id){
self.list.removeElement(self.taskElements[id]);
delete self.taskElements[id];
});
this.trigger('listchange');
return this;
},
/**
* Trigger the pulse animation on the status badge
* @returns {taskQueueManager} - self for chaining
*/
pulse : function pulse(){
if(this.badge){
this.badge.pulse(3);
}
return this;
},
/**
* Show the list component
* @event listshow - Emitted when the list is displayed
* @returns {taskQueueManager} - self for chaining
*/
showList : function showList() {
if (this.list) {
this.list.show();
this.trigger('listshow');
}
return this;
},
/**
* Hide the list component
* @event listhide - Emitted when the list is hidden
* @returns {taskQueueManager} - self for chaining
*/
hideList : function hideList() {
if (this.list) {
this.list.hide();
this.trigger('listhide');
}
return this;
},
/**
* Remove all finished element (completed or failed) from the list
* @returns {taskQueueManager}
*/
removeAllFinished : function removeAllFinished(){
var self = this;
_(this.taskElements).filter(function(element){
return (element.getStatus() === 'completed' || element.getStatus() === 'failed');
}).forEach(function(element){
delete self.taskElements[element.getId()];
self.list.removeElement(element);
self.trigger('listchange');
});
this.trigger('listclearfinished');
return this;
},
/**
* Toggle the visibility of the clear all button according to the element status
* @returns {taskQueueManager}
*/
selfUpdateClearButton : function selfUpdateClearButton(){
var $clearAllBox = this.getElement().find('.clear-box');
var finishedCount = _(this.taskElements).map(function(element){
return element.getStatus();
}).filter(function(status){
return (status === 'completed' || status === 'failed');
}).size();
if(finishedCount > 0){
//show button
hider.show($clearAllBox);
}else{
hider.hide($clearAllBox);
}
return this;
}
};
/**
* Builds the task queue manager
* @param {Object} config - the component config
* @param {Array} data - the initial task data to be loaded from the server REST api call
* @returns {taskQueueManager} the component
*
* @event remove - Emitted when a list element is removed
* @event download - Emitted when a list element requests the file download associated to a completed task
* @event report - Emitted when a list element requests a task report to be displayed
* @event redirect - Emitted when a list element requests a redirection to the created resource
* @event listchange - Emitted when the list element list has been updated
* @event listclearfinished - Emitted when all the finished element from the list is removed
*/
return function taskQueueManagerFactory(config, data) {
data = data || {};
/**
* The component
* @typedef {ui/component} taskQueueManager
*/
return makeAbsorbable(component(taskQueue))
.setTemplate(managerTpl)
.on('destroy listhide', function(){
$(document).off('click.task-queue-manager');
})
.on('init', function() {
//initialize the task element collection
this.taskElements = {};
//create the list
this.list = makeAlignable(taskListFactory())
.init({
title : __('Background tasks'),
emptyText : __('There is currently no background task'),
});
})
.on('listshow', function(){
var self = this;
var $component = this.getElement();
//close the popup when clicking outside of the component
$(document).off('click.task-queue-manager').on('click.task-queue-manager', function(e){
if($component.get(0) !== e.target && !$.contains($component.get(0), e.target)){
if(!self.list.is('hidden')){
self.hideList();
}
}
});
})
.on('listchange', function(){
this.selfUpdateBadge();
this.selfUpdateClearButton();
})
.on('render', function() {
var self = this;
var $trigger = this.getElement();
//position the list
this.list.render($trigger)
.moveBy(0, 0)
.alignWith($trigger, _alignementConfig.initial)
.hide();//start hidden
//load initial data
this.loadData(data);
//prevent closing the panel when clicking on it
this.list.getElement()
.addClass('overflown-element')
.on('click', function(e){
e.stopPropagation();
});
this.list.on('clearall', function(){
self.removeAllFinished();
});
//toggle list visibility
$trigger.on('click', function(){
if(self.list.is('hidden')){
self.showList();
}else{
self.hideList();
}
});
})
.init(config || {});
};
});