From a718b102f8f279ec8faeea6ccd4e5de311f49b2c Mon Sep 17 00:00:00 2001 From: Philip ABBET <philip.abbet@idiap.ch> Date: Fri, 25 Aug 2017 14:41:27 +0200 Subject: [PATCH] Reformatting of the JS files --- .../accounts/static/accounts/js/dialogs.js | 8 +- .../algorithms/static/algorithms/js/editor.js | 8 +- .../algorithms/static/algorithms/js/models.js | 8 +- .../static/attestations/js/dialogs.js | 8 +- beat/web/backend/static/backend/js/models.js | 8 +- .../databases/static/databases/js/models.js | 8 +- .../databases/static/databases/js/utils.js | 8 +- .../static/dataformats/js/models.js | 8 +- .../static/experiments/js/dialogs.js | 8 +- .../static/experiments/js/utils.js | 3852 ++++++++--------- .../static/plotters/app/app.config.js | 8 +- beat/web/plotters/static/plotters/app/app.js | 8 +- .../controllers/plotterparameterController.js | 10 +- .../directives/plotterparameterItemView.js | 12 +- .../plotters/app/factories/plotterFactory.js | 8 +- .../app/factories/plotterparameterFactory.js | 8 +- .../plotters/static/plotters/js/dialogs.js | 8 +- .../js/new_plotterparameter_dialog.js | 8 +- .../reports/static/reports/app/app.config.js | 8 +- beat/web/reports/static/reports/app/app.js | 16 +- .../app/controllers/groupsController.js | 56 +- .../app/controllers/reportController.js | 4 +- .../reports/app/directives/bootstrapModal.js | 74 +- .../reports/app/directives/downloadLink.js | 80 +- .../reports/app/directives/dragHandle.js | 28 +- .../app/directives/edit/addGroupMenu.js | 70 +- .../app/directives/edit/addItemsMenu.js | 204 +- .../app/directives/edit/tableFieldSelector.js | 302 +- .../reports/app/directives/editableLabel.js | 40 +- .../static/reports/app/directives/error.js | 98 +- .../app/directives/experimentsTable.js | 200 +- .../static/reports/app/directives/layout.js | 62 +- .../static/reports/app/directives/lock.js | 48 +- .../reports/app/directives/panelContainer.js | 68 +- .../reports/app/directives/panelContent.js | 102 +- .../app/directives/panelExperiments.js | 232 +- .../reports/app/directives/panelItems.js | 78 +- .../static/reports/app/directives/plotItem.js | 224 +- .../static/reports/app/directives/publish.js | 156 +- .../static/reports/app/directives/save.js | 56 +- .../reports/app/directives/tableItem.js | 462 +- .../static/reports/app/directives/textItem.js | 228 +- .../app/factories/experimentFactory.js | 46 +- .../reports/app/factories/plotterFactory.js | 30 +- .../reports/app/factories/reportFactory.js | 184 +- .../reports/app/services/errorService.js | 48 +- .../app/services/experimentsService.js | 410 +- .../reports/app/services/groupsService.js | 462 +- .../reports/app/services/plotService.js | 384 +- .../reports/app/services/reportService.js | 224 +- .../static/reports/app/services/urlService.js | 122 +- .../reports/js/base_64_encoder_decoder.js | 8 +- .../web/reports/static/reports/js/download.js | 274 +- .../static/reports/js/new_report_dialog.js | 8 +- .../static/reports/test/report-spec.js | 1906 ++++---- .../reports/static/reports/test/test-spec.js | 28 +- .../toolchains/static/toolchains/js/common.js | 8 +- .../static/toolchains/js/dialogs.js | 8 +- .../toolchains/static/toolchains/js/editor.js | 14 +- .../toolchains/static/toolchains/js/models.js | 8 +- .../toolchains/static/toolchains/js/utils.js | 8 +- .../toolchains/static/toolchains/js/viewer.js | 12 +- beat/web/ui/static/ui/js/history.js | 8 +- beat/web/ui/static/ui/js/listselector.js | 8 +- beat/web/ui/static/ui/js/multipleselector.js | 8 +- .../ui/js/multipleselector_for_report.js | 8 +- .../ui/static/ui/js/plotterparameterdialog.js | 8 +- beat/web/ui/static/ui/js/save_as_dialog.js | 8 +- beat/web/ui/static/ui/js/smartselector.js | 10 +- buildout.cfg | 2 +- 70 files changed, 5571 insertions(+), 5571 deletions(-) diff --git a/beat/web/accounts/static/accounts/js/dialogs.js b/beat/web/accounts/static/accounts/js/dialogs.js index 2e65ddcaf..1d14883c8 100644 --- a/beat/web/accounts/static/accounts/js/dialogs.js +++ b/beat/web/accounts/static/accounts/js/dialogs.js @@ -1,21 +1,21 @@ /* * Copyright (c) 2016 Idiap Research Institute, http://www.idiap.ch/ * Contact: beat.support@idiap.ch - * + * * This file is part of the beat.web module of the BEAT platform. - * + * * Commercial License Usage * Licensees holding valid commercial BEAT licenses may use this file in * accordance with the terms contained in a written agreement between you * and Idiap. For further information contact tto@idiap.ch - * + * * Alternatively, this file may be used under the terms of the GNU Affero * Public License version 3 as published by the Free Software and appearing * in the file LICENSE.AGPL included in the packaging of this file. * The BEAT platform 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. - * + * * You should have received a copy of the GNU Affero Public License along * with the BEAT platform. If not, see http://www.gnu.org/licenses/. */ diff --git a/beat/web/algorithms/static/algorithms/js/editor.js b/beat/web/algorithms/static/algorithms/js/editor.js index 33654c6dc..c06bc00b8 100644 --- a/beat/web/algorithms/static/algorithms/js/editor.js +++ b/beat/web/algorithms/static/algorithms/js/editor.js @@ -1,21 +1,21 @@ /* * Copyright (c) 2016 Idiap Research Institute, http://www.idiap.ch/ * Contact: beat.support@idiap.ch - * + * * This file is part of the beat.web module of the BEAT platform. - * + * * Commercial License Usage * Licensees holding valid commercial BEAT licenses may use this file in * accordance with the terms contained in a written agreement between you * and Idiap. For further information contact tto@idiap.ch - * + * * Alternatively, this file may be used under the terms of the GNU Affero * Public License version 3 as published by the Free Software and appearing * in the file LICENSE.AGPL included in the packaging of this file. * The BEAT platform 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. - * + * * You should have received a copy of the GNU Affero Public License along * with the BEAT platform. If not, see http://www.gnu.org/licenses/. */ diff --git a/beat/web/algorithms/static/algorithms/js/models.js b/beat/web/algorithms/static/algorithms/js/models.js index 1b12b6416..ded7a7631 100644 --- a/beat/web/algorithms/static/algorithms/js/models.js +++ b/beat/web/algorithms/static/algorithms/js/models.js @@ -1,21 +1,21 @@ /* * Copyright (c) 2016 Idiap Research Institute, http://www.idiap.ch/ * Contact: beat.support@idiap.ch - * + * * This file is part of the beat.web module of the BEAT platform. - * + * * Commercial License Usage * Licensees holding valid commercial BEAT licenses may use this file in * accordance with the terms contained in a written agreement between you * and Idiap. For further information contact tto@idiap.ch - * + * * Alternatively, this file may be used under the terms of the GNU Affero * Public License version 3 as published by the Free Software and appearing * in the file LICENSE.AGPL included in the packaging of this file. * The BEAT platform 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. - * + * * You should have received a copy of the GNU Affero Public License along * with the BEAT platform. If not, see http://www.gnu.org/licenses/. */ diff --git a/beat/web/attestations/static/attestations/js/dialogs.js b/beat/web/attestations/static/attestations/js/dialogs.js index 51e46781b..e9e918bbe 100644 --- a/beat/web/attestations/static/attestations/js/dialogs.js +++ b/beat/web/attestations/static/attestations/js/dialogs.js @@ -1,21 +1,21 @@ /* * Copyright (c) 2016 Idiap Research Institute, http://www.idiap.ch/ * Contact: beat.support@idiap.ch - * + * * This file is part of the beat.web module of the BEAT platform. - * + * * Commercial License Usage * Licensees holding valid commercial BEAT licenses may use this file in * accordance with the terms contained in a written agreement between you * and Idiap. For further information contact tto@idiap.ch - * + * * Alternatively, this file may be used under the terms of the GNU Affero * Public License version 3 as published by the Free Software and appearing * in the file LICENSE.AGPL included in the packaging of this file. * The BEAT platform 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. - * + * * You should have received a copy of the GNU Affero Public License along * with the BEAT platform. If not, see http://www.gnu.org/licenses/. */ diff --git a/beat/web/backend/static/backend/js/models.js b/beat/web/backend/static/backend/js/models.js index 33ef15c74..6f2ed43d7 100644 --- a/beat/web/backend/static/backend/js/models.js +++ b/beat/web/backend/static/backend/js/models.js @@ -1,21 +1,21 @@ /* * Copyright (c) 2016 Idiap Research Institute, http://www.idiap.ch/ * Contact: beat.support@idiap.ch - * + * * This file is part of the beat.web module of the BEAT platform. - * + * * Commercial License Usage * Licensees holding valid commercial BEAT licenses may use this file in * accordance with the terms contained in a written agreement between you * and Idiap. For further information contact tto@idiap.ch - * + * * Alternatively, this file may be used under the terms of the GNU Affero * Public License version 3 as published by the Free Software and appearing * in the file LICENSE.AGPL included in the packaging of this file. * The BEAT platform 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. - * + * * You should have received a copy of the GNU Affero Public License along * with the BEAT platform. If not, see http://www.gnu.org/licenses/. */ diff --git a/beat/web/databases/static/databases/js/models.js b/beat/web/databases/static/databases/js/models.js index adb2c016a..c11024808 100644 --- a/beat/web/databases/static/databases/js/models.js +++ b/beat/web/databases/static/databases/js/models.js @@ -1,21 +1,21 @@ /* * Copyright (c) 2016 Idiap Research Institute, http://www.idiap.ch/ * Contact: beat.support@idiap.ch - * + * * This file is part of the beat.web module of the BEAT platform. - * + * * Commercial License Usage * Licensees holding valid commercial BEAT licenses may use this file in * accordance with the terms contained in a written agreement between you * and Idiap. For further information contact tto@idiap.ch - * + * * Alternatively, this file may be used under the terms of the GNU Affero * Public License version 3 as published by the Free Software and appearing * in the file LICENSE.AGPL included in the packaging of this file. * The BEAT platform 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. - * + * * You should have received a copy of the GNU Affero Public License along * with the BEAT platform. If not, see http://www.gnu.org/licenses/. */ diff --git a/beat/web/databases/static/databases/js/utils.js b/beat/web/databases/static/databases/js/utils.js index 6d6a6a7ee..463786d7c 100644 --- a/beat/web/databases/static/databases/js/utils.js +++ b/beat/web/databases/static/databases/js/utils.js @@ -1,21 +1,21 @@ /* * Copyright (c) 2016 Idiap Research Institute, http://www.idiap.ch/ * Contact: beat.support@idiap.ch - * + * * This file is part of the beat.web module of the BEAT platform. - * + * * Commercial License Usage * Licensees holding valid commercial BEAT licenses may use this file in * accordance with the terms contained in a written agreement between you * and Idiap. For further information contact tto@idiap.ch - * + * * Alternatively, this file may be used under the terms of the GNU Affero * Public License version 3 as published by the Free Software and appearing * in the file LICENSE.AGPL included in the packaging of this file. * The BEAT platform 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. - * + * * You should have received a copy of the GNU Affero Public License along * with the BEAT platform. If not, see http://www.gnu.org/licenses/. */ diff --git a/beat/web/dataformats/static/dataformats/js/models.js b/beat/web/dataformats/static/dataformats/js/models.js index afa6de56f..cd3e0054a 100644 --- a/beat/web/dataformats/static/dataformats/js/models.js +++ b/beat/web/dataformats/static/dataformats/js/models.js @@ -1,21 +1,21 @@ /* * Copyright (c) 2016 Idiap Research Institute, http://www.idiap.ch/ * Contact: beat.support@idiap.ch - * + * * This file is part of the beat.web module of the BEAT platform. - * + * * Commercial License Usage * Licensees holding valid commercial BEAT licenses may use this file in * accordance with the terms contained in a written agreement between you * and Idiap. For further information contact tto@idiap.ch - * + * * Alternatively, this file may be used under the terms of the GNU Affero * Public License version 3 as published by the Free Software and appearing * in the file LICENSE.AGPL included in the packaging of this file. * The BEAT platform 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. - * + * * You should have received a copy of the GNU Affero Public License along * with the BEAT platform. If not, see http://www.gnu.org/licenses/. */ diff --git a/beat/web/experiments/static/experiments/js/dialogs.js b/beat/web/experiments/static/experiments/js/dialogs.js index 67c2b7092..e87121f51 100644 --- a/beat/web/experiments/static/experiments/js/dialogs.js +++ b/beat/web/experiments/static/experiments/js/dialogs.js @@ -1,21 +1,21 @@ /* * Copyright (c) 2016 Idiap Research Institute, http://www.idiap.ch/ * Contact: beat.support@idiap.ch - * + * * This file is part of the beat.web module of the BEAT platform. - * + * * Commercial License Usage * Licensees holding valid commercial BEAT licenses may use this file in * accordance with the terms contained in a written agreement between you * and Idiap. For further information contact tto@idiap.ch - * + * * Alternatively, this file may be used under the terms of the GNU Affero * Public License version 3 as published by the Free Software and appearing * in the file LICENSE.AGPL included in the packaging of this file. * The BEAT platform 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. - * + * * You should have received a copy of the GNU Affero Public License along * with the BEAT platform. If not, see http://www.gnu.org/licenses/. */ diff --git a/beat/web/experiments/static/experiments/js/utils.js b/beat/web/experiments/static/experiments/js/utils.js index eb79e93af..eb56f56fb 100644 --- a/beat/web/experiments/static/experiments/js/utils.js +++ b/beat/web/experiments/static/experiments/js/utils.js @@ -54,884 +54,884 @@ if (beat.experiments.utils === undefined) beat.experiments.utils = {}; // } //------------------------------------------------------------------------------ beat.experiments.utils.displayPlot = function( - prefix, - container, - value, - available_plotters, - available_plotter_parameter, - replace_container_content, - callback + prefix, + container, + value, + available_plotters, + available_plotter_parameter, + replace_container_content, + callback ) { - var table_element = null; - var state_merged = value.merged; - if (state_merged == undefined) state_merged = true; - - // Function to regenerate graph - function _regenerate_graph(plotter_selector) { - var data = JSON.parse(JSON.stringify(value)); - - //sample data plot or real plot required? - var default_post_prefix = '/plotters/plot/'; - var sampledata_post_prefix = '/plotters/plot_sample/'; - var sampledatawithparams_post_prefix = '/plotters/plot_sample_with_params/'; - var post_prefix = default_post_prefix; - if ('sample_data' in data) { - post_prefix = sampledata_post_prefix; - - if ('dynamic_params' in data) - post_prefix = sampledatawithparams_post_prefix; - } - - data.content_type = 'image/png'; - data.base64 = true; - - $.ajaxSetup({ - beforeSend: function(xhr, settings) { - var csrftoken = $.cookie('csrftoken'); - xhr.setRequestHeader('X-CSRFToken', csrftoken); - }, - }); - - $.ajax({ - type: 'POST', - url: prefix + post_prefix, - data: data, - - success: function(data) { - if (replace_container_content) { - while (container.firstChild) - container.removeChild(container.firstChild); - } - - if (table_element !== null) $(table_element).remove(); - - // adds table that will have the two elements - table_element = document.createElement('table'); - table_element.className = 'plotter'; - container.appendChild(table_element); - - var tr = document.createElement('tr'); - var td = document.createElement('td'); - - if(!value.report_number){ - table_element.appendChild(tr); - - var td = document.createElement('td'); - td.appendChild(plotter_selector); - tr.appendChild(td); - } - - // adds image place holder - var img = document.createElement('img'); - img.id = container.id + '_graph'; - //img.width = value['width']; - //img.height = value['height']; - img.className = 'img-responsive'; - img.src = 'data:' + value['content_type'] + ';base64,' + data; - - tr = document.createElement('tr'); - table_element.appendChild(tr); - - td = document.createElement('td'); - td.appendChild(img); - tr.appendChild(td); - - $(container).removeClass('progress'); - }, - - statusCode: { - 404: function(data, status, message) { - container.textContent = message + ' (' + data.status + ')'; - $(container).addClass('error'); - $(container).removeClass('progress'); - }, - 500: function(data, status, message) { - container.textContent = message + ' (' + data.status + ')'; - $(container).addClass('error'); - $(container).removeClass('progress'); - }, - }, - }); - } - - // Function to regenerate multiple graphs - function _regenerate_multiple_graph(plotter_selector) { - var data = JSON.parse(JSON.stringify(value)); - data.content_type = 'image/png'; - data.base64 = true; - - // create the containers - if (replace_container_content) { - while (container.firstChild) container.removeChild(container.firstChild); - } - - if (table_element !== null) $(table_element).remove(); - - // adds table that will have the two elements - table_element = document.createElement('table'); - table_element.className = 'plotter'; - container.appendChild(table_element); - - var tr = document.createElement('tr'); - var td = document.createElement('td'); - - if(!value.report_number){ - table_element.appendChild(tr); - - var td = document.createElement('td'); - td.appendChild(plotter_selector); - tr.appendChild(td); - } - - $.ajaxSetup({ - beforeSend: function(xhr, settings) { - var csrftoken = $.cookie('csrftoken'); - xhr.setRequestHeader('X-CSRFToken', csrftoken); - }, - }); - - function getPlot(_data) { - $.ajax({ - type: 'POST', - url: prefix + '/plotters/plot/', - data: _data, - - success: function(data) { - if (_data.experiment.length == 1) { - var tr_title = document.createElement('tr'); - table_element.appendChild(tr_title); - - var td_title = document.createElement('td'); - td_title.className = 'td_title'; - const title_h3 = document.createElement('h3'); - title_h3.textContent = _data.legend; - td_title.appendChild(title_h3); - tr_title.appendChild(td_title); - } - // adds image place holder - var img = document.createElement('img'); - img.id = container.id + '_graph'; - //img.width = value['width']; - //img.height = value['height']; - img.className = 'img-responsive chart'; - img.src = 'data:' + value['content_type'] + ';base64,' + data; - - tr = document.createElement('tr'); - table_element.appendChild(tr); - - td = document.createElement('td'); - td.appendChild(img); - tr.appendChild(td); - - $(container).removeClass('progress'); - }, - - statusCode: { - 404: function(data, status, message) { - container.textContent = message + ' (' + data.status + ')'; - $(container).addClass('error'); - $(container).removeClass('progress'); - }, - 500: function(data, status, message) { - container.textContent = message + ' (' + data.status + ')'; - $(container).addClass('error'); - $(container).removeClass('progress'); - }, - }, - }); - } - - if (!state_merged) { - for (var i = 0; i < data.experiment.length; ++i) { - // This step is required to copy elements from object in javascript! - // if we do a basic copy using "=" we have a reference to the object (pointer) - var data_per_experiment = JSON.parse(JSON.stringify(data)); - - data_per_experiment.experiment = [data.experiment[i]]; - if (data.legend != undefined) { - data_per_experiment.legend = data.legend.split('&')[i]; - } else { - data_per_experiment.legend = data.experiment[i]; - } - - getPlot(data_per_experiment); - } - } else { - getPlot(data); - } - } - - // Creates a selector box for chart plotters and parameters - function _create_selector( - what, - selected, - options, - other_what, - other_selected, - other_options - ) { - var span = document.createElement('span'); - - if (options.length > 1) { - var title = document.createElement('span'); - //title.innerHTML = 'Plotter: '; - title.innerHTML = 'Plotter: '; - span.appendChild(title); - - var selector = document.createElement('select'); - selector.id = container.id + '_settings_select'; - selector.className = 'settings_select'; - span.appendChild(selector); - - for (var i = 0; i < options.length; ++i) { - var opt = document.createElement('option'); - opt.value = options[i]; - opt.innerHTML = options[i]; - - if (options[i] === selected) opt.selected = true; - - selector.appendChild(opt); - } - - $(selector).change(function() { - value[what] = $(selector).val(); - - var plotter_selector = _create_selector( - what, - value[what], - available_plotters, - other_what, - value[other_what], - available_plotter_parameter - ); - _regenerate_graph(plotter_selector); - - if (callback) callback($(selector).val(), value[other_what]); - }); - } - - if (other_options.length > 1) { - var title = document.createElement('span'); - //title.innerHTML = 'Plotter: '; - title.innerHTML = ' Plotter parameter: '; - span.appendChild(title); - - var selector_plotterparameter = document.createElement('select'); - selector_plotterparameter.id = - container.id + '_settings_select_plotterparameter'; - selector_plotterparameter.className = 'settings_select_plotterparameter'; - span.appendChild(selector_plotterparameter); - - for (var i = 0; i < other_options.length; ++i) { - var opt = document.createElement('option'); - opt.value = other_options[i]; - opt.innerHTML = other_options[i]; - - if (other_options[i] === other_selected) { - opt.selected = true; - } - - selector_plotterparameter.appendChild(opt); - } - - $(selector_plotterparameter).change(function() { - value[other_what] = $(selector_plotterparameter).val(); - - var plotter_selector = _create_selector( - what, - value[what], - available_plotters, - other_what, - value[other_what], - available_plotter_parameter - ); - _regenerate_graph(plotter_selector); - - if (callback) callback(value[what], $(selector_plotterparameter).val()); - }); - } - - return span; - } - - // Creates a selector box for chart plotters and parameters - function _create_selector_with_merge_button( - what, - selected, - options, - other_what, - other_selected, - other_options, - multiple_experiments_flag, - expand_plots - ) { - var span = document.createElement('span'); - - if (options.length > 1) { - var title = document.createElement('span'); - //title.innerHTML = 'Plotter: '; - title.innerHTML = 'Plotter: '; - span.appendChild(title); - - var selector = document.createElement('select'); - selector.id = container.id + '_settings_select'; - selector.className = 'settings_select'; - span.appendChild(selector); - - for (var i = 0; i < options.length; ++i) { - var opt = document.createElement('option'); - opt.value = options[i]; - opt.innerHTML = options[i]; - - if (options[i] === selected) opt.selected = true; - - selector.appendChild(opt); - } - - $(selector).change(function() { - value[what] = $(selector).val(); - - var plotter_selector = null; - if (!multiple_experiments_flag) - plotter_selector = _create_selector_with_merge_button( - what, - value[what], - available_plotters, - other_what, - value[other_what], - available_plotter_parameter, - false - ); - else - plotter_selector = _create_selector_with_merge_button( - what, - value[what], - available_plotters, - other_what, - value[other_what], - available_plotter_parameter, - true - ); - _regenerate_multiple_graph(plotter_selector); - - if (callback) callback($(selector).val(), value[other_what]); - }); - } - - if (other_options.length > 1) { - var title = document.createElement('span'); - //title.innerHTML = 'Plotter: '; - title.innerHTML = ' Plotter parameter: '; - span.appendChild(title); - - var selector_plotterparameter = document.createElement('select'); - selector_plotterparameter.id = - container.id + '_settings_select_plotterparameter'; - selector_plotterparameter.className = 'settings_select_plotterparameter'; - span.appendChild(selector_plotterparameter); - - for (var i = 0; i < other_options.length; ++i) { - var opt = document.createElement('option'); - opt.value = other_options[i]; - opt.innerHTML = other_options[i]; - - if (other_options[i] === other_selected) { - opt.selected = true; - } - - selector_plotterparameter.appendChild(opt); - } - - $(selector_plotterparameter).change(function() { - value[other_what] = $(selector_plotterparameter).val(); - - var plotter_selector = null; - if (!multiple_experiments_flag) - plotter_selector = _create_selector_with_merge_button( - what, - value[what], - available_plotters, - other_what, - value[other_what], - available_plotter_parameter, - false - ); - else - plotter_selector = _create_selector_with_merge_button( - what, - value[what], - available_plotters, - other_what, - value[other_what], - available_plotter_parameter, - true, - !value['merged'] - ); - _regenerate_multiple_graph(plotter_selector); - - if (callback) - callback( - value[what], - $(selector_plotterparameter).val(), - value['merged'] - ); - }); - } - - var button_merge = document.createElement('a'); - button_merge.id = container.id + '_button_merge_a'; - - if (expand_plots) { - button_merge.className = 'btn btn-xs btn-primary merge_unmerge expand'; - button_merge.innerHTML = "<i class='fa fa-compress fa-lg'></i> Merge"; - } else { - button_merge.className = 'btn btn-xs btn-primary merge_unmerge merge'; - button_merge.innerHTML = "<i class='fa fa-expand fa-lg'></i> Expand"; - } - span.appendChild(button_merge); - - $(button_merge).click(function() { - if (!$(button_merge).hasClass('expand')) { - $(button_merge).addClass('expand'); - $(button_merge).removeClass('merge'); - $(button_merge).children().removeClass('fa-compress'); - $(button_merge).children().addClass('fa-expand'); - var plotter_selector = _create_selector_with_merge_button( - 'plotter', - value['plotter'], - available_plotters, - 'parameter', - value['parameter'], - available_plotter_parameter, - true, - true - ); - state_merged = false; - } else { - $(button_merge).addClass('merge'); - $(button_merge).removeClass('expand'); - $(button_merge).children().removeClass('fa-expand'); - $(button_merge).children().addClass('fa-compress'); - var plotter_selector = _create_selector_with_merge_button( - 'plotter', - value['plotter'], - available_plotters, - 'parameter', - value['parameter'], - available_plotter_parameter, - true, - false - ); - state_merged = true; - } - value.merged = state_merged; - _regenerate_multiple_graph(plotter_selector); - if (callback) - callback(value['plotter'], value['parameter'], value['merged']); - //callback(value["plotter"], $(plotter_selector).val()); - }); - - return span; - } - - if (Array.isArray(value.experiment) && value.experiment.length > 1) { - //creates button merge/unmerge and plotter/plotter_parameter selector - var plotter_selector = _create_selector_with_merge_button( - 'plotter', - value['plotter'], - available_plotters, - 'parameter', - value['parameter'], - available_plotter_parameter, - true, - !state_merged - ); - _regenerate_multiple_graph(plotter_selector); - } else { - // creates plotter/plotter_parameter selector - var plotter_selector = _create_selector( - 'plotter', - value['plotter'], - available_plotters, - 'parameter', - value['parameter'], - available_plotter_parameter - ); - _regenerate_graph(plotter_selector); - } + var table_element = null; + var state_merged = value.merged; + if (state_merged == undefined) state_merged = true; + + // Function to regenerate graph + function _regenerate_graph(plotter_selector) { + var data = JSON.parse(JSON.stringify(value)); + + //sample data plot or real plot required? + var default_post_prefix = '/plotters/plot/'; + var sampledata_post_prefix = '/plotters/plot_sample/'; + var sampledatawithparams_post_prefix = '/plotters/plot_sample_with_params/'; + var post_prefix = default_post_prefix; + if ('sample_data' in data) { + post_prefix = sampledata_post_prefix; + + if ('dynamic_params' in data) + post_prefix = sampledatawithparams_post_prefix; + } + + data.content_type = 'image/png'; + data.base64 = true; + + $.ajaxSetup({ + beforeSend: function(xhr, settings) { + var csrftoken = $.cookie('csrftoken'); + xhr.setRequestHeader('X-CSRFToken', csrftoken); + }, + }); + + $.ajax({ + type: 'POST', + url: prefix + post_prefix, + data: data, + + success: function(data) { + if (replace_container_content) { + while (container.firstChild) + container.removeChild(container.firstChild); + } + + if (table_element !== null) $(table_element).remove(); + + // adds table that will have the two elements + table_element = document.createElement('table'); + table_element.className = 'plotter'; + container.appendChild(table_element); + + var tr = document.createElement('tr'); + var td = document.createElement('td'); + + if(!value.report_number){ + table_element.appendChild(tr); + + var td = document.createElement('td'); + td.appendChild(plotter_selector); + tr.appendChild(td); + } + + // adds image place holder + var img = document.createElement('img'); + img.id = container.id + '_graph'; + //img.width = value['width']; + //img.height = value['height']; + img.className = 'img-responsive'; + img.src = 'data:' + value['content_type'] + ';base64,' + data; + + tr = document.createElement('tr'); + table_element.appendChild(tr); + + td = document.createElement('td'); + td.appendChild(img); + tr.appendChild(td); + + $(container).removeClass('progress'); + }, + + statusCode: { + 404: function(data, status, message) { + container.textContent = message + ' (' + data.status + ')'; + $(container).addClass('error'); + $(container).removeClass('progress'); + }, + 500: function(data, status, message) { + container.textContent = message + ' (' + data.status + ')'; + $(container).addClass('error'); + $(container).removeClass('progress'); + }, + }, + }); + } + + // Function to regenerate multiple graphs + function _regenerate_multiple_graph(plotter_selector) { + var data = JSON.parse(JSON.stringify(value)); + data.content_type = 'image/png'; + data.base64 = true; + + // create the containers + if (replace_container_content) { + while (container.firstChild) container.removeChild(container.firstChild); + } + + if (table_element !== null) $(table_element).remove(); + + // adds table that will have the two elements + table_element = document.createElement('table'); + table_element.className = 'plotter'; + container.appendChild(table_element); + + var tr = document.createElement('tr'); + var td = document.createElement('td'); + + if(!value.report_number){ + table_element.appendChild(tr); + + var td = document.createElement('td'); + td.appendChild(plotter_selector); + tr.appendChild(td); + } + + $.ajaxSetup({ + beforeSend: function(xhr, settings) { + var csrftoken = $.cookie('csrftoken'); + xhr.setRequestHeader('X-CSRFToken', csrftoken); + }, + }); + + function getPlot(_data) { + $.ajax({ + type: 'POST', + url: prefix + '/plotters/plot/', + data: _data, + + success: function(data) { + if (_data.experiment.length == 1) { + var tr_title = document.createElement('tr'); + table_element.appendChild(tr_title); + + var td_title = document.createElement('td'); + td_title.className = 'td_title'; + const title_h3 = document.createElement('h3'); + title_h3.textContent = _data.legend; + td_title.appendChild(title_h3); + tr_title.appendChild(td_title); + } + // adds image place holder + var img = document.createElement('img'); + img.id = container.id + '_graph'; + //img.width = value['width']; + //img.height = value['height']; + img.className = 'img-responsive chart'; + img.src = 'data:' + value['content_type'] + ';base64,' + data; + + tr = document.createElement('tr'); + table_element.appendChild(tr); + + td = document.createElement('td'); + td.appendChild(img); + tr.appendChild(td); + + $(container).removeClass('progress'); + }, + + statusCode: { + 404: function(data, status, message) { + container.textContent = message + ' (' + data.status + ')'; + $(container).addClass('error'); + $(container).removeClass('progress'); + }, + 500: function(data, status, message) { + container.textContent = message + ' (' + data.status + ')'; + $(container).addClass('error'); + $(container).removeClass('progress'); + }, + }, + }); + } + + if (!state_merged) { + for (var i = 0; i < data.experiment.length; ++i) { + // This step is required to copy elements from object in javascript! + // if we do a basic copy using "=" we have a reference to the object (pointer) + var data_per_experiment = JSON.parse(JSON.stringify(data)); + + data_per_experiment.experiment = [data.experiment[i]]; + if (data.legend != undefined) { + data_per_experiment.legend = data.legend.split('&')[i]; + } else { + data_per_experiment.legend = data.experiment[i]; + } + + getPlot(data_per_experiment); + } + } else { + getPlot(data); + } + } + + // Creates a selector box for chart plotters and parameters + function _create_selector( + what, + selected, + options, + other_what, + other_selected, + other_options + ) { + var span = document.createElement('span'); + + if (options.length > 1) { + var title = document.createElement('span'); + //title.innerHTML = 'Plotter: '; + title.innerHTML = 'Plotter: '; + span.appendChild(title); + + var selector = document.createElement('select'); + selector.id = container.id + '_settings_select'; + selector.className = 'settings_select'; + span.appendChild(selector); + + for (var i = 0; i < options.length; ++i) { + var opt = document.createElement('option'); + opt.value = options[i]; + opt.innerHTML = options[i]; + + if (options[i] === selected) opt.selected = true; + + selector.appendChild(opt); + } + + $(selector).change(function() { + value[what] = $(selector).val(); + + var plotter_selector = _create_selector( + what, + value[what], + available_plotters, + other_what, + value[other_what], + available_plotter_parameter + ); + _regenerate_graph(plotter_selector); + + if (callback) callback($(selector).val(), value[other_what]); + }); + } + + if (other_options.length > 1) { + var title = document.createElement('span'); + //title.innerHTML = 'Plotter: '; + title.innerHTML = ' Plotter parameter: '; + span.appendChild(title); + + var selector_plotterparameter = document.createElement('select'); + selector_plotterparameter.id = + container.id + '_settings_select_plotterparameter'; + selector_plotterparameter.className = 'settings_select_plotterparameter'; + span.appendChild(selector_plotterparameter); + + for (var i = 0; i < other_options.length; ++i) { + var opt = document.createElement('option'); + opt.value = other_options[i]; + opt.innerHTML = other_options[i]; + + if (other_options[i] === other_selected) { + opt.selected = true; + } + + selector_plotterparameter.appendChild(opt); + } + + $(selector_plotterparameter).change(function() { + value[other_what] = $(selector_plotterparameter).val(); + + var plotter_selector = _create_selector( + what, + value[what], + available_plotters, + other_what, + value[other_what], + available_plotter_parameter + ); + _regenerate_graph(plotter_selector); + + if (callback) callback(value[what], $(selector_plotterparameter).val()); + }); + } + + return span; + } + + // Creates a selector box for chart plotters and parameters + function _create_selector_with_merge_button( + what, + selected, + options, + other_what, + other_selected, + other_options, + multiple_experiments_flag, + expand_plots + ) { + var span = document.createElement('span'); + + if (options.length > 1) { + var title = document.createElement('span'); + //title.innerHTML = 'Plotter: '; + title.innerHTML = 'Plotter: '; + span.appendChild(title); + + var selector = document.createElement('select'); + selector.id = container.id + '_settings_select'; + selector.className = 'settings_select'; + span.appendChild(selector); + + for (var i = 0; i < options.length; ++i) { + var opt = document.createElement('option'); + opt.value = options[i]; + opt.innerHTML = options[i]; + + if (options[i] === selected) opt.selected = true; + + selector.appendChild(opt); + } + + $(selector).change(function() { + value[what] = $(selector).val(); + + var plotter_selector = null; + if (!multiple_experiments_flag) + plotter_selector = _create_selector_with_merge_button( + what, + value[what], + available_plotters, + other_what, + value[other_what], + available_plotter_parameter, + false + ); + else + plotter_selector = _create_selector_with_merge_button( + what, + value[what], + available_plotters, + other_what, + value[other_what], + available_plotter_parameter, + true + ); + _regenerate_multiple_graph(plotter_selector); + + if (callback) callback($(selector).val(), value[other_what]); + }); + } + + if (other_options.length > 1) { + var title = document.createElement('span'); + //title.innerHTML = 'Plotter: '; + title.innerHTML = ' Plotter parameter: '; + span.appendChild(title); + + var selector_plotterparameter = document.createElement('select'); + selector_plotterparameter.id = + container.id + '_settings_select_plotterparameter'; + selector_plotterparameter.className = 'settings_select_plotterparameter'; + span.appendChild(selector_plotterparameter); + + for (var i = 0; i < other_options.length; ++i) { + var opt = document.createElement('option'); + opt.value = other_options[i]; + opt.innerHTML = other_options[i]; + + if (other_options[i] === other_selected) { + opt.selected = true; + } + + selector_plotterparameter.appendChild(opt); + } + + $(selector_plotterparameter).change(function() { + value[other_what] = $(selector_plotterparameter).val(); + + var plotter_selector = null; + if (!multiple_experiments_flag) + plotter_selector = _create_selector_with_merge_button( + what, + value[what], + available_plotters, + other_what, + value[other_what], + available_plotter_parameter, + false + ); + else + plotter_selector = _create_selector_with_merge_button( + what, + value[what], + available_plotters, + other_what, + value[other_what], + available_plotter_parameter, + true, + !value['merged'] + ); + _regenerate_multiple_graph(plotter_selector); + + if (callback) + callback( + value[what], + $(selector_plotterparameter).val(), + value['merged'] + ); + }); + } + + var button_merge = document.createElement('a'); + button_merge.id = container.id + '_button_merge_a'; + + if (expand_plots) { + button_merge.className = 'btn btn-xs btn-primary merge_unmerge expand'; + button_merge.innerHTML = "<i class='fa fa-compress fa-lg'></i> Merge"; + } else { + button_merge.className = 'btn btn-xs btn-primary merge_unmerge merge'; + button_merge.innerHTML = "<i class='fa fa-expand fa-lg'></i> Expand"; + } + span.appendChild(button_merge); + + $(button_merge).click(function() { + if (!$(button_merge).hasClass('expand')) { + $(button_merge).addClass('expand'); + $(button_merge).removeClass('merge'); + $(button_merge).children().removeClass('fa-compress'); + $(button_merge).children().addClass('fa-expand'); + var plotter_selector = _create_selector_with_merge_button( + 'plotter', + value['plotter'], + available_plotters, + 'parameter', + value['parameter'], + available_plotter_parameter, + true, + true + ); + state_merged = false; + } else { + $(button_merge).addClass('merge'); + $(button_merge).removeClass('expand'); + $(button_merge).children().removeClass('fa-expand'); + $(button_merge).children().addClass('fa-compress'); + var plotter_selector = _create_selector_with_merge_button( + 'plotter', + value['plotter'], + available_plotters, + 'parameter', + value['parameter'], + available_plotter_parameter, + true, + false + ); + state_merged = true; + } + value.merged = state_merged; + _regenerate_multiple_graph(plotter_selector); + if (callback) + callback(value['plotter'], value['parameter'], value['merged']); + //callback(value["plotter"], $(plotter_selector).val()); + }); + + return span; + } + + if (Array.isArray(value.experiment) && value.experiment.length > 1) { + //creates button merge/unmerge and plotter/plotter_parameter selector + var plotter_selector = _create_selector_with_merge_button( + 'plotter', + value['plotter'], + available_plotters, + 'parameter', + value['parameter'], + available_plotter_parameter, + true, + !state_merged + ); + _regenerate_multiple_graph(plotter_selector); + } else { + // creates plotter/plotter_parameter selector + var plotter_selector = _create_selector( + 'plotter', + value['plotter'], + available_plotters, + 'parameter', + value['parameter'], + available_plotter_parameter + ); + _regenerate_graph(plotter_selector); + } }; //----------------------------------------------- beat.experiments.utils.getPlotData = function( - prefix, - value, - available_plotters, - available_plotter_parameter, - content_type, - callback + prefix, + value, + available_plotters, + available_plotter_parameter, + content_type, + callback ) { - var selected_content_type = ''; - if (content_type == 'PNG') { - selected_content_type = 'image/png'; - } else if (content_type == 'JPEG') { - selected_content_type = 'image/jpeg'; - } else if (content_type == 'PDF') { - selected_content_type = 'application/pdf'; - } - - var state_merged = value.merged; - if (state_merged == undefined) state_merged = true; - - var data = JSON.parse(JSON.stringify(value)); - data.content_type = selected_content_type; - data.base64 = true; - - // Function to regenerate graph - function _fetch_graph(_data) { - $.ajaxSetup({ - beforeSend: function(xhr, settings) { - var csrftoken = $.cookie('csrftoken'); - xhr.setRequestHeader('X-CSRFToken', csrftoken); - }, - }); - - var content_type = selected_content_type; - $.ajax({ - type: 'GET', - url: prefix + '/plotters/plot/', - data: _data, - - success: function(data) { - var returned_data = 'data:' + content_type + ';base64,' + data; - if (callback) callback(returned_data, content_type); - }, - - statusCode: { - 404: function(data, status, message) { - if (typeof container !== 'undefined') { - container.textContent = message + ' (' + data.status + ')'; - $(container).addClass('error'); - $(container).removeClass('progress'); - } else { - throw new Error(message); - } - }, - 500: function(data, status, message) { - if (typeof container !== 'undefined') { - container.textContent = message + ' (' + data.status + ')'; - $(container).addClass('error'); - $(container).removeClass('progress'); - } else { - throw new Error(data.responseText); - } - }, - }, - }); - } - - if (!state_merged) { - for (var i = 0; i < data.experiment.length; ++i) { - // This step is required to copy elements from object in javascript! - // if we do a basic copy using "=" we have a reference to the object (pointer) - var data_per_experiment = JSON.parse(JSON.stringify(data)); - - data_per_experiment.experiment = [data.experiment[i]]; - data_per_experiment.legend = data.legend.split('&')[i]; - - _fetch_graph(data_per_experiment); - } - } else { - _fetch_graph(data); - } + var selected_content_type = ''; + if (content_type == 'PNG') { + selected_content_type = 'image/png'; + } else if (content_type == 'JPEG') { + selected_content_type = 'image/jpeg'; + } else if (content_type == 'PDF') { + selected_content_type = 'application/pdf'; + } + + var state_merged = value.merged; + if (state_merged == undefined) state_merged = true; + + var data = JSON.parse(JSON.stringify(value)); + data.content_type = selected_content_type; + data.base64 = true; + + // Function to regenerate graph + function _fetch_graph(_data) { + $.ajaxSetup({ + beforeSend: function(xhr, settings) { + var csrftoken = $.cookie('csrftoken'); + xhr.setRequestHeader('X-CSRFToken', csrftoken); + }, + }); + + var content_type = selected_content_type; + $.ajax({ + type: 'GET', + url: prefix + '/plotters/plot/', + data: _data, + + success: function(data) { + var returned_data = 'data:' + content_type + ';base64,' + data; + if (callback) callback(returned_data, content_type); + }, + + statusCode: { + 404: function(data, status, message) { + if (typeof container !== 'undefined') { + container.textContent = message + ' (' + data.status + ')'; + $(container).addClass('error'); + $(container).removeClass('progress'); + } else { + throw new Error(message); + } + }, + 500: function(data, status, message) { + if (typeof container !== 'undefined') { + container.textContent = message + ' (' + data.status + ')'; + $(container).addClass('error'); + $(container).removeClass('progress'); + } else { + throw new Error(data.responseText); + } + }, + }, + }); + } + + if (!state_merged) { + for (var i = 0; i < data.experiment.length; ++i) { + // This step is required to copy elements from object in javascript! + // if we do a basic copy using "=" we have a reference to the object (pointer) + var data_per_experiment = JSON.parse(JSON.stringify(data)); + + data_per_experiment.experiment = [data.experiment[i]]; + data_per_experiment.legend = data.legend.split('&')[i]; + + _fetch_graph(data_per_experiment); + } + } else { + _fetch_graph(data); + } }; //----------------------------------------------- beat.experiments.utils.displayResult = function(element, value, type) { - if (value === null) return; - - if (type == 'float32') { - if (value == '+inf' || value == '-inf' || value == 'NaN') { - $(element).text('' + value); - } else { - var n = parseFloat(value); - if (n <= 1.0) - $(element).text('' + parseFloat(parseFloat(value).toPrecision(3))); - else $(element).text('' + parseFloat(parseFloat(value).toFixed(3))); - } - } else if (type == 'int32') { - $(element).text('' + value); - } else if (type == 'bool') { - $(element).text(value ? 'True' : 'False'); - } else if (type == 'string') { - $(element).html(value.replace(/\n/g, '<br />')); - } else { - $(element).text('ERROR (invalid type)'); - element.style.color = '#FF0000'; - } + if (value === null) return; + + if (type == 'float32') { + if (value == '+inf' || value == '-inf' || value == 'NaN') { + $(element).text('' + value); + } else { + var n = parseFloat(value); + if (n <= 1.0) + $(element).text('' + parseFloat(parseFloat(value).toPrecision(3))); + else $(element).text('' + parseFloat(parseFloat(value).toFixed(3))); + } + } else if (type == 'int32') { + $(element).text('' + value); + } else if (type == 'bool') { + $(element).text(value ? 'True' : 'False'); + } else if (type == 'string') { + $(element).html(value.replace(/\n/g, '<br />')); + } else { + $(element).text('ERROR (invalid type)'); + element.style.color = '#FF0000'; + } }; //----------------------------------------------- beat.experiments.utils.getBlockInputSignature = function(block) { - var block_inputs_list = []; + var block_inputs_list = []; - for (var i = 0; i < block.nbInputs(); ++i) { - var input = block.inputs[i]; + for (var i = 0; i < block.nbInputs(); ++i) { + var input = block.inputs[i]; - block_inputs_list.push({ - name: input.name, - dataformat: input.connections[0].output.dataformat, - channel: input.channel, - }); - } + block_inputs_list.push({ + name: input.name, + dataformat: input.connections[0].output.dataformat, + channel: input.channel, + }); + } - return beat.experiments.utils.getEntryPointsSignature(block_inputs_list); + return beat.experiments.utils.getEntryPointsSignature(block_inputs_list); }; //----------------------------------------------- beat.experiments.utils.getBlockOutputSignature = function(block) { - var block_outputs_list = []; + var block_outputs_list = []; - for (var i = 0; i < block.nbOutputs(); ++i) { - var output = block.outputs[i]; + for (var i = 0; i < block.nbOutputs(); ++i) { + var output = block.outputs[i]; - block_outputs_list.push({ - name: output.name, - dataformat: output.connections.length > 0 - ? output.connections[0].input.dataformat - : null, - channel: output.channel, - }); - } + block_outputs_list.push({ + name: output.name, + dataformat: output.connections.length > 0 + ? output.connections[0].input.dataformat + : null, + channel: output.channel, + }); + } - return beat.experiments.utils.getEntryPointsSignature(block_outputs_list); + return beat.experiments.utils.getEntryPointsSignature(block_outputs_list); }; //----------------------------------------------- beat.experiments.utils.getEntryPointsSignature = function(entry_points) { - var signature = {}; + var signature = {}; - for (var i = 0; i < entry_points.length; ++i) { - var entry = entry_points[i]; + for (var i = 0; i < entry_points.length; ++i) { + var entry = entry_points[i]; - if (signature[entry.channel] === undefined) signature[entry.channel] = {}; + if (signature[entry.channel] === undefined) signature[entry.channel] = {}; - if (signature[entry.channel][entry.dataformat] !== undefined) - signature[entry.channel][entry.dataformat] += 1; - else signature[entry.channel][entry.dataformat] = 1; - } + if (signature[entry.channel][entry.dataformat] !== undefined) + signature[entry.channel][entry.dataformat] += 1; + else signature[entry.channel][entry.dataformat] = 1; + } - return signature; + return signature; }; //----------------------------------------------- beat.experiments.utils.analyzeCompatibility = function( - block_inputs_signature, - block_outputs_signature, - algorithm_inputs_signature, - algorithm_outputs_signature, - dataformats_list + block_inputs_signature, + block_outputs_signature, + algorithm_inputs_signature, + algorithm_outputs_signature, + dataformats_list ) { - function _getInfos(names, input_signatures, output_signatures) { - var result = {}; - for (var i = 0; i < names.length; ++i) { - var name = names[i]; - - var entry = { - nb_inputs: 0, - nb_outputs: 0, - }; - - if (input_signatures[name] !== undefined) { - var dataformats = Object.keys(input_signatures[name]); - for (var j = 0; j < dataformats.length; ++j) - entry.nb_inputs += input_signatures[name][dataformats[j]]; - } - - if (output_signatures[name] !== undefined) { - var dataformats = Object.keys(output_signatures[name]); - for (var j = 0; j < dataformats.length; ++j) - entry.nb_outputs += output_signatures[name][dataformats[j]]; - } - - result[name] = entry; - } - - return result; - } - - var input_channels = Object.keys(block_inputs_signature); - var output_channels = Object.keys(block_outputs_signature); - - var input_groups = Object.keys(algorithm_inputs_signature); - var output_groups = Object.keys(algorithm_outputs_signature); - - // Check that the number of channels/groups match - if ( - input_channels.length != input_groups.length || - output_channels.length != output_groups.length - ) - return null; - - // Find the compatible channel/group associations - var channel_names = input_channels.concat(output_channels).unique(); - var group_names = input_groups.concat(output_groups).unique(); - - var channels = _getInfos( - channel_names, - block_inputs_signature, - block_outputs_signature - ); - var groups = _getInfos( - group_names, - algorithm_inputs_signature, - algorithm_outputs_signature - ); - - var mapping = {}; - - for (var i = 0; i < channel_names.length; ++i) { - var channel = channel_names[i]; - - var matches = []; - for (var j = 0; j < group_names.length; ++j) { - var group = group_names[j]; - - if ( - channels[channel].nb_inputs != groups[group].nb_inputs || - channels[channel].nb_outputs != groups[group].nb_outputs - ) - continue; - - if (channels[channel].nb_inputs > 0) { - if ( - !beat.experiments.utils.compareSignatures( - algorithm_inputs_signature[group], - block_inputs_signature[channel], - dataformats_list - ) - ) { - continue; - } - } - - if (channels[channel].nb_outputs > 0) { - if ( - !beat.experiments.utils.compareSignatures( - block_outputs_signature[channel], - algorithm_outputs_signature[group], - dataformats_list - ) - ) { - continue; - } - } - - matches.push(group); - } - - if (matches.length == 0) return null; - - mapping[channel] = matches; - } - - var smart_mapping = new beat.experiments.utils.SmartMapping( - mapping, - block_inputs_signature, - block_outputs_signature, - algorithm_inputs_signature, - algorithm_outputs_signature - ); - - if (smart_mapping.isValid()) return smart_mapping; - - return null; + function _getInfos(names, input_signatures, output_signatures) { + var result = {}; + for (var i = 0; i < names.length; ++i) { + var name = names[i]; + + var entry = { + nb_inputs: 0, + nb_outputs: 0, + }; + + if (input_signatures[name] !== undefined) { + var dataformats = Object.keys(input_signatures[name]); + for (var j = 0; j < dataformats.length; ++j) + entry.nb_inputs += input_signatures[name][dataformats[j]]; + } + + if (output_signatures[name] !== undefined) { + var dataformats = Object.keys(output_signatures[name]); + for (var j = 0; j < dataformats.length; ++j) + entry.nb_outputs += output_signatures[name][dataformats[j]]; + } + + result[name] = entry; + } + + return result; + } + + var input_channels = Object.keys(block_inputs_signature); + var output_channels = Object.keys(block_outputs_signature); + + var input_groups = Object.keys(algorithm_inputs_signature); + var output_groups = Object.keys(algorithm_outputs_signature); + + // Check that the number of channels/groups match + if ( + input_channels.length != input_groups.length || + output_channels.length != output_groups.length + ) + return null; + + // Find the compatible channel/group associations + var channel_names = input_channels.concat(output_channels).unique(); + var group_names = input_groups.concat(output_groups).unique(); + + var channels = _getInfos( + channel_names, + block_inputs_signature, + block_outputs_signature + ); + var groups = _getInfos( + group_names, + algorithm_inputs_signature, + algorithm_outputs_signature + ); + + var mapping = {}; + + for (var i = 0; i < channel_names.length; ++i) { + var channel = channel_names[i]; + + var matches = []; + for (var j = 0; j < group_names.length; ++j) { + var group = group_names[j]; + + if ( + channels[channel].nb_inputs != groups[group].nb_inputs || + channels[channel].nb_outputs != groups[group].nb_outputs + ) + continue; + + if (channels[channel].nb_inputs > 0) { + if ( + !beat.experiments.utils.compareSignatures( + algorithm_inputs_signature[group], + block_inputs_signature[channel], + dataformats_list + ) + ) { + continue; + } + } + + if (channels[channel].nb_outputs > 0) { + if ( + !beat.experiments.utils.compareSignatures( + block_outputs_signature[channel], + algorithm_outputs_signature[group], + dataformats_list + ) + ) { + continue; + } + } + + matches.push(group); + } + + if (matches.length == 0) return null; + + mapping[channel] = matches; + } + + var smart_mapping = new beat.experiments.utils.SmartMapping( + mapping, + block_inputs_signature, + block_outputs_signature, + algorithm_inputs_signature, + algorithm_outputs_signature + ); + + if (smart_mapping.isValid()) return smart_mapping; + + return null; }; //----------------------------------------------- beat.experiments.utils.compareSignatures = function( - fixed, - extendable, - dataformats_list + fixed, + extendable, + dataformats_list ) { - // Clone the signatures to have a modifiable version - fixed = JSON.parse(JSON.stringify(fixed)); - extendable = JSON.parse(JSON.stringify(extendable)); + // Clone the signatures to have a modifiable version + fixed = JSON.parse(JSON.stringify(fixed)); + extendable = JSON.parse(JSON.stringify(extendable)); - // Compare the two signatures - while ( - Object.keys(fixed).length != 0 && - Object.keys(extendable).length != 0 - ) { - var fixed_names = Object.keys(fixed); - var extendable_names = Object.keys(extendable); + // Compare the two signatures + while ( + Object.keys(fixed).length != 0 && + Object.keys(extendable).length != 0 + ) { + var fixed_names = Object.keys(fixed); + var extendable_names = Object.keys(extendable); - if (fixed_names.length == 1 && fixed_names[0] == 'null') break; + if (fixed_names.length == 1 && fixed_names[0] == 'null') break; - if (extendable_names.length == 1 && extendable_names[0] == 'null') break; + if (extendable_names.length == 1 && extendable_names[0] == 'null') break; - for (var k = 0; k < extendable_names.length; ++k) { - var dataformat_name = extendable_names[k]; + for (var k = 0; k < extendable_names.length; ++k) { + var dataformat_name = extendable_names[k]; - if (dataformat_name == 'null') continue; + if (dataformat_name == 'null') continue; - if (fixed_names.indexOf(dataformat_name) >= 0) { - var nb = Math.min(fixed[dataformat_name], extendable[dataformat_name]); + if (fixed_names.indexOf(dataformat_name) >= 0) { + var nb = Math.min(fixed[dataformat_name], extendable[dataformat_name]); - fixed[dataformat_name] -= nb; - extendable[dataformat_name] -= nb; + fixed[dataformat_name] -= nb; + extendable[dataformat_name] -= nb; - if (fixed[dataformat_name] == 0) fixed[dataformat_name] = undefined; + if (fixed[dataformat_name] == 0) fixed[dataformat_name] = undefined; - if (extendable[dataformat_name] == 0) - extendable[dataformat_name] = undefined; - } + if (extendable[dataformat_name] == 0) + extendable[dataformat_name] = undefined; + } - if (extendable[dataformat_name] !== undefined) { - var details = dataformats_list.get(dataformat_name); - if (details !== null && details.extend !== null) { - if (extendable[details.extend] !== undefined) - extendable[details.extend] += extendable[dataformat_name]; - else extendable[details.extend] = extendable[dataformat_name]; + if (extendable[dataformat_name] !== undefined) { + var details = dataformats_list.get(dataformat_name); + if (details !== null && details.extend !== null) { + if (extendable[details.extend] !== undefined) + extendable[details.extend] += extendable[dataformat_name]; + else extendable[details.extend] = extendable[dataformat_name]; - extendable[dataformat_name] = undefined; - } else { - return false; - } - } + extendable[dataformat_name] = undefined; + } else { + return false; + } + } - fixed = JSON.parse(JSON.stringify(fixed)); - extendable = JSON.parse(JSON.stringify(extendable)); - } - } + fixed = JSON.parse(JSON.stringify(fixed)); + extendable = JSON.parse(JSON.stringify(extendable)); + } + } - var fixed_names = Object.keys(fixed); - var extendable_names = Object.keys(extendable); + var fixed_names = Object.keys(fixed); + var extendable_names = Object.keys(extendable); - var fixed_nulls = 0; - var fixed_not_nulls = 0; + var fixed_nulls = 0; + var fixed_not_nulls = 0; - for (var i = 0; i < fixed_names.length; ++i) { - if (fixed_names[i] == 'null') fixed_nulls += fixed[fixed_names[i]]; - else fixed_not_nulls += fixed[fixed_names[i]]; - } + for (var i = 0; i < fixed_names.length; ++i) { + if (fixed_names[i] == 'null') fixed_nulls += fixed[fixed_names[i]]; + else fixed_not_nulls += fixed[fixed_names[i]]; + } - var extended_nulls = 0; - var extended_not_nulls = 0; + var extended_nulls = 0; + var extended_not_nulls = 0; - for (var i = 0; i < extendable_names.length; ++i) { - if (extendable_names[i] == 'null') - extended_nulls += extendable[extendable_names[i]]; - else extended_not_nulls += extendable[extendable_names[i]]; - } + for (var i = 0; i < extendable_names.length; ++i) { + if (extendable_names[i] == 'null') + extended_nulls += extendable[extendable_names[i]]; + else extended_not_nulls += extendable[extendable_names[i]]; + } - return fixed_nulls == extended_not_nulls && fixed_not_nulls == extended_nulls; + return fixed_nulls == extended_not_nulls && fixed_not_nulls == extended_nulls; }; /******************************** CLASS: SmartDatasetList *******************************/ @@ -940,41 +940,41 @@ beat.experiments.utils.compareSignatures = function( // Constructor //---------------------------------------------------------------------------------------- beat.experiments.utils.SmartDatasetList = function() { - // Attributes - this.databases = {}; + // Attributes + this.databases = {}; }; //---------------------------------------------------------------------------------------- // Add an entry into the list //---------------------------------------------------------------------------------------- beat.experiments.utils.SmartDatasetList.prototype.add = function( - database, - protocol, - block, - dataset, - compatibility_infos + database, + protocol, + block, + dataset, + compatibility_infos ) { - if (this.databases[database] === undefined) { - this.databases[database] = { - usable: null, - protocols: {}, - }; - } - - if (this.databases[database].protocols[protocol] === undefined) { - this.databases[database].protocols[protocol] = { - usable: null, - blocks: {}, - }; - } - - if (this.databases[database].protocols[protocol].blocks[block] === undefined) - this.databases[database].protocols[protocol].blocks[block] = []; - - compatibility_infos.set = dataset; - this.databases[database].protocols[protocol].blocks[block].push( - compatibility_infos - ); + if (this.databases[database] === undefined) { + this.databases[database] = { + usable: null, + protocols: {}, + }; + } + + if (this.databases[database].protocols[protocol] === undefined) { + this.databases[database].protocols[protocol] = { + usable: null, + blocks: {}, + }; + } + + if (this.databases[database].protocols[protocol].blocks[block] === undefined) + this.databases[database].protocols[protocol].blocks[block] = []; + + compatibility_infos.set = dataset; + this.databases[database].protocols[protocol].blocks[block].push( + compatibility_infos + ); }; //---------------------------------------------------------------------------------------- @@ -982,426 +982,426 @@ beat.experiments.utils.SmartDatasetList.prototype.add = function( // database/protocol //---------------------------------------------------------------------------------------- beat.experiments.utils.SmartDatasetList.prototype.finish = function( - nb_dataset_blocks + nb_dataset_blocks ) { - var database_names = Object.keys(this.databases); - for (var i = 0; i < database_names.length; ++i) { - var database = this.databases[database_names[i]]; - - var protocol_names = Object.keys(database.protocols); - for (var j = 0; j < protocol_names.length; ++j) { - var protocol = database.protocols[protocol_names[j]]; - - // Remove protocols not covering all the dataset blocks - var block_names = Object.keys(protocol.blocks); - if (block_names.length != nb_dataset_blocks) { - delete database.protocols[protocol_names[j]]; - continue; - } - - // Sort the compatible datasets for each block - for (var k = 0; k < block_names.length; ++k) { - protocol.blocks[block_names[k]].sort(function(a, b) { - if (a.perfect) { - if (!b.perfect) return -1.0; - else return a.distance - b.distance; - } else { - if (b.perfect) return +1.0; - else return a.distance - b.distance; - } - }); - } - - // First process all the dataset blocks for which only one dataset is - // compatible - block_names = this._processSingleMatches(block_names, protocol); - if (block_names === null) { - delete database.protocols[protocol_names[j]]; - continue; - } - - // Next process all the dataset blocks for which one perfect dataset was - // found - if (block_names.length > 0) { - block_names = this._processPerfectMatches(block_names, protocol); - if (block_names === null) { - delete database.protocols[protocol_names[j]]; - continue; - } - } - - // Next process the best remaining match for each dataset block - if (block_names.length > 0) { - block_names = this._processBestMatches(block_names, protocol); - if (block_names === null) { - delete database.protocols[protocol_names[j]]; - continue; - } - } - } - - if (Object.keys(database.protocols) == 0) - delete this.databases[database_names[i]]; - } - - return Object.keys(this.databases).length > 0; + var database_names = Object.keys(this.databases); + for (var i = 0; i < database_names.length; ++i) { + var database = this.databases[database_names[i]]; + + var protocol_names = Object.keys(database.protocols); + for (var j = 0; j < protocol_names.length; ++j) { + var protocol = database.protocols[protocol_names[j]]; + + // Remove protocols not covering all the dataset blocks + var block_names = Object.keys(protocol.blocks); + if (block_names.length != nb_dataset_blocks) { + delete database.protocols[protocol_names[j]]; + continue; + } + + // Sort the compatible datasets for each block + for (var k = 0; k < block_names.length; ++k) { + protocol.blocks[block_names[k]].sort(function(a, b) { + if (a.perfect) { + if (!b.perfect) return -1.0; + else return a.distance - b.distance; + } else { + if (b.perfect) return +1.0; + else return a.distance - b.distance; + } + }); + } + + // First process all the dataset blocks for which only one dataset is + // compatible + block_names = this._processSingleMatches(block_names, protocol); + if (block_names === null) { + delete database.protocols[protocol_names[j]]; + continue; + } + + // Next process all the dataset blocks for which one perfect dataset was + // found + if (block_names.length > 0) { + block_names = this._processPerfectMatches(block_names, protocol); + if (block_names === null) { + delete database.protocols[protocol_names[j]]; + continue; + } + } + + // Next process the best remaining match for each dataset block + if (block_names.length > 0) { + block_names = this._processBestMatches(block_names, protocol); + if (block_names === null) { + delete database.protocols[protocol_names[j]]; + continue; + } + } + } + + if (Object.keys(database.protocols) == 0) + delete this.databases[database_names[i]]; + } + + return Object.keys(this.databases).length > 0; }; //---------------------------------------------------------------------------------------- // Returns the name of all the databases in the list //---------------------------------------------------------------------------------------- beat.experiments.utils.SmartDatasetList.prototype.databaseNames = function() { - return Object.keys(this.databases); + return Object.keys(this.databases); }; //---------------------------------------------------------------------------------------- // Returns the name of all the protocols of a specific database //---------------------------------------------------------------------------------------- beat.experiments.utils.SmartDatasetList.prototype.protocolNames = function( - database + database ) { - if (this.databases[database] === undefined) return []; + if (this.databases[database] === undefined) return []; - return Object.keys(this.databases[database].protocols); + return Object.keys(this.databases[database].protocols); }; //---------------------------------------------------------------------------------------- // Returns the name of all the protocols of a specific database //---------------------------------------------------------------------------------------- beat.experiments.utils.SmartDatasetList.prototype.protocol = function( - database, - name + database, + name ) { - if (this.databases[database] === undefined) return null; + if (this.databases[database] === undefined) return null; - if (this.databases[database].protocols[name] === undefined) return null; + if (this.databases[database].protocols[name] === undefined) return null; - return this.databases[database].protocols[name]; + return this.databases[database].protocols[name]; }; //---------------------------------------------------------------------------------------- // Update the usability status of one block/dataset combination //---------------------------------------------------------------------------------------- beat.experiments.utils.SmartDatasetList.prototype.update = function( - database_name, - protocol_name, - block_name, - dataset, - usable + database_name, + protocol_name, + block_name, + dataset, + usable ) { - var database = this.databases[database_name]; - if (database === undefined) return; + var database = this.databases[database_name]; + if (database === undefined) return; - var protocol = database.protocols[protocol_name]; - if (protocol === undefined) return; + var protocol = database.protocols[protocol_name]; + if (protocol === undefined) return; - var block = protocol.blocks[block_name]; - if (block === undefined) return; + var block = protocol.blocks[block_name]; + if (block === undefined) return; - if (block.set != dataset) return; + if (block.set != dataset) return; - if (usable != block.usable) { - block.usable = usable; + if (usable != block.usable) { + block.usable = usable; - if (usable) { - protocol.usable = null; - database.usable = null; - } else { - protocol.usable = false; - database.usable = null; - } - } + if (usable) { + protocol.usable = null; + database.usable = null; + } else { + protocol.usable = false; + database.usable = null; + } + } }; //---------------------------------------------------------------------------------------- // Indicates if a protocol is usable (given the current configuration) //---------------------------------------------------------------------------------------- beat.experiments.utils.SmartDatasetList.prototype.isProtocolUsable = function( - database_name, - protocol_name + database_name, + protocol_name ) { - var database = this.databases[database_name]; - if (database === undefined) return false; + var database = this.databases[database_name]; + if (database === undefined) return false; - var protocol = database.protocols[protocol_name]; - if (protocol === undefined) return false; + var protocol = database.protocols[protocol_name]; + if (protocol === undefined) return false; - if (protocol.usable === null) { - protocol.usable = true; + if (protocol.usable === null) { + protocol.usable = true; - var block_names = Object.keys(protocol.blocks); - for (var i = 0; i < block_names.length; ++i) { - if (!protocol.blocks[block_names[i]].usable) { - protocol.usable = false; - break; - } - } - } + var block_names = Object.keys(protocol.blocks); + for (var i = 0; i < block_names.length; ++i) { + if (!protocol.blocks[block_names[i]].usable) { + protocol.usable = false; + break; + } + } + } - return protocol.usable; + return protocol.usable; }; //---------------------------------------------------------------------------------------- // Indicates if a database is usable (given the current configuration) //---------------------------------------------------------------------------------------- beat.experiments.utils.SmartDatasetList.prototype.isDatabaseUsable = function( - database_name + database_name ) { - var database = this.databases[database_name]; - if (database === undefined) return false; - - if (database.usable === null) { - database.usable = false; - - var protocol_names = Object.keys(database.protocols); - for (var i = 0; i < protocol_names.length; ++i) { - if (this.isProtocolUsable(database_name, protocol_names[i])) { - database.usable = true; - break; - } - } - } - - return database.usable; + var database = this.databases[database_name]; + if (database === undefined) return false; + + if (database.usable === null) { + database.usable = false; + + var protocol_names = Object.keys(database.protocols); + for (var i = 0; i < protocol_names.length; ++i) { + if (this.isProtocolUsable(database_name, protocol_names[i])) { + database.usable = true; + break; + } + } + } + + return database.usable; }; //--------------------------------------------------------- beat.experiments.utils.SmartDatasetList.prototype._removeDataset = function( - block_names, - protocol, - set_to_remove + block_names, + protocol, + set_to_remove ) { - for (var i = 0; i < block_names.length; ++i) { - var block_name = block_names[i]; + for (var i = 0; i < block_names.length; ++i) { + var block_name = block_names[i]; - for (var j = 0; j < protocol.blocks[block_name].length; ++j) { - if (protocol.blocks[block_name][j].set == set_to_remove) { - protocol.blocks[block_name].splice(j, 1); + for (var j = 0; j < protocol.blocks[block_name].length; ++j) { + if (protocol.blocks[block_name][j].set == set_to_remove) { + protocol.blocks[block_name].splice(j, 1); - if (protocol.blocks[block_name].length == 0) return false; + if (protocol.blocks[block_name].length == 0) return false; - break; - } - } - } + break; + } + } + } - return true; + return true; }; //---------------------------------------------------------------------------------------- // Process all the dataset blocks for which only one dataset is compatible //---------------------------------------------------------------------------------------- beat.experiments.utils.SmartDatasetList.prototype._processSingleMatches = function( - block_names, - protocol + block_names, + protocol ) { - var changed = true; - while (changed && block_names.length > 0) { - changed = false; - for (var i = 0; i < block_names.length; ++i) { - var block_name = block_names[i]; - - if (protocol.blocks[block_name].length == 1) { - protocol.blocks[block_name] = protocol.blocks[block_name][0]; - block_names.splice(i, 1); - - if ( - !this._removeDataset( - block_names, - protocol, - protocol.blocks[block_name].set - ) - ) - return null; - - changed = true; - break; - } - } - } - - return block_names; + var changed = true; + while (changed && block_names.length > 0) { + changed = false; + for (var i = 0; i < block_names.length; ++i) { + var block_name = block_names[i]; + + if (protocol.blocks[block_name].length == 1) { + protocol.blocks[block_name] = protocol.blocks[block_name][0]; + block_names.splice(i, 1); + + if ( + !this._removeDataset( + block_names, + protocol, + protocol.blocks[block_name].set + ) + ) + return null; + + changed = true; + break; + } + } + } + + return block_names; }; //---------------------------------------------------------------------------------------- // Process all the dataset blocks for which one perfect dataset was found //---------------------------------------------------------------------------------------- beat.experiments.utils.SmartDatasetList.prototype._processPerfectMatches = function( - block_names, - protocol + block_names, + protocol ) { - var changed = true; - while (changed && block_names.length > 0) { - changed = false; - for (var i = 0; i < block_names.length; ++i) { - var block_name = block_names[i]; - - for (var j = 0; j < protocol.blocks[block_name].length; ++j) { - if (protocol.blocks[block_name][j].perfect) { - protocol.blocks[block_name] = protocol.blocks[block_name][j]; - block_names.splice(i, 1); - - if ( - !this._removeDataset( - block_names, - protocol, - protocol.blocks[block_name].set - ) - ) - return null; - - changed = true; - break; - } - } - } - } - - if (block_names.length > 0) - block_names = this._processSingleMatches(block_names, protocol); - - return block_names; + var changed = true; + while (changed && block_names.length > 0) { + changed = false; + for (var i = 0; i < block_names.length; ++i) { + var block_name = block_names[i]; + + for (var j = 0; j < protocol.blocks[block_name].length; ++j) { + if (protocol.blocks[block_name][j].perfect) { + protocol.blocks[block_name] = protocol.blocks[block_name][j]; + block_names.splice(i, 1); + + if ( + !this._removeDataset( + block_names, + protocol, + protocol.blocks[block_name].set + ) + ) + return null; + + changed = true; + break; + } + } + } + } + + if (block_names.length > 0) + block_names = this._processSingleMatches(block_names, protocol); + + return block_names; }; //---------------------------------------------------------------------------------------- // Process the best match for each dataset block, when no ambiguity is possible //---------------------------------------------------------------------------------------- beat.experiments.utils.SmartDatasetList.prototype._processSingleBestMatches = function( - block_names, - protocol + block_names, + protocol ) { - var changed = true; - while (changed && block_names.length > 0) { - changed = false; - for (var i = 0; i < block_names.length; ++i) { - var block_name = block_names[i]; - - var best_match = 0; - var nb_matches = 1; - - for (var j = 1; j < protocol.blocks[block_name].length; ++j) { - if ( - protocol.blocks[block_name][j].distance == - protocol.blocks[block_name][best_match].distance - ) { - nb_matches += 1; - } else if ( - protocol.blocks[block_name][j].distance < - protocol.blocks[block_name][best_match].distance - ) { - best_match = j; - nb_matches = 1; - } - } - - if (nb_matches == 1) { - protocol.blocks[block_name] = protocol.blocks[block_name][best_match]; - block_names.splice(i, 1); - - if ( - !this._removeDataset( - block_names, - protocol, - protocol.blocks[block_name].set - ) - ) - return null; - - changed = true; - break; - } - } - } - - if (block_names.length > 0) - block_names = this._processSingleMatches(block_names, protocol); - - return block_names; + var changed = true; + while (changed && block_names.length > 0) { + changed = false; + for (var i = 0; i < block_names.length; ++i) { + var block_name = block_names[i]; + + var best_match = 0; + var nb_matches = 1; + + for (var j = 1; j < protocol.blocks[block_name].length; ++j) { + if ( + protocol.blocks[block_name][j].distance == + protocol.blocks[block_name][best_match].distance + ) { + nb_matches += 1; + } else if ( + protocol.blocks[block_name][j].distance < + protocol.blocks[block_name][best_match].distance + ) { + best_match = j; + nb_matches = 1; + } + } + + if (nb_matches == 1) { + protocol.blocks[block_name] = protocol.blocks[block_name][best_match]; + block_names.splice(i, 1); + + if ( + !this._removeDataset( + block_names, + protocol, + protocol.blocks[block_name].set + ) + ) + return null; + + changed = true; + break; + } + } + } + + if (block_names.length > 0) + block_names = this._processSingleMatches(block_names, protocol); + + return block_names; }; //---------------------------------------------------------------------------------------- // Process the best matches for each dataset block //---------------------------------------------------------------------------------------- beat.experiments.utils.SmartDatasetList.prototype._processBestMatches = function( - block_names, - protocol + block_names, + protocol ) { - block_names = this._processSingleBestMatches(block_names, protocol); - - while (block_names !== null && block_names.length > 0) { - var best_block = null; - var best_distance = null; - var matches = null; - - for (var i = 0; i < block_names.length; ++i) { - var block_name = block_names[i]; - - var block_best_distance = protocol.blocks[block_name][0].distance; - var block_matches = [ - { - set: protocol.blocks[block_name][0].set, - index: 0, - }, - ]; - - for (var j = 1; j < protocol.blocks[block_name].length; ++j) { - if (protocol.blocks[block_name][j].distance == block_best_distance) { - block_matches.push({ - set: protocol.blocks[block_name][j].set, - index: j, - }); - } else if ( - protocol.blocks[block_name][j].distance < block_best_distance - ) { - block_best_distance = protocol.blocks[block_name][j].distance; - block_matches.push({ - set: protocol.blocks[block_name][j].set, - index: j, - }); - } - } - - if ( - best_block === null || - block_best_distance < best_distance || - (block_best_distance == best_distance && - block_matches.length < matches.length) - ) { - best_block = block_name; - best_distance = block_best_distance; - matches = block_matches; - } - } - - // Select the dataset by fuzzy matching - var choice = null; - var score = -1.0; - for (var i = 0; i < matches.length; ++i) { - var match_score = best_block.score(matches[i].set, 0.5); - if (match_score > score) { - choice = matches[i]; - score = match_score; - } - } - - protocol.blocks[best_block] = protocol.blocks[best_block][choice.index]; - block_names.splice(block_names.indexOf(best_block), 1); - - if ( - !this._removeDataset( - block_names, - protocol, - protocol.blocks[best_block].set - ) - ) - return null; - - if (block_names.length > 0) - block_names = this._processSingleMatches(block_names, protocol); - } - - return block_names; + block_names = this._processSingleBestMatches(block_names, protocol); + + while (block_names !== null && block_names.length > 0) { + var best_block = null; + var best_distance = null; + var matches = null; + + for (var i = 0; i < block_names.length; ++i) { + var block_name = block_names[i]; + + var block_best_distance = protocol.blocks[block_name][0].distance; + var block_matches = [ + { + set: protocol.blocks[block_name][0].set, + index: 0, + }, + ]; + + for (var j = 1; j < protocol.blocks[block_name].length; ++j) { + if (protocol.blocks[block_name][j].distance == block_best_distance) { + block_matches.push({ + set: protocol.blocks[block_name][j].set, + index: j, + }); + } else if ( + protocol.blocks[block_name][j].distance < block_best_distance + ) { + block_best_distance = protocol.blocks[block_name][j].distance; + block_matches.push({ + set: protocol.blocks[block_name][j].set, + index: j, + }); + } + } + + if ( + best_block === null || + block_best_distance < best_distance || + (block_best_distance == best_distance && + block_matches.length < matches.length) + ) { + best_block = block_name; + best_distance = block_best_distance; + matches = block_matches; + } + } + + // Select the dataset by fuzzy matching + var choice = null; + var score = -1.0; + for (var i = 0; i < matches.length; ++i) { + var match_score = best_block.score(matches[i].set, 0.5); + if (match_score > score) { + choice = matches[i]; + score = match_score; + } + } + + protocol.blocks[best_block] = protocol.blocks[best_block][choice.index]; + block_names.splice(block_names.indexOf(best_block), 1); + + if ( + !this._removeDataset( + block_names, + protocol, + protocol.blocks[best_block].set + ) + ) + return null; + + if (block_names.length > 0) + block_names = this._processSingleMatches(block_names, protocol); + } + + return block_names; }; /********************************** CLASS: SmartMapping *********************************/ @@ -1410,227 +1410,227 @@ beat.experiments.utils.SmartDatasetList.prototype._processBestMatches = function // Constructor //---------------------------------------------------------------------------------------- beat.experiments.utils.SmartMapping = function( - possibilities, - block_inputs_signature, - block_outputs_signature, - algorithm_inputs_signature, - algorithm_outputs_signature + possibilities, + block_inputs_signature, + block_outputs_signature, + algorithm_inputs_signature, + algorithm_outputs_signature ) { - // Attributes - this.possibilities = JSON.parse(JSON.stringify(possibilities)); - this.block_inputs_signature = block_inputs_signature; - this.block_outputs_signature = block_outputs_signature; - this.algorithm_inputs_signature = algorithm_inputs_signature; - this.algorithm_outputs_signature = algorithm_outputs_signature; - this.iteration_entries = []; - - // First process all the possibilities for which only one choice is possible - if (!this._automaticMapping()) this.possibilities = null; + // Attributes + this.possibilities = JSON.parse(JSON.stringify(possibilities)); + this.block_inputs_signature = block_inputs_signature; + this.block_outputs_signature = block_outputs_signature; + this.algorithm_inputs_signature = algorithm_inputs_signature; + this.algorithm_outputs_signature = algorithm_outputs_signature; + this.iteration_entries = []; + + // First process all the possibilities for which only one choice is possible + if (!this._automaticMapping()) this.possibilities = null; }; //---------------------------------------------------------------------------------------- // Indicates if a valid mapping was found //---------------------------------------------------------------------------------------- beat.experiments.utils.SmartMapping.prototype.isValid = function() { - return this.possibilities !== null; + return this.possibilities !== null; }; //---------------------------------------------------------------------------------------- // Starts an iteration over the best channel/group combinations //---------------------------------------------------------------------------------------- beat.experiments.utils.SmartMapping.prototype.startIteration = function() { - this.iteration_entries = []; - - var channels_to_process = Object.keys(this.possibilities); - var processed_indexes = []; - var biggest = 0; - - // First: add the single possibility combinations - for (var i = 0; i < channels_to_process.length; ++i) { - var channel = channels_to_process[i]; - if (this.possibilities[channel].length == 1) { - this.iteration_entries.push({ - channel: channel, - group: this.possibilities[channel][0].group, - }); - processed_indexes.push(i); - } else if (this.possibilities[channel].length > biggest) { - biggest = this.possibilities[channel].length; - } - } - - processed_indexes.reverse(); - for (var i = 0; i < processed_indexes.length; ++i) - channels_to_process.splice(processed_indexes[i], 1); - - // Next: add the best match of all the other combinations, beginning with the ones - // with the most possibilities - var unavailable_groups = []; - while (channels_to_process.length > 0) { - processed_indexes = []; - var biggest2 = 0; - - for (var i = 0; i < channels_to_process.length; ++i) { - var channel = channels_to_process[i]; - - if (this.possibilities[channel].length == biggest) { - var entries = this.possibilities[channel].filter(function(entry) { - return unavailable_groups.indexOf(entry.group) == -1; - }); - - this.iteration_entries.push({ - channel: channel, - group: entries[0].group, - }); - - processed_indexes.push(i); - unavailable_groups.push(entries[0].group); - } else if (this.possibilities[channel].length > biggest2) { - biggest2 = this.possibilities[channel].length; - } - } - - processed_indexes.reverse(); - for (var i = 0; i < processed_indexes.length; ++i) - channels_to_process.splice(processed_indexes[i], 1); - - biggest = biggest2; - } - - this.iteration_entries.reverse(); + this.iteration_entries = []; + + var channels_to_process = Object.keys(this.possibilities); + var processed_indexes = []; + var biggest = 0; + + // First: add the single possibility combinations + for (var i = 0; i < channels_to_process.length; ++i) { + var channel = channels_to_process[i]; + if (this.possibilities[channel].length == 1) { + this.iteration_entries.push({ + channel: channel, + group: this.possibilities[channel][0].group, + }); + processed_indexes.push(i); + } else if (this.possibilities[channel].length > biggest) { + biggest = this.possibilities[channel].length; + } + } + + processed_indexes.reverse(); + for (var i = 0; i < processed_indexes.length; ++i) + channels_to_process.splice(processed_indexes[i], 1); + + // Next: add the best match of all the other combinations, beginning with the ones + // with the most possibilities + var unavailable_groups = []; + while (channels_to_process.length > 0) { + processed_indexes = []; + var biggest2 = 0; + + for (var i = 0; i < channels_to_process.length; ++i) { + var channel = channels_to_process[i]; + + if (this.possibilities[channel].length == biggest) { + var entries = this.possibilities[channel].filter(function(entry) { + return unavailable_groups.indexOf(entry.group) == -1; + }); + + this.iteration_entries.push({ + channel: channel, + group: entries[0].group, + }); + + processed_indexes.push(i); + unavailable_groups.push(entries[0].group); + } else if (this.possibilities[channel].length > biggest2) { + biggest2 = this.possibilities[channel].length; + } + } + + processed_indexes.reverse(); + for (var i = 0; i < processed_indexes.length; ++i) + channels_to_process.splice(processed_indexes[i], 1); + + biggest = biggest2; + } + + this.iteration_entries.reverse(); }; //---------------------------------------------------------------------------------------- // Indicates if the iteration over the best channel/group combinations is done //---------------------------------------------------------------------------------------- beat.experiments.utils.SmartMapping.prototype.isIterationDone = function() { - return this.iteration_entries.length == 0; + return this.iteration_entries.length == 0; }; //---------------------------------------------------------------------------------------- // Returns the next channel/group combination in the iteration //---------------------------------------------------------------------------------------- beat.experiments.utils.SmartMapping.prototype.next = function() { - return this.iteration_entries.pop(); + return this.iteration_entries.pop(); }; //--------------------------------------------------------- beat.experiments.utils.SmartMapping.prototype._removePossibility = function( - channel_names, - group_to_remove + channel_names, + group_to_remove ) { - for (var i = 0; i < channel_names.length; ++i) { - var channel = channel_names[i]; + for (var i = 0; i < channel_names.length; ++i) { + var channel = channel_names[i]; - for (var j = 0; j < this.possibilities[channel].length; ++j) { - if (this.possibilities[channel][j] == group_to_remove) { - this.possibilities[channel].splice(j, 1); + for (var j = 0; j < this.possibilities[channel].length; ++j) { + if (this.possibilities[channel][j] == group_to_remove) { + this.possibilities[channel].splice(j, 1); - if (this.possibilities[channel].length == 0) return false; + if (this.possibilities[channel].length == 0) return false; - break; - } - } - } + break; + } + } + } - return true; + return true; }; //---------------------------------------------------------------------------------------- // Automatically assign the channels to the groups //---------------------------------------------------------------------------------------- beat.experiments.utils.SmartMapping.prototype._automaticMapping = function() { - var channel_names = Object.keys(this.possibilities); - - // Process all the channels for which there is only one unique group - var changed = true; - while (changed && channel_names.length > 0) { - changed = false; - for (var i = 0; i < channel_names.length; ++i) { - var channel = channel_names[i]; - - if (this.possibilities[channel].length == 1) { - this.possibilities[channel] = [ - { - group: this.possibilities[channel][0], - score: 1.0, - }, - ]; - - channel_names.splice(i, 1); - - if ( - !this._removePossibility( - channel_names, - this.possibilities[channel][0].group - ) - ) - return false; - - changed = true; - break; - } - } - } - - // Process all the groups for which there is only one unique channel - var reverse_possibilities = {}; - for (var i = 0; i < channel_names.length; ++i) { - var channel = channel_names[i]; - - for (var j = 0; j < this.possibilities[channel].length; ++j) { - var group = this.possibilities[channel][j]; - - if (reverse_possibilities[group] === undefined) - reverse_possibilities[group] = []; - - reverse_possibilities[group].push(channel); - } - } - - var group_names = Object.keys(reverse_possibilities); - for (var i = 0; i < group_names.length; ++i) { - var group = group_names[i]; - - if (reverse_possibilities[group].length == 1) { - this.possibilities[reverse_possibilities[group][0]] = [ - { - group: group, - score: 1.0, - }, - ]; - - channel_names.splice( - channel_names.indexOf(reverse_possibilities[group][0]), - 1 - ); - - if (!this._removePossibility(channel_names, group)) return false; - } - } - - // Process all the remaining channels - for (var i = 0; i < channel_names.length; ++i) { - var channel = channel_names[i]; - - var result = []; - for (var j = 0; j < this.possibilities[channel].length; ++j) { - var entry = { - group: this.possibilities[channel][j], - score: channel.score(this.possibilities[channel][j], 0.5), - }; - - result.push(entry); - } - - result.sort(function(a, b) { - return a.score < b.score; - }); - this.possibilities[channel] = result; - } - - return true; + var channel_names = Object.keys(this.possibilities); + + // Process all the channels for which there is only one unique group + var changed = true; + while (changed && channel_names.length > 0) { + changed = false; + for (var i = 0; i < channel_names.length; ++i) { + var channel = channel_names[i]; + + if (this.possibilities[channel].length == 1) { + this.possibilities[channel] = [ + { + group: this.possibilities[channel][0], + score: 1.0, + }, + ]; + + channel_names.splice(i, 1); + + if ( + !this._removePossibility( + channel_names, + this.possibilities[channel][0].group + ) + ) + return false; + + changed = true; + break; + } + } + } + + // Process all the groups for which there is only one unique channel + var reverse_possibilities = {}; + for (var i = 0; i < channel_names.length; ++i) { + var channel = channel_names[i]; + + for (var j = 0; j < this.possibilities[channel].length; ++j) { + var group = this.possibilities[channel][j]; + + if (reverse_possibilities[group] === undefined) + reverse_possibilities[group] = []; + + reverse_possibilities[group].push(channel); + } + } + + var group_names = Object.keys(reverse_possibilities); + for (var i = 0; i < group_names.length; ++i) { + var group = group_names[i]; + + if (reverse_possibilities[group].length == 1) { + this.possibilities[reverse_possibilities[group][0]] = [ + { + group: group, + score: 1.0, + }, + ]; + + channel_names.splice( + channel_names.indexOf(reverse_possibilities[group][0]), + 1 + ); + + if (!this._removePossibility(channel_names, group)) return false; + } + } + + // Process all the remaining channels + for (var i = 0; i < channel_names.length; ++i) { + var channel = channel_names[i]; + + var result = []; + for (var j = 0; j < this.possibilities[channel].length; ++j) { + var entry = { + group: this.possibilities[channel][j], + score: channel.score(this.possibilities[channel][j], 0.5), + }; + + result.push(entry); + } + + result.sort(function(a, b) { + return a.score < b.score; + }); + this.possibilities[channel] = result; + } + + return true; }; /** @@ -1649,54 +1649,54 @@ beat.experiments.utils.SmartMapping.prototype._automaticMapping = function() { * finished (e.g. "/experiments/user/user/toolchain/1/name/") **/ beat.experiments.utils.modal_attest = function(name, url, redirect) { - var dialog = new BootstrapDialog({ - title: '<i class="fa fa-certificate"></i> Attestation for "' + name + '"', - message: - '<p>When you attest an experiment, the platform guarantees it is reproducible, therefore all components related to this experiment (including the toolchain, algorithms, libraries and dataformats) will be frozen. This procedure is <strong>not</strong> irreversible. You can always delete locked (i.e. unpublished) attestations. This procedure also does not stop you from forking or creating new revisions of objects used in this experiment.</p>', - type: BootstrapDialog.TYPE_PRIMARY, - size: BootstrapDialog.SIZE_WIDE, - buttons: [ - { - label: 'Cancel', - cssClass: 'btn-default', - action: function(the_dialog) { - the_dialog.close(); - }, - }, - { - label: 'Attest', - cssClass: 'btn-primary', - action: function(the_dialog) { - $.ajaxSetup({ - beforeSend: function(xhr, settings) { - var csrftoken = $.cookie('csrftoken'); - xhr.setRequestHeader('X-CSRFToken', csrftoken); - }, - }); - - var d = $.ajax({ - type: 'POST', - url: url, - data: JSON.stringify({ experiment: name }), - contentType: 'application/json; charset=utf-8', - dataType: 'json', - }); - - d.done(function(data) { - the_dialog.close(); - window.location.href = redirect; - }); - - d.fail(function(data, text_status) { - the_dialog.close(); - process_error(data, text_status); - }); - }, - }, - ], - }); - dialog.realize(); - dialog.open(); + var dialog = new BootstrapDialog({ + title: '<i class="fa fa-certificate"></i> Attestation for "' + name + '"', + message: + '<p>When you attest an experiment, the platform guarantees it is reproducible, therefore all components related to this experiment (including the toolchain, algorithms, libraries and dataformats) will be frozen. This procedure is <strong>not</strong> irreversible. You can always delete locked (i.e. unpublished) attestations. This procedure also does not stop you from forking or creating new revisions of objects used in this experiment.</p>', + type: BootstrapDialog.TYPE_PRIMARY, + size: BootstrapDialog.SIZE_WIDE, + buttons: [ + { + label: 'Cancel', + cssClass: 'btn-default', + action: function(the_dialog) { + the_dialog.close(); + }, + }, + { + label: 'Attest', + cssClass: 'btn-primary', + action: function(the_dialog) { + $.ajaxSetup({ + beforeSend: function(xhr, settings) { + var csrftoken = $.cookie('csrftoken'); + xhr.setRequestHeader('X-CSRFToken', csrftoken); + }, + }); + + var d = $.ajax({ + type: 'POST', + url: url, + data: JSON.stringify({ experiment: name }), + contentType: 'application/json; charset=utf-8', + dataType: 'json', + }); + + d.done(function(data) { + the_dialog.close(); + window.location.href = redirect; + }); + + d.fail(function(data, text_status) { + the_dialog.close(); + process_error(data, text_status); + }); + }, + }, + ], + }); + dialog.realize(); + dialog.open(); }; /** @@ -1714,50 +1714,50 @@ beat.experiments.utils.modal_attest = function(name, url, redirect) { * finished (e.g. "/experiments/user/user/toolchain/1/name/") **/ beat.experiments.utils.modal_cancel = function(name, url, redirect) { - var dialog = new BootstrapDialog({ - title: '<i class="fa fa-power-off"></i> Stopping experiment "' + name + '"', - message: - '<p>Choose "Cancel" to halt the stop operation. Choose "Stop" to continue and cancel the experiment. Stopping the experiment execution does not erase cached information, so you can continue later from the point where you have stopped.</p>', - type: BootstrapDialog.TYPE_PRIMARY, - buttons: [ - { - label: 'Cancel', - cssClass: 'btn-default', - action: function(the_dialog) { - the_dialog.close(); - }, - }, - { - label: 'Stop', - cssClass: 'btn-primary', - action: function(the_dialog) { - $.ajaxSetup({ - beforeSend: function(xhr, settings) { - var csrftoken = $.cookie('csrftoken'); - xhr.setRequestHeader('X-CSRFToken', csrftoken); - }, - }); - - var d = $.ajax({ - type: 'POST', - url: url, - }); - - d.done(function(data) { - the_dialog.close(); - window.location.href = redirect; - }); - - d.fail(function(data, text_status) { - the_dialog.close(); - process_error(data, text_status); - }); - }, - }, - ], - }); - dialog.realize(); - dialog.open(); + var dialog = new BootstrapDialog({ + title: '<i class="fa fa-power-off"></i> Stopping experiment "' + name + '"', + message: + '<p>Choose "Cancel" to halt the stop operation. Choose "Stop" to continue and cancel the experiment. Stopping the experiment execution does not erase cached information, so you can continue later from the point where you have stopped.</p>', + type: BootstrapDialog.TYPE_PRIMARY, + buttons: [ + { + label: 'Cancel', + cssClass: 'btn-default', + action: function(the_dialog) { + the_dialog.close(); + }, + }, + { + label: 'Stop', + cssClass: 'btn-primary', + action: function(the_dialog) { + $.ajaxSetup({ + beforeSend: function(xhr, settings) { + var csrftoken = $.cookie('csrftoken'); + xhr.setRequestHeader('X-CSRFToken', csrftoken); + }, + }); + + var d = $.ajax({ + type: 'POST', + url: url, + }); + + d.done(function(data) { + the_dialog.close(); + window.location.href = redirect; + }); + + d.fail(function(data, text_status) { + the_dialog.close(); + process_error(data, text_status); + }); + }, + }, + ], + }); + dialog.realize(); + dialog.open(); }; /** @@ -1772,212 +1772,212 @@ beat.experiments.utils.modal_cancel = function(name, url, redirect) { * **/ beat.experiments.utils.modal_add_to_report = function(names, report_list_url) { - if (!Array.isArray(names)) { - BootstrapDialog.alert('The input "names" must be an array'); - return false; - } - - if (names.length == 0) { - BootstrapDialog.alert('Select at least 1 experiment to add to a report'); - return false; - } - - $.ajaxSetup({ - beforeSend: function(xhr, settings) { - var csrftoken = $.cookie('csrftoken'); - xhr.setRequestHeader('X-CSRFToken', csrftoken); - }, - }); - - //retrieve list of existing reports - if that succeeds, construct modal form - function addToReport(names) { - return $.ajax({ - type: 'GET', - url: report_list_url + '?fields=name,short_description,add_url', - }).pipe(function(data) { - var message = $(document.createElement('div')); - message.append( - $(document.createElement('p')).text( - 'By clicking Add, the experiment(s) will be added to the selected report (if possible). You can cancel the operation by clicking Cancel.' - ) - ); - var form_group = $(document.createElement('div')).addClass('form-group'); - message.append(form_group); - var select = $(document.createElement('select')).addClass('form-control'); - form_group.append(select); - var first_option = $(document.createElement('option')); - select.append(first_option); - first_option.val(''); - first_option.attr('disabled', true); - first_option.attr('selected', true); - first_option.text('Select a report...'); - - data.forEach(function(i) { - const opt = $(document.createElement('option')); - select.append(opt); - opt.val(i.add_url); - const div = $(document.createElement('div')); - opt.append(div); - opt.data('name', i.name); - div.text(i.name); - if (i.short_description) { - const help = $(document.createElement('span')).addClass('help'); - help.text(' (' + i.short_description + ')'); - div.append(help); - } - }); - - BootstrapDialog.show({ - title: '<i class="fa fa-file-text-o fa-lg"></i> Select a report', - message: message, - type: BootstrapDialog.TYPE_PRIMARY, - buttons: [ - { - label: 'Cancel', - cssClass: 'btn-default', - action: function(the_dialog) { - the_dialog.close(); - return false; - }, - }, - { - label: 'Add', - cssClass: 'btn-primary', - action: function(the_dialog) { - if (!select.val()) { - BootstrapDialog.alert({ - title: '<i class="fa fa-warning"></i> Error', - message: 'You must select a report to add experiments to', - type: BootstrapDialog.TYPE_WARNING, - }); - return false; - } - the_dialog.close(); - - var post_info = { experiments: names }; - - var d = $.ajax({ - type: 'POST', - data: JSON.stringify(post_info), - url: select.val(), - contentType: 'application/json; charset=utf-8', - dataType: 'json', - }); - - d.done(function(data, status) { - var message = $(document.createElement('div')); - message.addClass('report-results'); - - var sent = post_info.experiments.length; - var successful = sent; - - var description = $(document.createElement('h5')); - message.append(description); - - if (data !== undefined) { - // some experiments have failed - - // adds information about failed/incompatible experiments - function _add_list(message, objects, title) { - if (objects === undefined) return; - var length = objects.length; - if (length > 0) { - var _title = $(document.createElement('h5')); - _title.text(title); - message.append(_title); - var ul = $(document.createElement('ul')); - for (var i = 0; i < length; ++i) { - var li = $(document.createElement('li')); - li.text(objects[i]); - ul.append(li); - } - message.append(ul); - } - return length; - } - if (data.inaccessible_experiments !== undefined) - successful -= _add_list( - message, - data.inaccessible_experiments, - 'These experiments have failed (and cannot be added):' - ); - if (data.incompatible_experiments !== undefined) - successful -= _add_list( - message, - data.incompatible_experiments, - 'These experiments have different analyzers (and cannot be added):' - ); - } - - var size = BootstrapDialog.SIZE_NORMAL; - var type = BootstrapDialog.TYPE_PRIMARY; - var title = '<i class="fa fa-check"></i> Report changes'; - var btn_type = 'btn-primary'; - let viewReportUrl = `${$.ajaxSettings.url.split( - '/experiments' - )[0]}${select - .val() - .replace('add/', '') - .replace(/api\/v.\//, '')}`; - if (successful == sent) { - description.text( - `Successfully added ${sent} experiment(s) to report` - ); - } else { - description.text( - 'Added ' + - successful + - ' (out of ' + - sent + - ' in total) experiment(s) to report' - ); - size = BootstrapDialog.SIZE_WIDE; - type = BootstrapDialog.TYPE_WARNING; - btn_type = 'btn-warning'; - title = '<i class="fa fa-warning"></i> Report changes'; - } - - BootstrapDialog.show({ - title: title, - message: message, - size: size, - type: type, - buttons: [ - { - label: 'View Report', - cssClass: btn_type, - action: function(dialog) { - dialog.close(); - window.open(viewReportUrl, '_blank'); - }, - }, - { - label: 'OK', - cssClass: btn_type, - action: function(dialog) { - dialog.close(); - }, - }, - ], - }); - - return true; - }); - - d.fail(function(data, status) { - process_error(data, status); - }); - }, - }, - ], - }); - }); - } - - addToReport(names).fail(function(data, text_status) { - process_error(data, text_status); - return false; - }); + if (!Array.isArray(names)) { + BootstrapDialog.alert('The input "names" must be an array'); + return false; + } + + if (names.length == 0) { + BootstrapDialog.alert('Select at least 1 experiment to add to a report'); + return false; + } + + $.ajaxSetup({ + beforeSend: function(xhr, settings) { + var csrftoken = $.cookie('csrftoken'); + xhr.setRequestHeader('X-CSRFToken', csrftoken); + }, + }); + + //retrieve list of existing reports - if that succeeds, construct modal form + function addToReport(names) { + return $.ajax({ + type: 'GET', + url: report_list_url + '?fields=name,short_description,add_url', + }).pipe(function(data) { + var message = $(document.createElement('div')); + message.append( + $(document.createElement('p')).text( + 'By clicking Add, the experiment(s) will be added to the selected report (if possible). You can cancel the operation by clicking Cancel.' + ) + ); + var form_group = $(document.createElement('div')).addClass('form-group'); + message.append(form_group); + var select = $(document.createElement('select')).addClass('form-control'); + form_group.append(select); + var first_option = $(document.createElement('option')); + select.append(first_option); + first_option.val(''); + first_option.attr('disabled', true); + first_option.attr('selected', true); + first_option.text('Select a report...'); + + data.forEach(function(i) { + const opt = $(document.createElement('option')); + select.append(opt); + opt.val(i.add_url); + const div = $(document.createElement('div')); + opt.append(div); + opt.data('name', i.name); + div.text(i.name); + if (i.short_description) { + const help = $(document.createElement('span')).addClass('help'); + help.text(' (' + i.short_description + ')'); + div.append(help); + } + }); + + BootstrapDialog.show({ + title: '<i class="fa fa-file-text-o fa-lg"></i> Select a report', + message: message, + type: BootstrapDialog.TYPE_PRIMARY, + buttons: [ + { + label: 'Cancel', + cssClass: 'btn-default', + action: function(the_dialog) { + the_dialog.close(); + return false; + }, + }, + { + label: 'Add', + cssClass: 'btn-primary', + action: function(the_dialog) { + if (!select.val()) { + BootstrapDialog.alert({ + title: '<i class="fa fa-warning"></i> Error', + message: 'You must select a report to add experiments to', + type: BootstrapDialog.TYPE_WARNING, + }); + return false; + } + the_dialog.close(); + + var post_info = { experiments: names }; + + var d = $.ajax({ + type: 'POST', + data: JSON.stringify(post_info), + url: select.val(), + contentType: 'application/json; charset=utf-8', + dataType: 'json', + }); + + d.done(function(data, status) { + var message = $(document.createElement('div')); + message.addClass('report-results'); + + var sent = post_info.experiments.length; + var successful = sent; + + var description = $(document.createElement('h5')); + message.append(description); + + if (data !== undefined) { + // some experiments have failed + + // adds information about failed/incompatible experiments + function _add_list(message, objects, title) { + if (objects === undefined) return; + var length = objects.length; + if (length > 0) { + var _title = $(document.createElement('h5')); + _title.text(title); + message.append(_title); + var ul = $(document.createElement('ul')); + for (var i = 0; i < length; ++i) { + var li = $(document.createElement('li')); + li.text(objects[i]); + ul.append(li); + } + message.append(ul); + } + return length; + } + if (data.inaccessible_experiments !== undefined) + successful -= _add_list( + message, + data.inaccessible_experiments, + 'These experiments have failed (and cannot be added):' + ); + if (data.incompatible_experiments !== undefined) + successful -= _add_list( + message, + data.incompatible_experiments, + 'These experiments have different analyzers (and cannot be added):' + ); + } + + var size = BootstrapDialog.SIZE_NORMAL; + var type = BootstrapDialog.TYPE_PRIMARY; + var title = '<i class="fa fa-check"></i> Report changes'; + var btn_type = 'btn-primary'; + let viewReportUrl = `${$.ajaxSettings.url.split( + '/experiments' + )[0]}${select + .val() + .replace('add/', '') + .replace(/api\/v.\//, '')}`; + if (successful == sent) { + description.text( + `Successfully added ${sent} experiment(s) to report` + ); + } else { + description.text( + 'Added ' + + successful + + ' (out of ' + + sent + + ' in total) experiment(s) to report' + ); + size = BootstrapDialog.SIZE_WIDE; + type = BootstrapDialog.TYPE_WARNING; + btn_type = 'btn-warning'; + title = '<i class="fa fa-warning"></i> Report changes'; + } + + BootstrapDialog.show({ + title: title, + message: message, + size: size, + type: type, + buttons: [ + { + label: 'View Report', + cssClass: btn_type, + action: function(dialog) { + dialog.close(); + window.open(viewReportUrl, '_blank'); + }, + }, + { + label: 'OK', + cssClass: btn_type, + action: function(dialog) { + dialog.close(); + }, + }, + ], + }); + + return true; + }); + + d.fail(function(data, status) { + process_error(data, status); + }); + }, + }, + ], + }); + }); + } + + addToReport(names).fail(function(data, text_status) { + process_error(data, text_status); + return false; + }); }; /** @@ -1991,112 +1991,112 @@ beat.experiments.utils.modal_add_to_report = function(names, report_list_url) { * **/ beat.experiments.utils.modal_new_experiment = function(toolchain_list_url) { - $.ajaxSetup({ - beforeSend: function(xhr, settings) { - var csrftoken = $.cookie('csrftoken'); - xhr.setRequestHeader('X-CSRFToken', csrftoken); - }, - }); - - //retrieve list of existing toolchains - if that succeeds, builds modal form - var d = $.ajax({ - type: 'GET', - url: - toolchain_list_url + '?fields=name,short_description,new_experiment_url', - }); - - d.fail(function(data, text_status) { - process_error(data, text_status); - return false; - }); - - d.done(function(data) { - var message = $(document.createElement('div')); - message.append( - $(document.createElement('p')).text( - 'Choose a toolchain to create a new experiment.' - ) - ); - var form_group = $(document.createElement('div')).addClass('form-group'); - message.append(form_group); - var select = $(document.createElement('select')).addClass('form-control'); - form_group.append(select); - var first_option = $(document.createElement('option')); - select.append(first_option); - first_option.val(''); - first_option.attr('disabled', true); - first_option.attr('selected', true); - first_option.text('Select a toolchain...'); - - select.chosen({ - disable_search_threshold: 5, - search_contains: true, - allow_single_deselect: true, - }); - - select.on('chosen:showing_dropdown', function(e, params) { - select.find('option:gt(0)').remove(); - select.find('option:eq(0)').attr('selected', true); - data.forEach(function(i) { - var opt = $(document.createElement('option')); - select.append(opt); - opt.val(i.new_experiment_url); - var div = $(document.createElement('div')); - opt.append(div); - opt.data('name', i.name); - div.text(i.name); - if (i.short_description) { - var help = $(document.createElement('span')).addClass('help'); - help.text(' (' + i.short_description + ')'); - div.append(help); - } - }); - select.trigger('chosen:updated'); - select.trigger('chosen:open'); - }); - - //fix options when selected - select.on('change', function(e, params) { - var selected = $(this).find('option:selected'); - selected.text(selected.data('name')); - select.trigger('chosen:updated'); - }); - - BootstrapDialog.show({ - title: '<i class="fa fa-cog fa-lg"> Select a toolchain', - message: message, - type: BootstrapDialog.TYPE_PRIMARY, - onshown: function(the_dialog) { - select.trigger('chosen:activate'); - }, - buttons: [ - { - label: 'Cancel', - cssClass: 'btn-default', - action: function(the_dialog) { - the_dialog.close(); - return false; - }, - }, - { - label: 'Create', - cssClass: 'btn-primary', - action: function(the_dialog) { - if (!select.val()) { - BootstrapDialog.alert({ - title: '<i class="fa fa-warning"></i> Error', - message: 'You must select a toolchain to continue', - type: BootstrapDialog.TYPE_WARNING, - }); - return false; - } - the_dialog.close(); - window.location.href = select.val(); - }, - }, - ], - }); - }); + $.ajaxSetup({ + beforeSend: function(xhr, settings) { + var csrftoken = $.cookie('csrftoken'); + xhr.setRequestHeader('X-CSRFToken', csrftoken); + }, + }); + + //retrieve list of existing toolchains - if that succeeds, builds modal form + var d = $.ajax({ + type: 'GET', + url: + toolchain_list_url + '?fields=name,short_description,new_experiment_url', + }); + + d.fail(function(data, text_status) { + process_error(data, text_status); + return false; + }); + + d.done(function(data) { + var message = $(document.createElement('div')); + message.append( + $(document.createElement('p')).text( + 'Choose a toolchain to create a new experiment.' + ) + ); + var form_group = $(document.createElement('div')).addClass('form-group'); + message.append(form_group); + var select = $(document.createElement('select')).addClass('form-control'); + form_group.append(select); + var first_option = $(document.createElement('option')); + select.append(first_option); + first_option.val(''); + first_option.attr('disabled', true); + first_option.attr('selected', true); + first_option.text('Select a toolchain...'); + + select.chosen({ + disable_search_threshold: 5, + search_contains: true, + allow_single_deselect: true, + }); + + select.on('chosen:showing_dropdown', function(e, params) { + select.find('option:gt(0)').remove(); + select.find('option:eq(0)').attr('selected', true); + data.forEach(function(i) { + var opt = $(document.createElement('option')); + select.append(opt); + opt.val(i.new_experiment_url); + var div = $(document.createElement('div')); + opt.append(div); + opt.data('name', i.name); + div.text(i.name); + if (i.short_description) { + var help = $(document.createElement('span')).addClass('help'); + help.text(' (' + i.short_description + ')'); + div.append(help); + } + }); + select.trigger('chosen:updated'); + select.trigger('chosen:open'); + }); + + //fix options when selected + select.on('change', function(e, params) { + var selected = $(this).find('option:selected'); + selected.text(selected.data('name')); + select.trigger('chosen:updated'); + }); + + BootstrapDialog.show({ + title: '<i class="fa fa-cog fa-lg"> Select a toolchain', + message: message, + type: BootstrapDialog.TYPE_PRIMARY, + onshown: function(the_dialog) { + select.trigger('chosen:activate'); + }, + buttons: [ + { + label: 'Cancel', + cssClass: 'btn-default', + action: function(the_dialog) { + the_dialog.close(); + return false; + }, + }, + { + label: 'Create', + cssClass: 'btn-primary', + action: function(the_dialog) { + if (!select.val()) { + BootstrapDialog.alert({ + title: '<i class="fa fa-warning"></i> Error', + message: 'You must select a toolchain to continue', + type: BootstrapDialog.TYPE_WARNING, + }); + return false; + } + the_dialog.close(); + window.location.href = select.val(); + }, + }, + ], + }); + }); }; /** @@ -2112,99 +2112,99 @@ beat.experiments.utils.modal_new_experiment = function(toolchain_list_url) { * **/ beat.experiments.utils.modal_rename = function( - userid, - current_name, - list_url, - update_url + userid, + current_name, + list_url, + update_url ) { - $.ajaxSetup({ - beforeSend: function(xhr, settings) { - var csrftoken = $.cookie('csrftoken'); - xhr.setRequestHeader('X-CSRFToken', csrftoken); - }, - }); - - //retrieve list of existing experiments - if that succeeds, builds modal form - var d = $.ajax({ - type: 'GET', - url: list_url + '?fields=short_name,author', - }); - - d.fail(function(data, text_status) { - process_error(data, text_status); - return false; - }); - - d.done(function(data) { - //filter returned names to only keep author experiments' names - data = data - .filter(function(e) { - return e.author == userid && e.short_name != current_name; - }) - .map(function(e) { - return e.short_name; - }); - - var message = $(document.createElement('div')); - //message copied from templates/experiments/setup.html - message.append( - $( - '<span class="help">Enter a meaningful name to help you recognize this experiment. Auto-completion will help you in keeping your naming conventions tide. If a chosen name is <span class="text-danger">highlighted in red</span>, it is because it is already being used. In this case, choose another name.</span>' - ) - ); - message.append( - $( - '<div class="form-group has-feedback"><label class="control-label" for="settings_name">Name:</label><input class="form-control input-sm" id="modal-rename" type="text" class="label" data-placeholder="Start typing a name..." autocomplete="off" autocorrect="off" autocapitalize="off" value="' + - current_name + - '"></input><span class="glyphicon glyphicon-remove form-control-feedback" aria-hidden="true"></span>' - ) - ); - - beat.experiments.dialogs.name_typeahead(message.find('input'), data); - - BootstrapDialog.show({ - title: '<i class="fa fa-tag fa-lg"></i> Rename experiment', - message: message, - type: BootstrapDialog.TYPE_PRIMARY, - buttons: [ - { - label: 'Cancel', - cssClass: 'btn-default', - action: function(the_dialog) { - the_dialog.close(); - return false; - }, - }, - { - label: 'Rename', - cssClass: 'btn-primary', - action: function(the_dialog) { - the_dialog.close(); - var new_name = message.find('input').val().trim(); - if (new_name === current_name) { - the_dialog.close(); - return false; - } - var d2 = $.ajax({ - type: 'PUT', - url: update_url, - data: JSON.stringify({ name: new_name }), - contentType: 'application/json; charset=utf-8', - dataType: 'json', - }); - d2.fail(function(data, text_status) { - process_error(data, text_status); - return false; - }); - d2.done(function(data) { - window.location.href = data.view_url; - return true; - }); - }, - }, - ], - }); - }); + $.ajaxSetup({ + beforeSend: function(xhr, settings) { + var csrftoken = $.cookie('csrftoken'); + xhr.setRequestHeader('X-CSRFToken', csrftoken); + }, + }); + + //retrieve list of existing experiments - if that succeeds, builds modal form + var d = $.ajax({ + type: 'GET', + url: list_url + '?fields=short_name,author', + }); + + d.fail(function(data, text_status) { + process_error(data, text_status); + return false; + }); + + d.done(function(data) { + //filter returned names to only keep author experiments' names + data = data + .filter(function(e) { + return e.author == userid && e.short_name != current_name; + }) + .map(function(e) { + return e.short_name; + }); + + var message = $(document.createElement('div')); + //message copied from templates/experiments/setup.html + message.append( + $( + '<span class="help">Enter a meaningful name to help you recognize this experiment. Auto-completion will help you in keeping your naming conventions tide. If a chosen name is <span class="text-danger">highlighted in red</span>, it is because it is already being used. In this case, choose another name.</span>' + ) + ); + message.append( + $( + '<div class="form-group has-feedback"><label class="control-label" for="settings_name">Name:</label><input class="form-control input-sm" id="modal-rename" type="text" class="label" data-placeholder="Start typing a name..." autocomplete="off" autocorrect="off" autocapitalize="off" value="' + + current_name + + '"></input><span class="glyphicon glyphicon-remove form-control-feedback" aria-hidden="true"></span>' + ) + ); + + beat.experiments.dialogs.name_typeahead(message.find('input'), data); + + BootstrapDialog.show({ + title: '<i class="fa fa-tag fa-lg"></i> Rename experiment', + message: message, + type: BootstrapDialog.TYPE_PRIMARY, + buttons: [ + { + label: 'Cancel', + cssClass: 'btn-default', + action: function(the_dialog) { + the_dialog.close(); + return false; + }, + }, + { + label: 'Rename', + cssClass: 'btn-primary', + action: function(the_dialog) { + the_dialog.close(); + var new_name = message.find('input').val().trim(); + if (new_name === current_name) { + the_dialog.close(); + return false; + } + var d2 = $.ajax({ + type: 'PUT', + url: update_url, + data: JSON.stringify({ name: new_name }), + contentType: 'application/json; charset=utf-8', + dataType: 'json', + }); + d2.fail(function(data, text_status) { + process_error(data, text_status); + return false; + }); + d2.done(function(data) { + window.location.href = data.view_url; + return true; + }); + }, + }, + ], + }); + }); }; /** @@ -2222,21 +2222,21 @@ beat.experiments.utils.modal_rename = function( * where the current block state is stored. */ beat.experiments.utils.update_viewer = function( - viewer, - objects, - dt_block_name, - dt_block_status + viewer, + objects, + dt_block_name, + dt_block_status ) { - //gather block information - block_status = {}; - objects.each(function(idx) { - var bk_name = $(this).data(dt_block_name); - if (!bk_name) return; - var bk_status = $(this).data(dt_block_status); - if (bk_status === 'cached') bk_status = 'generated'; //< viewer quirk - block_status[bk_name] = bk_status; - }); - viewer.updateBlocksStatus(block_status); + //gather block information + block_status = {}; + objects.each(function(idx) { + var bk_name = $(this).data(dt_block_name); + if (!bk_name) return; + var bk_status = $(this).data(dt_block_status); + if (bk_status === 'cached') bk_status = 'generated'; //< viewer quirk + block_status[bk_name] = bk_status; + }); + viewer.updateBlocksStatus(block_status); }; /** @@ -2265,74 +2265,74 @@ beat.experiments.utils.update_viewer = function( * representations and the toolchain viewer. */ beat.experiments.utils.update_blocks = function( - url, - st, - dt, - unveil, - objects, - dt_block_name, - viewer, - interval + url, + st, + dt, + unveil, + objects, + dt_block_name, + viewer, + interval ) { - var _status = $(st).data(dt); - - if (_status === 'Failed') { - beat.experiments.utils.update_viewer(viewer, $(objects), dt_block_name, dt); - return; - } - - //only updates if in one of the "interesting" states - var interesting_states = ['Scheduled', 'Running', 'Canceling']; - if (interesting_states.indexOf(_status) <= -1) return; - - function _do_update() { - var _status = $(st).data(dt); - - if (interesting_states.indexOf(_status) <= -1) { - //experiment changed status - should reload - $(unveil).show(); - if (viewer.running) viewer.onExperimentDone(); - if (_status === 'Failed') { - beat.experiments.utils.update_viewer( - viewer, - $(objects), - dt_block_name, - dt - ); - } - return; - } - - var d = $.get(url); - - d.done(function(data) { - var parsed = $($.parseHTML(data)); - $(objects).each(function(idx) { - var _self = $(this); - var r = parsed.find('#' + _self.attr('id')); - //only replaces if it changed - var old_status = _self.data(dt); - var new_status = r.data(dt); - if (r && old_status !== new_status) _self.replaceWith(r); - }); - - if (!viewer.running) viewer.onExperimentStarted(); - beat.experiments.utils.update_viewer( - viewer, - $(objects), - dt_block_name, - dt - ); - }); - } - - //if we get to this point, we install the interval function - $.ajaxSetup({ - beforeSend: function(xhr, settings) { - var csrftoken = $.cookie('csrftoken'); - xhr.setRequestHeader('X-CSRFToken', csrftoken); - }, - }); - - var timeout_id = window.setInterval(_do_update, interval); + var _status = $(st).data(dt); + + if (_status === 'Failed') { + beat.experiments.utils.update_viewer(viewer, $(objects), dt_block_name, dt); + return; + } + + //only updates if in one of the "interesting" states + var interesting_states = ['Scheduled', 'Running', 'Canceling']; + if (interesting_states.indexOf(_status) <= -1) return; + + function _do_update() { + var _status = $(st).data(dt); + + if (interesting_states.indexOf(_status) <= -1) { + //experiment changed status - should reload + $(unveil).show(); + if (viewer.running) viewer.onExperimentDone(); + if (_status === 'Failed') { + beat.experiments.utils.update_viewer( + viewer, + $(objects), + dt_block_name, + dt + ); + } + return; + } + + var d = $.get(url); + + d.done(function(data) { + var parsed = $($.parseHTML(data)); + $(objects).each(function(idx) { + var _self = $(this); + var r = parsed.find('#' + _self.attr('id')); + //only replaces if it changed + var old_status = _self.data(dt); + var new_status = r.data(dt); + if (r && old_status !== new_status) _self.replaceWith(r); + }); + + if (!viewer.running) viewer.onExperimentStarted(); + beat.experiments.utils.update_viewer( + viewer, + $(objects), + dt_block_name, + dt + ); + }); + } + + //if we get to this point, we install the interval function + $.ajaxSetup({ + beforeSend: function(xhr, settings) { + var csrftoken = $.cookie('csrftoken'); + xhr.setRequestHeader('X-CSRFToken', csrftoken); + }, + }); + + var timeout_id = window.setInterval(_do_update, interval); }; diff --git a/beat/web/plotters/static/plotters/app/app.config.js b/beat/web/plotters/static/plotters/app/app.config.js index d3915a497..b69fecfc8 100644 --- a/beat/web/plotters/static/plotters/app/app.config.js +++ b/beat/web/plotters/static/plotters/app/app.config.js @@ -1,21 +1,21 @@ /* * Copyright (c) 2016 Idiap Research Institute, http://www.idiap.ch/ * Contact: beat.support@idiap.ch - * + * * This file is part of the beat.web module of the BEAT platform. - * + * * Commercial License Usage * Licensees holding valid commercial BEAT licenses may use this file in * accordance with the terms contained in a written agreement between you * and Idiap. For further information contact tto@idiap.ch - * + * * Alternatively, this file may be used under the terms of the GNU Affero * Public License version 3 as published by the Free Software and appearing * in the file LICENSE.AGPL included in the packaging of this file. * The BEAT platform 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. - * + * * You should have received a copy of the GNU Affero Public License along * with the BEAT platform. If not, see http://www.gnu.org/licenses/. */ diff --git a/beat/web/plotters/static/plotters/app/app.js b/beat/web/plotters/static/plotters/app/app.js index 58a518fb9..1ee6f5086 100644 --- a/beat/web/plotters/static/plotters/app/app.js +++ b/beat/web/plotters/static/plotters/app/app.js @@ -1,21 +1,21 @@ /* * Copyright (c) 2016 Idiap Research Institute, http://www.idiap.ch/ * Contact: beat.support@idiap.ch - * + * * This file is part of the beat.web module of the BEAT platform. - * + * * Commercial License Usage * Licensees holding valid commercial BEAT licenses may use this file in * accordance with the terms contained in a written agreement between you * and Idiap. For further information contact tto@idiap.ch - * + * * Alternatively, this file may be used under the terms of the GNU Affero * Public License version 3 as published by the Free Software and appearing * in the file LICENSE.AGPL included in the packaging of this file. * The BEAT platform 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. - * + * * You should have received a copy of the GNU Affero Public License along * with the BEAT platform. If not, see http://www.gnu.org/licenses/. */ diff --git a/beat/web/plotters/static/plotters/app/controllers/plotterparameterController.js b/beat/web/plotters/static/plotters/app/controllers/plotterparameterController.js index 462a73a7f..3bd323dce 100644 --- a/beat/web/plotters/static/plotters/app/controllers/plotterparameterController.js +++ b/beat/web/plotters/static/plotters/app/controllers/plotterparameterController.js @@ -1,21 +1,21 @@ /* * Copyright (c) 2016 Idiap Research Institute, http://www.idiap.ch/ * Contact: beat.support@idiap.ch - * + * * This file is part of the beat.web module of the BEAT platform. - * + * * Commercial License Usage * Licensees holding valid commercial BEAT licenses may use this file in * accordance with the terms contained in a written agreement between you * and Idiap. For further information contact tto@idiap.ch - * + * * Alternatively, this file may be used under the terms of the GNU Affero * Public License version 3 as published by the Free Software and appearing * in the file LICENSE.AGPL included in the packaging of this file. * The BEAT platform 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. - * + * * You should have received a copy of the GNU Affero Public License along * with the BEAT platform. If not, see http://www.gnu.org/licenses/. */ @@ -101,7 +101,7 @@ app.controller('plotterparameterController',['$scope', 'plotterFactory', 'plotte return false; } }); - + $.each($scope.plotters.selected.declaration.parameters, function( key, value ) { //push all keys in array $scope.textdata.push(key); diff --git a/beat/web/plotters/static/plotters/app/directives/plotterparameterItemView.js b/beat/web/plotters/static/plotters/app/directives/plotterparameterItemView.js index 3afd5c7b3..d2bea19b5 100644 --- a/beat/web/plotters/static/plotters/app/directives/plotterparameterItemView.js +++ b/beat/web/plotters/static/plotters/app/directives/plotterparameterItemView.js @@ -1,21 +1,21 @@ /* * Copyright (c) 2016 Idiap Research Institute, http://www.idiap.ch/ * Contact: beat.support@idiap.ch - * + * * This file is part of the beat.web module of the BEAT platform. - * + * * Commercial License Usage * Licensees holding valid commercial BEAT licenses may use this file in * accordance with the terms contained in a written agreement between you * and Idiap. For further information contact tto@idiap.ch - * + * * Alternatively, this file may be used under the terms of the GNU Affero * Public License version 3 as published by the Free Software and appearing * in the file LICENSE.AGPL included in the packaging of this file. * The BEAT platform 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. - * + * * You should have received a copy of the GNU Affero Public License along * with the BEAT platform. If not, see http://www.gnu.org/licenses/. */ @@ -635,7 +635,7 @@ app.directive("buttonplusminus", function() //Directive used to handle save plotterparameter click app.directive("plotterparameteritems", function($compile) { - return function(scope, element, attrs) + return function(scope, element, attrs) { scope.$on("addParametersElement", function(event) { @@ -670,7 +670,7 @@ app.directive("plotterparameteritems", function($compile) //Directive used to handle dynamic testing on graph display app.directive("testplotterparameters", function($compile) { - return function(scope, element, attrs) + return function(scope, element, attrs) { element.bind("click", function() { diff --git a/beat/web/plotters/static/plotters/app/factories/plotterFactory.js b/beat/web/plotters/static/plotters/app/factories/plotterFactory.js index 9babc79f3..f87fc6712 100644 --- a/beat/web/plotters/static/plotters/app/factories/plotterFactory.js +++ b/beat/web/plotters/static/plotters/app/factories/plotterFactory.js @@ -1,21 +1,21 @@ /* * Copyright (c) 2016 Idiap Research Institute, http://www.idiap.ch/ * Contact: beat.support@idiap.ch - * + * * This file is part of the beat.web module of the BEAT platform. - * + * * Commercial License Usage * Licensees holding valid commercial BEAT licenses may use this file in * accordance with the terms contained in a written agreement between you * and Idiap. For further information contact tto@idiap.ch - * + * * Alternatively, this file may be used under the terms of the GNU Affero * Public License version 3 as published by the Free Software and appearing * in the file LICENSE.AGPL included in the packaging of this file. * The BEAT platform 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. - * + * * You should have received a copy of the GNU Affero Public License along * with the BEAT platform. If not, see http://www.gnu.org/licenses/. */ diff --git a/beat/web/plotters/static/plotters/app/factories/plotterparameterFactory.js b/beat/web/plotters/static/plotters/app/factories/plotterparameterFactory.js index d6292c515..2dd9480de 100644 --- a/beat/web/plotters/static/plotters/app/factories/plotterparameterFactory.js +++ b/beat/web/plotters/static/plotters/app/factories/plotterparameterFactory.js @@ -1,21 +1,21 @@ /* * Copyright (c) 2016 Idiap Research Institute, http://www.idiap.ch/ * Contact: beat.support@idiap.ch - * + * * This file is part of the beat.web module of the BEAT platform. - * + * * Commercial License Usage * Licensees holding valid commercial BEAT licenses may use this file in * accordance with the terms contained in a written agreement between you * and Idiap. For further information contact tto@idiap.ch - * + * * Alternatively, this file may be used under the terms of the GNU Affero * Public License version 3 as published by the Free Software and appearing * in the file LICENSE.AGPL included in the packaging of this file. * The BEAT platform 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. - * + * * You should have received a copy of the GNU Affero Public License along * with the BEAT platform. If not, see http://www.gnu.org/licenses/. */ diff --git a/beat/web/plotters/static/plotters/js/dialogs.js b/beat/web/plotters/static/plotters/js/dialogs.js index 59fa6bc64..f74d7dc0e 100644 --- a/beat/web/plotters/static/plotters/js/dialogs.js +++ b/beat/web/plotters/static/plotters/js/dialogs.js @@ -1,21 +1,21 @@ /* * Copyright (c) 2016 Idiap Research Institute, http://www.idiap.ch/ * Contact: beat.support@idiap.ch - * + * * This file is part of the beat.web module of the BEAT platform. - * + * * Commercial License Usage * Licensees holding valid commercial BEAT licenses may use this file in * accordance with the terms contained in a written agreement between you * and Idiap. For further information contact tto@idiap.ch - * + * * Alternatively, this file may be used under the terms of the GNU Affero * Public License version 3 as published by the Free Software and appearing * in the file LICENSE.AGPL included in the packaging of this file. * The BEAT platform 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. - * + * * You should have received a copy of the GNU Affero Public License along * with the BEAT platform. If not, see http://www.gnu.org/licenses/. */ diff --git a/beat/web/plotters/static/plotters/js/new_plotterparameter_dialog.js b/beat/web/plotters/static/plotters/js/new_plotterparameter_dialog.js index b527b723c..c74462730 100644 --- a/beat/web/plotters/static/plotters/js/new_plotterparameter_dialog.js +++ b/beat/web/plotters/static/plotters/js/new_plotterparameter_dialog.js @@ -1,21 +1,21 @@ /* * Copyright (c) 2016 Idiap Research Institute, http://www.idiap.ch/ * Contact: beat.support@idiap.ch - * + * * This file is part of the beat.web module of the BEAT platform. - * + * * Commercial License Usage * Licensees holding valid commercial BEAT licenses may use this file in * accordance with the terms contained in a written agreement between you * and Idiap. For further information contact tto@idiap.ch - * + * * Alternatively, this file may be used under the terms of the GNU Affero * Public License version 3 as published by the Free Software and appearing * in the file LICENSE.AGPL included in the packaging of this file. * The BEAT platform 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. - * + * * You should have received a copy of the GNU Affero Public License along * with the BEAT platform. If not, see http://www.gnu.org/licenses/. */ diff --git a/beat/web/reports/static/reports/app/app.config.js b/beat/web/reports/static/reports/app/app.config.js index 0896cf9b7..124cae4ab 100644 --- a/beat/web/reports/static/reports/app/app.config.js +++ b/beat/web/reports/static/reports/app/app.config.js @@ -20,13 +20,13 @@ * with the BEAT platform. If not, see http://www.gnu.org/licenses/. */ angular.module('reportApp').config(function configureStartEndSymbol($interpolateProvider) { - $interpolateProvider.startSymbol('{$').endSymbol('$}'); + $interpolateProvider.startSymbol('{$').endSymbol('$}'); } ); angular.module('reportApp').config(function configHttp($httpProvider) { - $httpProvider.defaults.xsrfCookieName = 'csrftoken'; - $httpProvider.defaults.xsrfHeaderName = 'X-CSRFToken'; - $httpProvider.defaults.withCredentials = true; + $httpProvider.defaults.xsrfCookieName = 'csrftoken'; + $httpProvider.defaults.xsrfHeaderName = 'X-CSRFToken'; + $httpProvider.defaults.withCredentials = true; } ); diff --git a/beat/web/reports/static/reports/app/app.js b/beat/web/reports/static/reports/app/app.js index 663256161..effd85f40 100644 --- a/beat/web/reports/static/reports/app/app.js +++ b/beat/web/reports/static/reports/app/app.js @@ -23,13 +23,13 @@ angular.module('reportApp', ['ui.router', 'angular.filter', 'ui.sortable', 'ui.c angular.module('reportApp').config(function ($stateProvider, $urlRouterProvider){ - $urlRouterProvider - .otherwise('/'); + $urlRouterProvider + .otherwise('/'); - $stateProvider - .state('report', { - url: '/', - views: { - } - }); + $stateProvider + .state('report', { + url: '/', + views: { + } + }); }); diff --git a/beat/web/reports/static/reports/app/controllers/groupsController.js b/beat/web/reports/static/reports/app/controllers/groupsController.js index 0c9c91eb5..67f6fc414 100644 --- a/beat/web/reports/static/reports/app/controllers/groupsController.js +++ b/beat/web/reports/static/reports/app/controllers/groupsController.js @@ -22,37 +22,37 @@ /* * GroupsController - * provides access to the groups data to Django templates, - * used for handling the removal of experiments from the report + * provides access to the groups data to Django templates, + * used for handling the removal of experiments from the report */ angular.module('reportApp').controller('GroupsController', ['$http', 'UrlService', function ($http, UrlService){ - let vm = this; + let vm = this; - vm.expNamesToRemove = []; - vm.toggleExpName = (expName) => { - let idx = vm.expNamesToRemove.indexOf(expName); - if(idx > -1){ - vm.expNamesToRemove.splice(idx, 1); - } else { - vm.expNamesToRemove.push(expName); - } - }; + vm.expNamesToRemove = []; + vm.toggleExpName = (expName) => { + let idx = vm.expNamesToRemove.indexOf(expName); + if(idx > -1){ + vm.expNamesToRemove.splice(idx, 1); + } else { + vm.expNamesToRemove.push(expName); + } + }; - vm.removeExperiments = () => { - if(vm.expNamesToRemove.length === 0){ - return; - } + vm.removeExperiments = () => { + if(vm.expNamesToRemove.length === 0){ + return; + } - let url = UrlService.getRemoveExperimentUrl(); - return $http({ - headers: {'Content-Type': 'application/json'}, - url, - method: "POST", - data: { - experiments: [...vm.expNamesToRemove] - } - }) - .then(res => location.reload()) - ; - }; + let url = UrlService.getRemoveExperimentUrl(); + return $http({ + headers: {'Content-Type': 'application/json'}, + url, + method: "POST", + data: { + experiments: [...vm.expNamesToRemove] + } + }) + .then(res => location.reload()) + ; + }; }]); diff --git a/beat/web/reports/static/reports/app/controllers/reportController.js b/beat/web/reports/static/reports/app/controllers/reportController.js index 64b783d7b..e6c611838 100644 --- a/beat/web/reports/static/reports/app/controllers/reportController.js +++ b/beat/web/reports/static/reports/app/controllers/reportController.js @@ -21,7 +21,7 @@ */ /* reportController - * bootstraps the reports angular code by requiring the ReportService. - * NOTE: DONT add any code to this controller. + * bootstraps the reports angular code by requiring the ReportService. + * NOTE: DONT add any code to this controller. */ angular.module('reportApp').controller('reportController',['ReportService', function (ReportService){}]); diff --git a/beat/web/reports/static/reports/app/directives/bootstrapModal.js b/beat/web/reports/static/reports/app/directives/bootstrapModal.js index 3a60c59e9..05d1ca379 100644 --- a/beat/web/reports/static/reports/app/directives/bootstrapModal.js +++ b/beat/web/reports/static/reports/app/directives/bootstrapModal.js @@ -23,47 +23,47 @@ /* * bootstrapModal * Desc: - * represents a modal from Bootstrap 3 + * represents a modal from Bootstrap 3 */ angular.module('reportApp') .directive("bootstrapModal", [function(){ - return { - scope: { - domId: '@', - buttonCancelFunc: '&', - buttonSubmitFunc: '&', - buttonCancelText: '@', - buttonSubmitText: '@' - }, - link: function(scope){ - }, - transclude: { - 'title': '?bTitle', - 'content': '?bContent', - 'footer': '?bFooter' - }, - template: ` + return { + scope: { + domId: '@', + buttonCancelFunc: '&', + buttonSubmitFunc: '&', + buttonCancelText: '@', + buttonSubmitText: '@' + }, + link: function(scope){ + }, + transclude: { + 'title': '?bTitle', + 'content': '?bContent', + 'footer': '?bFooter' + }, + template: ` <div class="modal fade" id="{{ domId }}" tabindex="-1" role="dialog" aria-labelledby="reportModalLabel"> - <div class="modal-dialog" role="document"> - <div class="modal-content"> - <div class="modal-header"> - <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button> - <h4 class="modal-title" id="reportModalLabel" ng-transclude='title'>Modal Title</h4> - </div> - <div class="modal-body" ng-transclude='content'> - Content - </div> - <div class="modal-footer" ng-transclude='footer'> - <button ng-click='buttonCancelFunc && buttonCancelFunc()()' type="button" class="btn btn-default" data-dismiss="modal"> - Cancel - </button> - <button ng-if='buttonSubmitText' ng-click='buttonSubmitFunc && buttonSubmitFunc()()' type="button" class="btn btn-primary" data-dismiss="modal"> - {{ buttonSubmitText }} - </button> - </div> - </div> - </div> + <div class="modal-dialog" role="document"> + <div class="modal-content"> + <div class="modal-header"> + <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button> + <h4 class="modal-title" id="reportModalLabel" ng-transclude='title'>Modal Title</h4> + </div> + <div class="modal-body" ng-transclude='content'> + Content + </div> + <div class="modal-footer" ng-transclude='footer'> + <button ng-click='buttonCancelFunc && buttonCancelFunc()()' type="button" class="btn btn-default" data-dismiss="modal"> + Cancel + </button> + <button ng-if='buttonSubmitText' ng-click='buttonSubmitFunc && buttonSubmitFunc()()' type="button" class="btn btn-primary" data-dismiss="modal"> + {{ buttonSubmitText }} + </button> + </div> + </div> + </div> </div> ` - }; + }; }]); diff --git a/beat/web/reports/static/reports/app/directives/downloadLink.js b/beat/web/reports/static/reports/app/directives/downloadLink.js index 74733f12a..78058d1b2 100644 --- a/beat/web/reports/static/reports/app/directives/downloadLink.js +++ b/beat/web/reports/static/reports/app/directives/downloadLink.js @@ -23,53 +23,53 @@ /* * downloadLink * Desc: - * A button to download the content of the report item in the - * chosen file format (PNG, JPEG, PDF) + * A button to download the content of the report item in the + * chosen file format (PNG, JPEG, PDF) */ angular.module('reportApp') .directive("downloadLink", ['PlotService', function(PlotService){ - return { - scope: { - domId: '@', - group: '=', - itemId: '=' - }, - link: function(scope){ - scope.filetypes = [ - 'PNG', - 'JPEG', - 'PDF' - ]; + return { + scope: { + domId: '@', + group: '=', + itemId: '=' + }, + link: function(scope){ + scope.filetypes = [ + 'PNG', + 'JPEG', + 'PDF' + ]; - // download the img via the invisible link element - scope.downloadImgs = (e, ftype) => { - // get plot data URL - PlotService.downloadPlot(scope.group, scope.itemId, ftype) - .then(data => { - e.preventDefault(); - // invisible el, see end of template - const a = document.querySelector(`#${scope.domId}-download`); + // download the img via the invisible link element + scope.downloadImgs = (e, ftype) => { + // get plot data URL + PlotService.downloadPlot(scope.group, scope.itemId, ftype) + .then(data => { + e.preventDefault(); + // invisible el, see end of template + const a = document.querySelector(`#${scope.domId}-download`); - a.href = data; - a.download = `${scope.domId}-${scope.itemId}.${ftype.toLowerCase()}`; - a.click(); - a.href = ''; - a.download = ''; - }) - ; - }; - }, - template: ` + a.href = data; + a.download = `${scope.domId}-${scope.itemId}.${ftype.toLowerCase()}`; + a.click(); + a.href = ''; + a.download = ''; + }) + ; + }; + }, + template: ` <div class='btn-group'> - <button type='button' class='btn btn-primary dropdown-toggle' data-toggle='dropdown' aria-haspopup='true' aria-expanded='false'> - Download - <span class="caret"></span> - </button> - <ul class="dropdown-menu"> - <li><a ng-repeat='t in filetypes' ng-click='downloadImgs($event, t)'>{{ t }}</a></li> - </ul> + <button type='button' class='btn btn-primary dropdown-toggle' data-toggle='dropdown' aria-haspopup='true' aria-expanded='false'> + Download + <span class="caret"></span> + </button> + <ul class="dropdown-menu"> + <li><a ng-repeat='t in filetypes' ng-click='downloadImgs($event, t)'>{{ t }}</a></li> + </ul> </div> <a id='{{ domId }}-download'></a> ` - }; + }; }]); diff --git a/beat/web/reports/static/reports/app/directives/dragHandle.js b/beat/web/reports/static/reports/app/directives/dragHandle.js index 4eb0fce36..a954ce0d2 100644 --- a/beat/web/reports/static/reports/app/directives/dragHandle.js +++ b/beat/web/reports/static/reports/app/directives/dragHandle.js @@ -23,24 +23,24 @@ /* * dragHandle * Desc: - * displays the drag handle button, and adds the specified element class - * to the parent el + * displays the drag handle button, and adds the specified element class + * to the parent el */ angular.module('reportApp') .directive("dragHandle", [function(){ - return { - scope: { - handleHelperClass: '@' - }, - link: function(scope, el){ - el.addClass(`${scope.handleHelperClass} btn-group`); - }, - template: ` + return { + scope: { + handleHelperClass: '@' + }, + link: function(scope, el){ + el.addClass(`${scope.handleHelperClass} btn-group`); + }, + template: ` <span - class='btn btn-default drag-handle' - data-toggle='tooltip' data-placement='top' title='Drag to re-order'> - <i class='fa fa-arrows fa-lg'></i> + class='btn btn-default drag-handle' + data-toggle='tooltip' data-placement='top' title='Drag to re-order'> + <i class='fa fa-arrows fa-lg'></i> </span> ` - }; + }; }]); diff --git a/beat/web/reports/static/reports/app/directives/edit/addGroupMenu.js b/beat/web/reports/static/reports/app/directives/edit/addGroupMenu.js index dcefe7f83..9df054655 100644 --- a/beat/web/reports/static/reports/app/directives/edit/addGroupMenu.js +++ b/beat/web/reports/static/reports/app/directives/edit/addGroupMenu.js @@ -23,48 +23,48 @@ /* * groupAddGroupMenu * Desc: - * the menu & validating for creating a new group + * the menu & validating for creating a new group */ angular.module('reportApp') .directive("groupAddGroupMenu", ['GroupsService', function(GroupsService){ - return { - scope: { - }, - link: function(scope){ - scope.newGroupName = { val: '' }; - // validates the user input - scope.hasError = (val) => { - // if val is undefined, empty, or a dup, its an err - const isErr = !val || val.length === 0 || GroupsService.groups.find(g => g.name === val); - // cast to boolean - return !!isErr; - }; + return { + scope: { + }, + link: function(scope){ + scope.newGroupName = { val: '' }; + // validates the user input + scope.hasError = (val) => { + // if val is undefined, empty, or a dup, its an err + const isErr = !val || val.length === 0 || GroupsService.groups.find(g => g.name === val); + // cast to boolean + return !!isErr; + }; - // creates a new group if the new group name is valid - // wipes the input on successful creation - scope.createGroup = () => { - if(scope.hasError(scope.newGroupName.val)){ - return; - } + // creates a new group if the new group name is valid + // wipes the input on successful creation + scope.createGroup = () => { + if(scope.hasError(scope.newGroupName.val)){ + return; + } - GroupsService.createGroup(scope.newGroupName.val); - scope.newGroupName.val = ''; - }; - }, - template: ` + GroupsService.createGroup(scope.newGroupName.val); + scope.newGroupName.val = ''; + }; + }, + template: ` <form ng-submit='createGroup()'> - <div class='form-group' ng-class="{'has-error': hasError(newGroupName.val)}"> - <div class="input-group"> - <span class="input-group-btn"> - <button ng-click='createGroup()' class="btn btn-default" type="button"> - <i class="fa fa-plus" aria-hidden="true"></i> - </button> - </span> - <input required id='createNewGroupInput' ng-model='newGroupName.val' type="text" class="form-control" placeholder="New group name..."> - </div> - </div> + <div class='form-group' ng-class="{'has-error': hasError(newGroupName.val)}"> + <div class="input-group"> + <span class="input-group-btn"> + <button ng-click='createGroup()' class="btn btn-default" type="button"> + <i class="fa fa-plus" aria-hidden="true"></i> + </button> + </span> + <input required id='createNewGroupInput' ng-model='newGroupName.val' type="text" class="form-control" placeholder="New group name..."> + </div> + </div> </form> ` - }; + }; }]); diff --git a/beat/web/reports/static/reports/app/directives/edit/addItemsMenu.js b/beat/web/reports/static/reports/app/directives/edit/addItemsMenu.js index a2c9a11be..d9ea7e3e6 100644 --- a/beat/web/reports/static/reports/app/directives/edit/addItemsMenu.js +++ b/beat/web/reports/static/reports/app/directives/edit/addItemsMenu.js @@ -23,129 +23,129 @@ /* * groupAddItemsMenu * Desc: - * the button group for adding report items (plot, table, text) to a group + * the button group for adding report items (plot, table, text) to a group */ angular.module('reportApp') .directive("groupAddItemsMenu", ['ExperimentsService', 'GroupsService', 'PlotService', function(ExperimentsService, GroupsService, PlotService){ - return { - scope: { - group: '=' - }, - link: function(scope){ - // finds the id for the next report item of - // the given type - // by looking at the existing items - const getNextItemId = (type) => { - const formatId = (type, count) => `${type}_${count}`; - let currCount = 0; - let nextId = formatId(type, currCount); - while(scope.group.reportItems.find(i => i.id === nextId)){ - currCount++; - nextId = formatId(type, currCount); - } + return { + scope: { + group: '=' + }, + link: function(scope){ + // finds the id for the next report item of + // the given type + // by looking at the existing items + const getNextItemId = (type) => { + const formatId = (type, count) => `${type}_${count}`; + let currCount = 0; + let nextId = formatId(type, currCount); + while(scope.group.reportItems.find(i => i.id === nextId)){ + currCount++; + nextId = formatId(type, currCount); + } - return nextId; - }; + return nextId; + }; - scope.plottables = ExperimentsService.plottables; + scope.plottables = ExperimentsService.plottables; - // helper func for adding a table - scope.addNewTable = () => { - const id = getNextItemId('table'); + // helper func for adding a table + scope.addNewTable = () => { + const id = getNextItemId('table'); - // default fields are fields that are meant for tables - // that have the 'primary' property on the field value set to true - const defaultFieldsSet = Object.entries(ExperimentsService.tableables) - // only exps in group - .filter(([expName, o]) => scope.group.experiments.includes(expName)) - .map(([expName, o]) => Object.entries(o)) - .reduce((a, fEntries) => [...a, ...fEntries], []) - // only fields with primary == true - .filter(([fName, o]) => o.primary) - .map(([fName, o]) => fName) - .reduce((s, fName) => s.add(fName), new Set()) - ; + // default fields are fields that are meant for tables + // that have the 'primary' property on the field value set to true + const defaultFieldsSet = Object.entries(ExperimentsService.tableables) + // only exps in group + .filter(([expName, o]) => scope.group.experiments.includes(expName)) + .map(([expName, o]) => Object.entries(o)) + .reduce((a, fEntries) => [...a, ...fEntries], []) + // only fields with primary == true + .filter(([fName, o]) => o.primary) + .map(([fName, o]) => fName) + .reduce((s, fName) => s.add(fName), new Set()) + ; - const defaultFields = Array.from(defaultFieldsSet); + const defaultFields = Array.from(defaultFieldsSet); - // tables have an arr of selected fields - // and a float precision - let content = { - itemName: `Table`, - fields: defaultFields, - precision: 10 - }; + // tables have an arr of selected fields + // and a float precision + let content = { + itemName: `Table`, + fields: defaultFields, + precision: 10 + }; - scope.group.addReportItem(id, content); - }; + scope.group.addReportItem(id, content); + }; - // helper func for adding a plot - scope.addNewPlot = (plot) => { - let id = getNextItemId('plot'); + // helper func for adding a plot + scope.addNewPlot = (plot) => { + let id = getNextItemId('plot'); - // plots have a given name (by analyzer) - // and a plot type - let content = { - itemName: `${plot.label}`, - name: plot.label, - type: plot.type, - merged: true, - savedPlotter: '', - savedConfig: '' - }; + // plots have a given name (by analyzer) + // and a plot type + let content = { + itemName: `${plot.label}`, + name: plot.label, + type: plot.type, + merged: true, + savedPlotter: '', + savedConfig: '' + }; - content.savedPlotter = PlotService.getPlotter(content).name; - content.savedConfig = PlotService.getPlotterConfig(content).name; + content.savedPlotter = PlotService.getPlotter(content).name; + content.savedConfig = PlotService.getPlotterConfig(content).name; - scope.group.addReportItem(id, content); - }; + scope.group.addReportItem(id, content); + }; - // helper func for adding a text block - scope.addNewText = () => { - let id = getNextItemId('text'); - // text blocks just have raw RST - let content = { - itemName: 'Text', - text: '' - }; + // helper func for adding a text block + scope.addNewText = () => { + let id = getNextItemId('text'); + // text blocks just have raw RST + let content = { + itemName: 'Text', + text: '' + }; - scope.group.addReportItem(id, content); - }; - }, - template: ` + scope.group.addReportItem(id, content); + }; + }, + template: ` <div class="btn-group" role="group"> - <button - ng-disabled='group.experiments.length == 0' - type="button" - class="btn btn-default dropdown-toggle" - data-toggle="dropdown" - aria-haspopup="true" - aria-expanded="false"> - Add Plot - <span class="caret"></span> - </button> - <ul class='dropdown-menu' ng-repeat='(expName, plots) in plottables' ng-if='expName === group.experiments[0]'> - <li ng-repeat='plot in plots'> - <a ng-click='addNewPlot(plot)'>{{ plot.label }} <i>({{ plot.type }})</i></a> - </li> - </ul> + <button + ng-disabled='group.experiments.length == 0' + type="button" + class="btn btn-default dropdown-toggle" + data-toggle="dropdown" + aria-haspopup="true" + aria-expanded="false"> + Add Plot + <span class="caret"></span> + </button> + <ul class='dropdown-menu' ng-repeat='(expName, plots) in plottables' ng-if='expName === group.experiments[0]'> + <li ng-repeat='plot in plots'> + <a ng-click='addNewPlot(plot)'>{{ plot.label }} <i>({{ plot.type }})</i></a> + </li> + </ul> </div> <button - ng-disabled='group.experiments.length == 0' - class='btn btn-default' - ng-click='addNewTable()'> - Add Table + ng-disabled='group.experiments.length == 0' + class='btn btn-default' + ng-click='addNewTable()'> + Add Table </button> <div class="btn-group" role="group"> - <button - ng-disabled='group.experiments.length == 0' - ng-click='addNewText()' - type="button" - class="btn btn-default" - > - Add Text Block - </button> + <button + ng-disabled='group.experiments.length == 0' + ng-click='addNewText()' + type="button" + class="btn btn-default" + > + Add Text Block + </button> </div> ` - }; + }; }]); diff --git a/beat/web/reports/static/reports/app/directives/edit/tableFieldSelector.js b/beat/web/reports/static/reports/app/directives/edit/tableFieldSelector.js index e49a45f81..e621ce683 100644 --- a/beat/web/reports/static/reports/app/directives/edit/tableFieldSelector.js +++ b/beat/web/reports/static/reports/app/directives/edit/tableFieldSelector.js @@ -23,177 +23,177 @@ /* * GroupTableFieldSelector * Desc: - * Handles the choosing of table columns + * Handles the choosing of table columns */ angular.module('reportApp') .directive("groupTableFieldSelector", ['GroupsService', 'ExperimentsService', function(GroupsService, ExperimentsService){ - return { - scope: { - id: '=', - group: '=', - // currently selected columns for the table - colsSelected: '=', - // function to execute when the user clicks the submit button - buttonAction: '&', - // title for the menu - title: '@', - // text for the submit button - buttonText: '@' - }, - link: function(scope){ - // bootstrap's auto dropdown toggling is disabled for the table creation dropdown - // add a click handler for the table creation dropdown submit button to toggle - // manually - scope.clickButton = (e) => { - $(`#${scope.id}`).dropdown('toggle'); - scope.buttonAction()(); - }; + return { + scope: { + id: '=', + group: '=', + // currently selected columns for the table + colsSelected: '=', + // function to execute when the user clicks the submit button + buttonAction: '&', + // title for the menu + title: '@', + // text for the submit button + buttonText: '@' + }, + link: function(scope){ + // bootstrap's auto dropdown toggling is disabled for the table creation dropdown + // add a click handler for the table creation dropdown submit button to toggle + // manually + scope.clickButton = (e) => { + $(`#${scope.id}`).dropdown('toggle'); + scope.buttonAction()(); + }; - scope.tableables = () => { - // start with the tableables generated in experimentsservice - const tableables = ExperimentsService.tableables; - const fieldArr = Object.entries(tableables) - // only look at fields that are from an experiment in the group - .filter(([e, fields]) => scope.group.experiments.includes(e)) - // get the names of the fields - .map(([e, fields]) => Object.keys(fields)) - // make one big array of all field names - .reduce((arr, fArr) => [...arr, ...fArr], []) - ; + scope.tableables = () => { + // start with the tableables generated in experimentsservice + const tableables = ExperimentsService.tableables; + const fieldArr = Object.entries(tableables) + // only look at fields that are from an experiment in the group + .filter(([e, fields]) => scope.group.experiments.includes(e)) + // get the names of the fields + .map(([e, fields]) => Object.keys(fields)) + // make one big array of all field names + .reduce((arr, fArr) => [...arr, ...fArr], []) + ; - // converting to and from a Set is a simple way of - // removing dups - const arr = Array.from(new Set(fieldArr)); + // converting to and from a Set is a simple way of + // removing dups + const arr = Array.from(new Set(fieldArr)); - return arr; - }; + return arr; + }; - // has this fieldName already been processed? - // need to look at the already-processed field names - scope.isUniqueTableable = (expName, fieldName) => { - const tableables = ExperimentsService.tableables; - const concatNames = (eName, fName) => `${eName}.${fName}`; + // has this fieldName already been processed? + // need to look at the already-processed field names + scope.isUniqueTableable = (expName, fieldName) => { + const tableables = ExperimentsService.tableables; + const concatNames = (eName, fName) => `${eName}.${fName}`; - // see if this field is a repeat - const isRepeat = Object.entries(tableables) - .filter(([e, fields]) => { - // only look at tableables of exps that are in group - let isInGroup = scope.group.experiments.includes(e); - // and have already been looked at - let alreadyChecked = Object.keys(tableables).indexOf(e) < Object.keys(tableables).indexOf(expName); - return isInGroup && alreadyChecked; - }) - // get field names - .map(([e, fields]) => Object.keys(fields)) - // flatten - .reduce((arr, fArr) => [...arr, ...fArr], []) - // does this flattened array have this field name in it? - .includes(fieldName) - ; + // see if this field is a repeat + const isRepeat = Object.entries(tableables) + .filter(([e, fields]) => { + // only look at tableables of exps that are in group + let isInGroup = scope.group.experiments.includes(e); + // and have already been looked at + let alreadyChecked = Object.keys(tableables).indexOf(e) < Object.keys(tableables).indexOf(expName); + return isInGroup && alreadyChecked; + }) + // get field names + .map(([e, fields]) => Object.keys(fields)) + // flatten + .reduce((arr, fArr) => [...arr, ...fArr], []) + // does this flattened array have this field name in it? + .includes(fieldName) + ; - // if it isnt a repeat, its unique! - return !isRepeat; - }; + // if it isnt a repeat, its unique! + return !isRepeat; + }; - // many tableable fields are fields from an analyzer, a block, or something else - // these fields have a field group name, a '.', and an actual field name - // find these group names and use them to subdivide the list of fields in the menu - scope.tableablesGroups = () => { - let groupNames = scope.tableables() - .filter(f => f.includes('.')) - .map(f => f.split('.')[0]); + // many tableable fields are fields from an analyzer, a block, or something else + // these fields have a field group name, a '.', and an actual field name + // find these group names and use them to subdivide the list of fields in the menu + scope.tableablesGroups = () => { + let groupNames = scope.tableables() + .filter(f => f.includes('.')) + .map(f => f.split('.')[0]); - const sorted = Array.from(new Set(groupNames)) - .sort((a, b) => groupNames.indexOf(a) - groupNames.indexOf(b)) + const sorted = Array.from(new Set(groupNames)) + .sort((a, b) => groupNames.indexOf(a) - groupNames.indexOf(b)) - return sorted; - }; + return sorted; + }; - // finds the actual field name whether its in a field group or not - scope.groupName = (field) => field.includes('.') ? field.split('.')[0] : field; + // finds the actual field name whether its in a field group or not + scope.groupName = (field) => field.includes('.') ? field.split('.')[0] : field; - // finds the actual field name whether its in a field group or not - scope.subfieldName = (field) => field.includes('.') ? field.split('.').slice(1).join('.') : field; + // finds the actual field name whether its in a field group or not + scope.subfieldName = (field) => field.includes('.') ? field.split('.').slice(1).join('.') : field; - scope.shouldShowField = (fName, gName) => { - const isUnique = scope.isUniqueTableable(fName); - const isInGroup = scope.groupName(fName) == gName; + scope.shouldShowField = (fName, gName) => { + const isUnique = scope.isUniqueTableable(fName); + const isInGroup = scope.groupName(fName) == gName; - return isUnique && isInGroup; - } + return isUnique && isInGroup; + } - // toggle the selection of a field - scope.toggleField = (fName) => { - let idx = scope.colsSelected.indexOf(fName); - if(idx > -1){ - scope.colsSelected.splice(idx, 1); - } else { - scope.colsSelected.push(fName); - scope.colsSelected.sort((a, b) => scope.tableables().indexOf(a) - scope.tableables().indexOf(b)); - } - }; - }, - template: ` + // toggle the selection of a field + scope.toggleField = (fName) => { + let idx = scope.colsSelected.indexOf(fName); + if(idx > -1){ + scope.colsSelected.splice(idx, 1); + } else { + scope.colsSelected.push(fName); + scope.colsSelected.sort((a, b) => scope.tableables().indexOf(a) - scope.tableables().indexOf(b)); + } + }; + }, + template: ` <button id='{{ id }}' type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> - {{ title }} - <span class="caret"></span> + {{ title }} + <span class="caret"></span> </button> <div class='dropdown-menu' ng-click="$event.stopPropagation();" style='width:300px;'> - <h4>Select columns to show in Table</h4> - <form style='max-height: 200px; overflow-y: scroll;'> - <fieldset> - <h4>General</h4> - <div - ng-repeat='fName in tableables()' - ng-if="isUniqueTableable(fName) && !fName.includes('.')"> - <label> - <input - type='checkbox' - value='{{ fName }}' - ng-checked='colsSelected.includes(fName)' - ng-click='toggleField(fName)'> - {{ fName }} - </label> - </div> - </fieldset> - <fieldset ng-repeat='gName in tableablesGroups()'> - <h4>{{ gName }}</h4> - <div - ng-repeat='fName in tableables()' - ng-if='shouldShowField(fName, gName)'> - <label> - <input - type='checkbox' - value='{{ fName }}' - ng-checked='colsSelected.includes(fName)' - ng-click='toggleField(fName)'> - {{ subfieldName(fName) }} - </label> - </div> - </fieldset> - </form> - <!-- - <select multiple ng-model='colsSelected'> - <optgroup label='General'> - <option - ng-repeat='fName in tableables()' - ng-if="isUniqueTableable(fName) && !fName.includes('.')" - value='{{ fName }}'> - {{ fName }} - </option> - </optgroup> - <optgroup ng-repeat='gName in tableablesGroups()' label='{{ gName }}'> - <option - ng-repeat='fName in tableables()' - ng-if='isUniqueTableable(fName) && fName.startsWith(gName)' - value='{{ fName }}'> - {{ subfieldName(fName) }} - </option> - </optgroup> - </select> - !--> - <button class='btn btn-default' ng-click='clickButton($event)'>{{ buttonText }}</button> + <h4>Select columns to show in Table</h4> + <form style='max-height: 200px; overflow-y: scroll;'> + <fieldset> + <h4>General</h4> + <div + ng-repeat='fName in tableables()' + ng-if="isUniqueTableable(fName) && !fName.includes('.')"> + <label> + <input + type='checkbox' + value='{{ fName }}' + ng-checked='colsSelected.includes(fName)' + ng-click='toggleField(fName)'> + {{ fName }} + </label> + </div> + </fieldset> + <fieldset ng-repeat='gName in tableablesGroups()'> + <h4>{{ gName }}</h4> + <div + ng-repeat='fName in tableables()' + ng-if='shouldShowField(fName, gName)'> + <label> + <input + type='checkbox' + value='{{ fName }}' + ng-checked='colsSelected.includes(fName)' + ng-click='toggleField(fName)'> + {{ subfieldName(fName) }} + </label> + </div> + </fieldset> + </form> + <!-- + <select multiple ng-model='colsSelected'> + <optgroup label='General'> + <option + ng-repeat='fName in tableables()' + ng-if="isUniqueTableable(fName) && !fName.includes('.')" + value='{{ fName }}'> + {{ fName }} + </option> + </optgroup> + <optgroup ng-repeat='gName in tableablesGroups()' label='{{ gName }}'> + <option + ng-repeat='fName in tableables()' + ng-if='isUniqueTableable(fName) && fName.startsWith(gName)' + value='{{ fName }}'> + {{ subfieldName(fName) }} + </option> + </optgroup> + </select> + !--> + <button class='btn btn-default' ng-click='clickButton($event)'>{{ buttonText }}</button> </div> ` - } + } }]); diff --git a/beat/web/reports/static/reports/app/directives/editableLabel.js b/beat/web/reports/static/reports/app/directives/editableLabel.js index 01f9eecd3..dbc5b9ccd 100644 --- a/beat/web/reports/static/reports/app/directives/editableLabel.js +++ b/beat/web/reports/static/reports/app/directives/editableLabel.js @@ -23,30 +23,30 @@ /* * editableLabel * Desc: - * represents an editable label (group name, report item name, etc.) + * represents an editable label (group name, report item name, etc.) */ angular.module('reportApp').directive("editableLabel", ['UrlService', function(UrlService){ - return { - scope: { - obj: '=', - field: '@' - }, - link: function(scope){ - scope.isViewmode = UrlService.isViewmode; - }, - template: ` + return { + scope: { + obj: '=', + field: '@' + }, + link: function(scope){ + scope.isViewmode = UrlService.isViewmode; + }, + template: ` <span style='display: inline-block;'> - <span ng-if='isViewmode()'>{{ obj[field] }}</span> - <input - ng-if='!isViewmode()' - style='display: inline;' - required - type='text' - class='form-control' - placeholder='A label...' - ng-model='obj[field]'/> + <span ng-if='isViewmode()'>{{ obj[field] }}</span> + <input + ng-if='!isViewmode()' + style='display: inline;' + required + type='text' + class='form-control' + placeholder='A label...' + ng-model='obj[field]'/> </span> ` - }; + }; }]); diff --git a/beat/web/reports/static/reports/app/directives/error.js b/beat/web/reports/static/reports/app/directives/error.js index a8759663e..1a1116e15 100644 --- a/beat/web/reports/static/reports/app/directives/error.js +++ b/beat/web/reports/static/reports/app/directives/error.js @@ -23,66 +23,66 @@ /* * reportError * Desc: - * Watches the ErrorService's error list and processes an error if - * its not empty. - * 'Processing': showing a modal to the user about the error. + * Watches the ErrorService's error list and processes an error if + * its not empty. + * 'Processing': showing a modal to the user about the error. * */ angular.module('reportApp') .directive("reportError", ['ErrorService', '$timeout', function(ErrorService, $timeout){ - return { - scope: { - }, - restrict: 'E', - link: function(scope){ - scope.currError = { - message: '', - error: '' - }; + return { + scope: { + }, + restrict: 'E', + link: function(scope){ + scope.currError = { + message: '', + error: '' + }; - const errors = []; + const errors = []; - const processError = () => { - const modalOpen = ($("#errorReportModal").data('bs.modal') || {}).isShown; - if(modalOpen || errors.length === 0){ - return; - } + const processError = () => { + const modalOpen = ($("#errorReportModal").data('bs.modal') || {}).isShown; + if(modalOpen || errors.length === 0){ + return; + } - // save our error - const e = errors.shift(); - scope.currError.message = e.message; - scope.currError.error = e.error; - scope.$apply(); + // save our error + const e = errors.shift(); + scope.currError.message = e.message; + scope.currError.error = e.error; + scope.$apply(); - // pop up the modal - $('#errorReportModal').modal(); - }; + // pop up the modal + $('#errorReportModal').modal(); + }; - $timeout(() => { - $('#errorReportModal').on('hidden.bs.modal', () => { - // finished processing the last error - // process the next one - processError(); - }); - }, 0); + $timeout(() => { + $('#errorReportModal').on('hidden.bs.modal', () => { + // finished processing the last error + // process the next one + processError(); + }); + }, 0); - scope.$on('user:error', (event, error) => { - errors.push(error); - processError(); - }); - }, - template: ` + scope.$on('user:error', (event, error) => { + errors.push(error); + processError(); + }); + }, + template: ` <bootstrap-modal dom-id='errorReportModal' button-cancel-text='Continue'> - <b-title> - Error - </b-title> - <b-content> - <p>There was an error:</p> - <p>{{ currError.message }}</p> - <small>Details:</small> - <pre>{{ currError.error }}</pre> - </b-content> + <b-title> + Error + </b-title> + <b-content> + <p>There was an error:</p> + <p>{{ currError.message }}</p> + <small>Details:</small> + <pre>{{ currError.error }}</pre> + </b-content> </bootstrap-modal> ` - }; + }; }]); diff --git a/beat/web/reports/static/reports/app/directives/experimentsTable.js b/beat/web/reports/static/reports/app/directives/experimentsTable.js index 7ff2edbfb..9b9649392 100644 --- a/beat/web/reports/static/reports/app/directives/experimentsTable.js +++ b/beat/web/reports/static/reports/app/directives/experimentsTable.js @@ -23,116 +23,116 @@ /* * experimentsTable * Desc: - * displays the report's experiments table - * - Experiment statuses - * - Name - * - Databases/Protocols - * - Analyzer + * displays the report's experiments table + * - Experiment statuses + * - Name + * - Databases/Protocols + * - Analyzer * - * also lets the user remove experiments from the report. + * also lets the user remove experiments from the report. */ angular.module('reportApp') .directive("experimentsTable", ['GroupsService', 'ExperimentsService', 'UrlService', 'ReportService', function(GroupsService, ExperimentsService, UrlService, ReportService){ - return { - scope: { - }, - link: function(scope){ - scope.ReportService = ReportService; - scope.domId = `experiments-table`; - scope.getAnalyzerFromExpName = ExperimentsService.getAnalyzerFromExpName; - scope.getExpUrl = UrlService.getExperimentUrl; - scope.getBlockUrl = UrlService.getBlockUrl; - scope.getDatabaseUrl = UrlService.getDatabaseUrl; - scope.getExperimentListPath = UrlService.getExperimentListPath; - scope.isViewmode = UrlService.isViewmode; + return { + scope: { + }, + link: function(scope){ + scope.ReportService = ReportService; + scope.domId = `experiments-table`; + scope.getAnalyzerFromExpName = ExperimentsService.getAnalyzerFromExpName; + scope.getExpUrl = UrlService.getExperimentUrl; + scope.getBlockUrl = UrlService.getBlockUrl; + scope.getDatabaseUrl = UrlService.getDatabaseUrl; + scope.getExperimentListPath = UrlService.getExperimentListPath; + scope.isViewmode = UrlService.isViewmode; - scope.expNames = ExperimentsService.experimentNames; - scope.exps = ExperimentsService.experiments; + scope.expNames = ExperimentsService.experimentNames; + scope.exps = ExperimentsService.experiments; - scope.groups = GroupsService.groups; + scope.groups = GroupsService.groups; - // collects an array of formatted databases & protocols of an experiment - // format is "<database name>@<protocol name>" - scope.getExpDatabases = (expName) => { - let dbs = scope.exps[expName].declaration.datasets; - return Array.from(new Set(Object.values(dbs).map(db => `${db.database}@${db.protocol}`))); - }; + // collects an array of formatted databases & protocols of an experiment + // format is "<database name>@<protocol name>" + scope.getExpDatabases = (expName) => { + let dbs = scope.exps[expName].declaration.datasets; + return Array.from(new Set(Object.values(dbs).map(db => `${db.database}@${db.protocol}`))); + }; - scope.deleteExpFromReport = (expName) => { - ExperimentsService.deleteExperiment(expName); - }; - }, - template: ` + scope.deleteExpFromReport = (expName) => { + ExperimentsService.deleteExperiment(expName); + }; + }, + template: ` <div id='{{ domId }}' class='panel panel-default'> - <div id='{{ domId }}-heading' class="panel-heading" role="tab"> - <h4 class="panel-title"> - <a - class='' - role="button" - data-toggle="collapse" - data-parent="#{{ domId }}-heading" - href="#collapse-{{ domId }}" - aria-expanded="true" - aria-controls="collapse-{{ domId }}"> - Experiments List - </a> - </h4> - </div> - <div id="collapse-{{ domId }}" - class="panel-collapse collapse in" - role="tabpanel" - aria-labelledby="{{ domId }}-heading"> - <table ng-if='expNames.length > 0' class="table table-striped table-hover"> - <thead> - <tr> - <th ng-if='!isViewmode()'></th> - <th ng-if='isViewmode() && groups.length == 1'>Alias</th> - <th>Experiment</th> - <th>Databases/Protocols</th> - <th>Analyzer</th> - </tr> - </thead> - <tbody> - <tr ng-repeat='expName in expNames'> - <td ng-if='!isViewmode()'> - <div class='btn-group action-buttons'> - <span - ng-click='deleteExpFromReport(expName)' - style='cursor: pointer;' - class="btn-delete" - data-toggle="tooltip" - data-placement="top" - title="Remove Experiment from Report"> - <i class="fa fa-times fa-lg"></i> - </span> - </div> - </td> - <td ng-if='isViewmode() && groups.length == 1'> - <span ng-if='groups[0].experiments.includes(expName)'> - {{ groups[0].aliases[expName] }} - </span> - </td> - <td><a href='{{ getExpUrl(expName) }}'>{{ expName }}</a></td> - <td> - <span ng-repeat='db in getExpDatabases(expName)'> - <a href='{{ getDatabaseUrl(db.split("@")[0]) }}'>{{ db }}</a> - - </span> - </td> - <td>{{ getAnalyzerFromExpName(expName) }}</td> - </tr> - </tbody> - </table> + <div id='{{ domId }}-heading' class="panel-heading" role="tab"> + <h4 class="panel-title"> + <a + class='' + role="button" + data-toggle="collapse" + data-parent="#{{ domId }}-heading" + href="#collapse-{{ domId }}" + aria-expanded="true" + aria-controls="collapse-{{ domId }}"> + Experiments List + </a> + </h4> + </div> + <div id="collapse-{{ domId }}" + class="panel-collapse collapse in" + role="tabpanel" + aria-labelledby="{{ domId }}-heading"> + <table ng-if='expNames.length > 0' class="table table-striped table-hover"> + <thead> + <tr> + <th ng-if='!isViewmode()'></th> + <th ng-if='isViewmode() && groups.length == 1'>Alias</th> + <th>Experiment</th> + <th>Databases/Protocols</th> + <th>Analyzer</th> + </tr> + </thead> + <tbody> + <tr ng-repeat='expName in expNames'> + <td ng-if='!isViewmode()'> + <div class='btn-group action-buttons'> + <span + ng-click='deleteExpFromReport(expName)' + style='cursor: pointer;' + class="btn-delete" + data-toggle="tooltip" + data-placement="top" + title="Remove Experiment from Report"> + <i class="fa fa-times fa-lg"></i> + </span> + </div> + </td> + <td ng-if='isViewmode() && groups.length == 1'> + <span ng-if='groups[0].experiments.includes(expName)'> + {{ groups[0].aliases[expName] }} + </span> + </td> + <td><a href='{{ getExpUrl(expName) }}'>{{ expName }}</a></td> + <td> + <span ng-repeat='db in getExpDatabases(expName)'> + <a href='{{ getDatabaseUrl(db.split("@")[0]) }}'>{{ db }}</a> + + </span> + </td> + <td>{{ getAnalyzerFromExpName(expName) }}</td> + </tr> + </tbody> + </table> - <div class='panel-body' ng-if='expNames.length == 0'> - <i class="fa fa-warning fa-lg"></i> - You have <strong>not added any experiments</strong> to this report yet. - You may add experiments from throughout the platform - (e.g. <a href="{{ getExperimentListPath() }}{{ ReportService.author }}/">your experiment list page</a>) - to unlock editing features for this report. - </div> - </div> + <div class='panel-body' ng-if='expNames.length == 0'> + <i class="fa fa-warning fa-lg"></i> + You have <strong>not added any experiments</strong> to this report yet. + You may add experiments from throughout the platform + (e.g. <a href="{{ getExperimentListPath() }}{{ ReportService.author }}/">your experiment list page</a>) + to unlock editing features for this report. + </div> + </div> </div> ` - }; + }; }]); diff --git a/beat/web/reports/static/reports/app/directives/layout.js b/beat/web/reports/static/reports/app/directives/layout.js index f32d9a527..27ae4d189 100644 --- a/beat/web/reports/static/reports/app/directives/layout.js +++ b/beat/web/reports/static/reports/app/directives/layout.js @@ -23,44 +23,44 @@ /* * groupsLayout * Desc: - * controls the layout of the reports content, - * generating group panels using the GroupsService data, - * and holding the menu for adding a group + * controls the layout of the reports content, + * generating group panels using the GroupsService data, + * and holding the menu for adding a group */ angular.module('reportApp').directive("groupsLayout", ['GroupsService', 'UrlService', function(GroupsService, UrlService){ - return { - scope: { - }, - link: function(scope, el, attr){ - scope.GroupsService = GroupsService; - scope.isViewmode = UrlService.isViewmode; + return { + scope: { + }, + link: function(scope, el, attr){ + scope.GroupsService = GroupsService; + scope.isViewmode = UrlService.isViewmode; - // drag handle CSS selector - scope.sortableOptions = { - handle: '.dragGroup .drag-handle' - }; - }, - template: ` + // drag handle CSS selector + scope.sortableOptions = { + handle: '.dragGroup .drag-handle' + }; + }, + template: ` <experiments-table></experiments-table> <div ng-if='!isViewmode()' group-add-group-menu class='panel'></div> <div id='groupsLayout' class='panel-group'> - <div ng-if='!isViewmode() || GroupsService.groups.length > 1' - ui-sortable='sortableOptions' - ng-model='GroupsService.groups' - id='groupsLayout' - class='panel-group'> - <div - group-panel-content - style='margin-bottom: 5px;' - ng-repeat='group in GroupsService.groups' - group='group'> - </div> - </div> - <div ng-if='isViewmode() && GroupsService.groups.length == 1'> - <group-panel-items group='GroupsService.groups[0]'></group-panel-items> - </div> + <div ng-if='!isViewmode() || GroupsService.groups.length > 1' + ui-sortable='sortableOptions' + ng-model='GroupsService.groups' + id='groupsLayout' + class='panel-group'> + <div + group-panel-content + style='margin-bottom: 5px;' + ng-repeat='group in GroupsService.groups' + group='group'> + </div> + </div> + <div ng-if='isViewmode() && GroupsService.groups.length == 1'> + <group-panel-items group='GroupsService.groups[0]'></group-panel-items> + </div> </div> ` - }; + }; }]); diff --git a/beat/web/reports/static/reports/app/directives/lock.js b/beat/web/reports/static/reports/app/directives/lock.js index ead9538a1..73b7f687c 100644 --- a/beat/web/reports/static/reports/app/directives/lock.js +++ b/beat/web/reports/static/reports/app/directives/lock.js @@ -23,37 +23,37 @@ /* * reportLock * Desc: - * Displays a modal for locking the current report. + * Displays a modal for locking the current report. */ angular.module('reportApp') .directive("reportLock", ['ReportService', 'ErrorService', function(ReportService, ErrorService){ - return { - scope: { - }, - restrict: 'E', - link: function(scope, el){ - // sends the request to lock the report - scope.lockReport = () => { - return ReportService.lockReport() - .then(() => { - window.location.reload(); - }) - .catch(e => { - ErrorService.logError(e, `Could not lock the report.`); - }); - } - }, - template: ` + return { + scope: { + }, + restrict: 'E', + link: function(scope, el){ + // sends the request to lock the report + scope.lockReport = () => { + return ReportService.lockReport() + .then(() => { + window.location.reload(); + }) + .catch(e => { + ErrorService.logError(e, `Could not lock the report.`); + }); + } + }, + template: ` <bootstrap-modal dom-id='lockReportModal' button-submit-text='Lock' button-submit-func='lockReport'> - <b-title> - Lock Report - </b-title> - <b-content> + <b-title> + Lock Report + </b-title> + <b-content> <p>Locking your report is the first step for publication.</p> <p>Your report will not be editable anymore.</p> <p>In order to do lock your report, your experiments will be locked as well, if they are not already (they will not be able to be edited or deleted).</p> - </b-content> + </b-content> </bootstrap-modal> ` - }; + }; }]); diff --git a/beat/web/reports/static/reports/app/directives/panelContainer.js b/beat/web/reports/static/reports/app/directives/panelContainer.js index e971fe661..7f4f843a2 100644 --- a/beat/web/reports/static/reports/app/directives/panelContainer.js +++ b/beat/web/reports/static/reports/app/directives/panelContainer.js @@ -23,46 +23,46 @@ /* * panelContainer * Desc: - * displays a Bootstrap panel with the specified header & body content + * displays a Bootstrap panel with the specified header & body content */ angular.module('reportApp') .directive("panelContainer", [function(){ - return { - scope: { - noPanelBody: '=', - domId: '=' - }, - restrict: 'A', - transclude: { - 'headerSlot': 'header', - 'contentSlot': 'content' - }, - link: function(scope, el){ - el.addClass('panel panel-default'); - }, - template: ` + return { + scope: { + noPanelBody: '=', + domId: '=' + }, + restrict: 'A', + transclude: { + 'headerSlot': 'header', + 'contentSlot': 'content' + }, + link: function(scope, el){ + el.addClass('panel panel-default'); + }, + template: ` <div id="{{domId}}-heading" class="panel-heading" role="tab"> - <h4 class="panel-title"> - <a - class='' - role="button" - data-toggle="collapse" - data-parent="#{{domId}}-heading" - href="#collapse-{{domId}}" - aria-expanded="true" - aria-controls="collapse-{{domId}}"> - </a> - <span ng-transclude='headerSlot'>Header Content</span> - </h4> + <h4 class="panel-title"> + <a + class='' + role="button" + data-toggle="collapse" + data-parent="#{{domId}}-heading" + href="#collapse-{{domId}}" + aria-expanded="true" + aria-controls="collapse-{{domId}}"> + </a> + <span ng-transclude='headerSlot'>Header Content</span> + </h4> </div> <div id="collapse-{{domId}}" - class="panel-collapse collapse in" - role="tabpanel" - aria-labelledby="{{domId}}-heading"> - <div ng-class='{ "panel-body": !noPanelBody }' ng-transclude='contentSlot'> - Body Content with hasPanelBody: {{ hasPanelBody }} - </div> + class="panel-collapse collapse in" + role="tabpanel" + aria-labelledby="{{domId}}-heading"> + <div ng-class='{ "panel-body": !noPanelBody }' ng-transclude='contentSlot'> + Body Content with hasPanelBody: {{ hasPanelBody }} + </div> </div> ` - }; + }; }]); diff --git a/beat/web/reports/static/reports/app/directives/panelContent.js b/beat/web/reports/static/reports/app/directives/panelContent.js index 9f6319b03..67e54aac7 100644 --- a/beat/web/reports/static/reports/app/directives/panelContent.js +++ b/beat/web/reports/static/reports/app/directives/panelContent.js @@ -23,61 +23,61 @@ /* * groupPanelContent * Desc: - * presents the group to the user in logical blocks: - * - a panel holds content - * - panel header contains an (editable) group name label - * - panel header also contains the buttons to add report items - * - two sub-panels are added: the experiments list, and the items list + * presents the group to the user in logical blocks: + * - a panel holds content + * - panel header contains an (editable) group name label + * - panel header also contains the buttons to add report items + * - two sub-panels are added: the experiments list, and the items list */ angular.module('reportApp').directive("groupPanelContent", ['GroupsService', 'UrlService', function(GroupsService, UrlService){ - return { - scope: { - group: '=' - }, - link: function(scope){ - scope.deleteGroup = GroupsService.deleteGroup; - scope.isViewmode = UrlService.isViewmode; - }, - template: ` + return { + scope: { + group: '=' + }, + link: function(scope){ + scope.deleteGroup = GroupsService.deleteGroup; + scope.isViewmode = UrlService.isViewmode; + }, + template: ` <div panel-container class='dragGroup' domId='group.name'> - <header> - <a - class='' - role="button" - data-toggle="collapse" - data-parent="#{{group.name}}-heading" - href="#collapse-{{group.name}}" - aria-expanded="true" - aria-controls="collapse-{{group.name}}"> - </a> - <editable-label obj='group' field='_name'></editable-label> - <div ng-if='!isViewmode()' class='btn-group action-buttons'> - <span - ng-click='deleteGroup(group.name)' - class="btn btn-default btn-delete" - data-toggle="tooltip" data-placement="top" title="Delete Group"> - <i class="fa fa-times fa-lg"></i> - </span> - <span drag-handle handle-helper-class='dragGroup'></span> - </div> - <div ng-if='!isViewmode()' - group-add-items-menu - class="btn-group" role="group" role='tab' - group='group'> - </div> - </header> - <content> - <div group-panel-experiments group='group' class='panel panel-default'></div> - <div - style='margin-top: 5px;' - ng-if='group.experiments.length > 0' - group-panel-items - group='group' - > - </div> - </content> + <header> + <a + class='' + role="button" + data-toggle="collapse" + data-parent="#{{group.name}}-heading" + href="#collapse-{{group.name}}" + aria-expanded="true" + aria-controls="collapse-{{group.name}}"> + </a> + <editable-label obj='group' field='_name'></editable-label> + <div ng-if='!isViewmode()' class='btn-group action-buttons'> + <span + ng-click='deleteGroup(group.name)' + class="btn btn-default btn-delete" + data-toggle="tooltip" data-placement="top" title="Delete Group"> + <i class="fa fa-times fa-lg"></i> + </span> + <span drag-handle handle-helper-class='dragGroup'></span> + </div> + <div ng-if='!isViewmode()' + group-add-items-menu + class="btn-group" role="group" role='tab' + group='group'> + </div> + </header> + <content> + <div group-panel-experiments group='group' class='panel panel-default'></div> + <div + style='margin-top: 5px;' + ng-if='group.experiments.length > 0' + group-panel-items + group='group' + > + </div> + </content> </div> ` - }; + }; }]); diff --git a/beat/web/reports/static/reports/app/directives/panelExperiments.js b/beat/web/reports/static/reports/app/directives/panelExperiments.js index 32326886e..5ed6d3e2d 100644 --- a/beat/web/reports/static/reports/app/directives/panelExperiments.js +++ b/beat/web/reports/static/reports/app/directives/panelExperiments.js @@ -23,132 +23,132 @@ /* * groupPanelExperiments * Desc: - * displays the experiments panel of the group - - * a table of experiments in the group, their databases/protocols, and aliases. - * Also has a menu for adding (compatible) experiments to the group. + * displays the experiments panel of the group - + * a table of experiments in the group, their databases/protocols, and aliases. + * Also has a menu for adding (compatible) experiments to the group. */ angular.module('reportApp').directive("groupPanelExperiments", ['GroupsService', 'ExperimentsService', 'UrlService', function(GroupsService, ExperimentsService, UrlService){ - return { - scope: { - group: '=' - }, - link: function(scope){ - scope.experiments = ExperimentsService.experiments; - scope.dropdownId = `${scope.group.name}_exp_add_dropdown`; + return { + scope: { + group: '=' + }, + link: function(scope){ + scope.experiments = ExperimentsService.experiments; + scope.dropdownId = `${scope.group.name}_exp_add_dropdown`; - scope.getExpName = (expName) => scope.experiments[expName] ? expName : expName.split('/').pop(); - const getExp = (expName) => scope.experiments[expName] || scope.experiments[expName.split('/').pop()]; + scope.getExpName = (expName) => scope.experiments[expName] ? expName : expName.split('/').pop(); + const getExp = (expName) => scope.experiments[expName] || scope.experiments[expName.split('/').pop()]; - // find experiments that are not in the group but are - // compatible with the existing experiments (if any) - scope.expsNotInGroup = () => { - return ExperimentsService.experimentNames - // exps not in group - .filter(e => !scope.group.experiments.includes(e)) - // exp has compatible analyzer - .filter(e => scope.group.analyzer === '' || ExperimentsService.getAnalyzerFromExpName(e) === scope.group.analyzer) - ; - }; + // find experiments that are not in the group but are + // compatible with the existing experiments (if any) + scope.expsNotInGroup = () => { + return ExperimentsService.experimentNames + // exps not in group + .filter(e => !scope.group.experiments.includes(e)) + // exp has compatible analyzer + .filter(e => scope.group.analyzer === '' || ExperimentsService.getAnalyzerFromExpName(e) === scope.group.analyzer) + ; + }; - // collects an array of formatted databases & protocols of an experiment - // format is "<database name>@<protocol name>" - scope.getExpDatabases = (expName) => { - const expObj = getExp(expName); - if(!expObj){ - return; - } + // collects an array of formatted databases & protocols of an experiment + // format is "<database name>@<protocol name>" + scope.getExpDatabases = (expName) => { + const expObj = getExp(expName); + if(!expObj){ + return; + } - let dbs = expObj.declaration.datasets; - return Array.from(new Set(Object.values(dbs).map(db => `${db.database}@${db.protocol}`))); - }; + let dbs = expObj.declaration.datasets; + return Array.from(new Set(Object.values(dbs).map(db => `${db.database}@${db.protocol}`))); + }; - scope.getAnalyzerFromExpName = ExperimentsService.getAnalyzerFromExpName; - scope.getExpUrl = UrlService.getExperimentUrl; - scope.getBlockUrl = UrlService.getBlockUrl; - scope.getDatabaseUrl = UrlService.getDatabaseUrl; - scope.isViewmode = UrlService.isViewmode; - }, - template: ` + scope.getAnalyzerFromExpName = ExperimentsService.getAnalyzerFromExpName; + scope.getExpUrl = UrlService.getExperimentUrl; + scope.getBlockUrl = UrlService.getBlockUrl; + scope.getDatabaseUrl = UrlService.getDatabaseUrl; + scope.isViewmode = UrlService.isViewmode; + }, + template: ` <div id="{{group.name}}-explist-heading" class="panel-heading" role="tab"> - <h4 class="panel-title"> - <a - class='' - role="button" - data-toggle="collapse" - data-parent="#{{group.name}}-explist-heading" - href="#collapse-{{group.name}}-explist" - aria-expanded="true" - aria-controls="collapse-{{group.name}}-explist"> - Experiments - </a> - <div ng-if='!isViewmode()' class='btn-group'> - <div class="dropdown"> - <button - class="btn btn-default dropdown-toggle" - ng-class='{disabled: expsNotInGroup().length === 0}' - type="button" - id="{{ dropdownId }}" - data-toggle="dropdown" - aria-haspopup="true" - aria-expanded="true"> - Add Experiment - <span class="caret"></span> - </button> - <ul class="dropdown-menu" aria-labelledby="{{ dropdownId }}"> - <li - ng-repeat='exp in expsNotInGroup()' - ng-click='group.addExperiment(exp, getAnalyzerFromExpName(exp))'> - <a>{{ exp }}</a> - </li> - </ul> - </div> - </div> - <i style='margin-left: 5px;' ng-if='group.analyzer.length > 0'> - Analyzer: <a href='{{ getBlockUrl(group.analyzer) }}'>{{ group.analyzer }}</a> - </i> - </h4> + <h4 class="panel-title"> + <a + class='' + role="button" + data-toggle="collapse" + data-parent="#{{group.name}}-explist-heading" + href="#collapse-{{group.name}}-explist" + aria-expanded="true" + aria-controls="collapse-{{group.name}}-explist"> + Experiments + </a> + <div ng-if='!isViewmode()' class='btn-group'> + <div class="dropdown"> + <button + class="btn btn-default dropdown-toggle" + ng-class='{disabled: expsNotInGroup().length === 0}' + type="button" + id="{{ dropdownId }}" + data-toggle="dropdown" + aria-haspopup="true" + aria-expanded="true"> + Add Experiment + <span class="caret"></span> + </button> + <ul class="dropdown-menu" aria-labelledby="{{ dropdownId }}"> + <li + ng-repeat='exp in expsNotInGroup()' + ng-click='group.addExperiment(exp, getAnalyzerFromExpName(exp))'> + <a>{{ exp }}</a> + </li> + </ul> + </div> + </div> + <i style='margin-left: 5px;' ng-if='group.analyzer.length > 0'> + Analyzer: <a href='{{ getBlockUrl(group.analyzer) }}'>{{ group.analyzer }}</a> + </i> + </h4> </div> <div id="collapse-{{group.name}}-explist" - class="panel-collapse collapse in" - role="tabpanel" - aria-labelledby="{{group.name}}-explist-heading"> - <div class="panel-body"> - <table ng-if='group.experiments.length > 0' class="table table-striped table-hover"> - <thead> - <tr> - <th ng-if='!isViewmode()'></th> - <th>Alias</th> - <th>Experiment</th> - <th>Databases/Protocols</th> - </tr> - </thead> - <tbody> - <tr ng-repeat='expName in group.experiments'> - <td ng-if='!isViewmode()'> - <div class='btn-group action-buttons'> - <span - style='cursor: pointer;' - ng-click='group.removeExperiment(expName)' - class="btn-delete" - data-toggle="tooltip" data-placement="top" title="Remove Experiment from Group"> - <i class="fa fa-times fa-lg"></i> - </span> - </div> - </td> - <td ng-if='!isViewmode()'><input ng-model='group.aliases[expName]'></input></td> - <td ng-if='isViewmode()'><span>{{ group.aliases[expName] }}</span></td> - <td><a href='{{ getExpUrl(expName) }}'>{{ getExpName(expName) }}</a></td> - <td> - <span ng-repeat='db in getExpDatabases(expName)'> - <a href='{{ getDatabaseUrl(db.split("@")[0]) }}'>{{ db }}</a> - - </span> - </td> - </tr> - </tbody> - </table> - </div> + class="panel-collapse collapse in" + role="tabpanel" + aria-labelledby="{{group.name}}-explist-heading"> + <div class="panel-body"> + <table ng-if='group.experiments.length > 0' class="table table-striped table-hover"> + <thead> + <tr> + <th ng-if='!isViewmode()'></th> + <th>Alias</th> + <th>Experiment</th> + <th>Databases/Protocols</th> + </tr> + </thead> + <tbody> + <tr ng-repeat='expName in group.experiments'> + <td ng-if='!isViewmode()'> + <div class='btn-group action-buttons'> + <span + style='cursor: pointer;' + ng-click='group.removeExperiment(expName)' + class="btn-delete" + data-toggle="tooltip" data-placement="top" title="Remove Experiment from Group"> + <i class="fa fa-times fa-lg"></i> + </span> + </div> + </td> + <td ng-if='!isViewmode()'><input ng-model='group.aliases[expName]'></input></td> + <td ng-if='isViewmode()'><span>{{ group.aliases[expName] }}</span></td> + <td><a href='{{ getExpUrl(expName) }}'>{{ getExpName(expName) }}</a></td> + <td> + <span ng-repeat='db in getExpDatabases(expName)'> + <a href='{{ getDatabaseUrl(db.split("@")[0]) }}'>{{ db }}</a> + + </span> + </td> + </tr> + </tbody> + </table> + </div> </div> ` - }; + }; }]); diff --git a/beat/web/reports/static/reports/app/directives/panelItems.js b/beat/web/reports/static/reports/app/directives/panelItems.js index 358862dd6..28df1fdd0 100644 --- a/beat/web/reports/static/reports/app/directives/panelItems.js +++ b/beat/web/reports/static/reports/app/directives/panelItems.js @@ -23,48 +23,48 @@ /* * groupPanelItems * Desc: - * displays the panel of report items of the group, - * using the item container adaptor + * displays the panel of report items of the group, + * using the item container adaptor */ angular.module('reportApp').directive("groupPanelItems", [function(){ - return { - scope: { - group: '=' - }, - link: function(scope){ - // CSS selector for drag handles for the ui-sortable functionality - // TODO: this needs to be changed each time the HTML hierarchy changes. - // Make it hierarchy-independent - scope.sortableOptions = { - handle: '.dragItem .drag-handle' - }; - }, - template: ` + return { + scope: { + group: '=' + }, + link: function(scope){ + // CSS selector for drag handles for the ui-sortable functionality + // TODO: this needs to be changed each time the HTML hierarchy changes. + // Make it hierarchy-independent + scope.sortableOptions = { + handle: '.dragItem .drag-handle' + }; + }, + template: ` <div ui-sortable='sortableOptions' ng-model='group._reportItems' class='panel-group'> - <div ng-repeat='item in group.reportItems'> - <div group-table-item - style='margin-bottom: 5px;' - ng-if="item.id.includes('table')" - group='group' - item-id='item.id' - content='item.content'> - </div> - <div group-plot-item - style='margin-bottom: 5px;' - ng-if="item.id.includes('plot')" - group='group' - item-id='item.id' - content='item.content'> - </div> - <div group-text-item - style='margin-bottom: 5px;' - ng-if="item.id.includes('text')" - group='group' - report-item='item' - item-id='item.id'> - </div> - </div> + <div ng-repeat='item in group.reportItems'> + <div group-table-item + style='margin-bottom: 5px;' + ng-if="item.id.includes('table')" + group='group' + item-id='item.id' + content='item.content'> + </div> + <div group-plot-item + style='margin-bottom: 5px;' + ng-if="item.id.includes('plot')" + group='group' + item-id='item.id' + content='item.content'> + </div> + <div group-text-item + style='margin-bottom: 5px;' + ng-if="item.id.includes('text')" + group='group' + report-item='item' + item-id='item.id'> + </div> + </div> </div> ` - }; + }; }]); diff --git a/beat/web/reports/static/reports/app/directives/plotItem.js b/beat/web/reports/static/reports/app/directives/plotItem.js index 4c7655b1b..36039d830 100644 --- a/beat/web/reports/static/reports/app/directives/plotItem.js +++ b/beat/web/reports/static/reports/app/directives/plotItem.js @@ -23,132 +23,132 @@ /* * groupPlotItem * Desc: - * displays a plot report item (basically a container for the plots code to insert into) + * displays a plot report item (basically a container for the plots code to insert into) */ angular.module('reportApp') .directive("groupPlotItem", ['ExperimentsService', 'PlotService', '$timeout', 'UrlService', function(ExperimentsService, PlotService, $timeout, UrlService){ - return { - scope: { - group: '=', - itemId: '=', - content: '=' - }, - link: function(scope){ - const group = scope.group; - scope.domId = `${scope.group.name}_${scope.itemId}`; + return { + scope: { + group: '=', + itemId: '=', + content: '=' + }, + link: function(scope){ + const group = scope.group; + scope.domId = `${scope.group.name}_${scope.itemId}`; - // container for the plots applet to insert into - scope.renderDivId = `${scope.domId}-render`; + // container for the plots applet to insert into + scope.renderDivId = `${scope.domId}-render`; - // the callback for when the plot renders - // (called every time the plot re-renders, e.g. user changes config/merged - const updatePlotConfig = (selectedPlotter, selectedConfig, isMerged) => { - scope.content.merged = isMerged; - scope.content.savedPlotter = selectedPlotter; - scope.content.savedConfig = selectedConfig; - }; + // the callback for when the plot renders + // (called every time the plot re-renders, e.g. user changes config/merged + const updatePlotConfig = (selectedPlotter, selectedConfig, isMerged) => { + scope.content.merged = isMerged; + scope.content.savedPlotter = selectedPlotter; + scope.content.savedConfig = selectedConfig; + }; - // wait until the container html element is rendered. - // angular will run these functions called with $timeout - // after everything has been rendered - $timeout(function() { - PlotService.addPlot(scope.group, scope.itemId, scope.renderDivId, updatePlotConfig); - }); + // wait until the container html element is rendered. + // angular will run these functions called with $timeout + // after everything has been rendered + $timeout(function() { + PlotService.addPlot(scope.group, scope.itemId, scope.renderDivId, updatePlotConfig); + }); - let plotTimer; + let plotTimer; - const updatePlot = () => { - clearTimeout(plotTimer); + const updatePlot = () => { + clearTimeout(plotTimer); - const queueUpdate = () => { - let el = document.querySelector(`#${scope.renderDivId}`); - // if the container is rendered and it already has had a render, - // redo the render - if(el && el.childNodes.length > 0){ - el.innerHTML = ''; - return PlotService.addPlot(scope.group, scope.itemId, scope.renderDivId, updatePlotConfig); - } - }; + const queueUpdate = () => { + let el = document.querySelector(`#${scope.renderDivId}`); + // if the container is rendered and it already has had a render, + // redo the render + if(el && el.childNodes.length > 0){ + el.innerHTML = ''; + return PlotService.addPlot(scope.group, scope.itemId, scope.renderDivId, updatePlotConfig); + } + }; - plotTimer = setTimeout(queueUpdate, 1000); - }; + plotTimer = setTimeout(queueUpdate, 1000); + }; - // if the user selected different plotter or config, rerender - scope.$watch( - () => `${scope.content.savedPlotter}|${scope.content.savedConfig}`, - updatePlot - ); + // if the user selected different plotter or config, rerender + scope.$watch( + () => `${scope.content.savedPlotter}|${scope.content.savedConfig}`, + updatePlot + ); - scope.toggleMerged = () => { - scope.content.merged = !scope.content.merged; - updatePlot(); - }; + scope.toggleMerged = () => { + scope.content.merged = !scope.content.merged; + updatePlot(); + }; - scope.getPlotter = () => PlotService.getPlotter(scope.content); - scope.getPossiblePlotters = () => PlotService.plotters.filter(p => p.dataformat === scope.content.type).map(p => p.name); - scope.getPossibleConfigs = () => PlotService.getPossibleConfigs(scope.getPlotter()); - scope.isViewmode = UrlService.isViewmode; - }, - template: ` + scope.getPlotter = () => PlotService.getPlotter(scope.content); + scope.getPossiblePlotters = () => PlotService.plotters.filter(p => p.dataformat === scope.content.type).map(p => p.name); + scope.getPossibleConfigs = () => PlotService.getPossibleConfigs(scope.getPlotter()); + scope.isViewmode = UrlService.isViewmode; + }, + template: ` <div panel-container dom-id='domId'> - <header> - <editable-label obj='content' field='itemName'></editable-label> - <div ng-if='!isViewmode()' class='btn-group action-buttons'> - <span - ng-click='group.removeReportItem(itemId)' - class="btn btn-default btn-delete" - data-toggle="tooltip" data-placement="top" title="Delete Plot"> - <i class="fa fa-times fa-lg"></i> - </span> - <span drag-handle handle-helper-class='dragItem'></span> - </div> - <div class='btn-group' role='group'> - <download-link - class='btn-group' - dom-id='{{ domId }}' - group='group' - item-id='itemId'> - </download-link> - <button ng-if='group.experiments.length > 1' class='btn btn-default' ng-click='toggleMerged()'> - <span ng-if='content.merged'><i class='fa fa-expand'></i> Expand</span> - <span ng-if='!content.merged'><i class='fa fa-compress'></i> Merge</span> - </button> - <div class='btn-group' role='group'> - <button - ng-class='{disabled: getPossiblePlotters().length === 1}' - class='btn btn-default dropdown-toggle' - type="button" - data-toggle="dropdown" - aria-haspopup="true" - aria-expanded="false"> - Plotter: {{ content.savedPlotter }} - <span class="caret"></span> - </button> - <ul class="dropdown-menu"> - <li ng-click='content.savedPlotter = p' ng-repeat='p in getPossiblePlotters()'><a>{{ p }}</a></li> - </ul> - </div> - <div class='btn-group' role='group'> - <button - ng-class='{disabled: getPossibleConfigs().length === 1}' - class='btn btn-default dropdown-toggle' - type="button" - data-toggle="dropdown" - aria-haspopup="true" - aria-expanded="false"> - Config: {{ content.savedConfig }} - <span class="caret"></span> - </button> - <ul class="dropdown-menu"> - <li ng-click='content.savedConfig = c' ng-repeat='c in getPossibleConfigs()'><a>{{ c }}</a></li> - </ul> - </div> - </div> - </header> - <content> - <div id='{{ renderDivId }}'></div> - </content> + <header> + <editable-label obj='content' field='itemName'></editable-label> + <div ng-if='!isViewmode()' class='btn-group action-buttons'> + <span + ng-click='group.removeReportItem(itemId)' + class="btn btn-default btn-delete" + data-toggle="tooltip" data-placement="top" title="Delete Plot"> + <i class="fa fa-times fa-lg"></i> + </span> + <span drag-handle handle-helper-class='dragItem'></span> + </div> + <div class='btn-group' role='group'> + <download-link + class='btn-group' + dom-id='{{ domId }}' + group='group' + item-id='itemId'> + </download-link> + <button ng-if='group.experiments.length > 1' class='btn btn-default' ng-click='toggleMerged()'> + <span ng-if='content.merged'><i class='fa fa-expand'></i> Expand</span> + <span ng-if='!content.merged'><i class='fa fa-compress'></i> Merge</span> + </button> + <div class='btn-group' role='group'> + <button + ng-class='{disabled: getPossiblePlotters().length === 1}' + class='btn btn-default dropdown-toggle' + type="button" + data-toggle="dropdown" + aria-haspopup="true" + aria-expanded="false"> + Plotter: {{ content.savedPlotter }} + <span class="caret"></span> + </button> + <ul class="dropdown-menu"> + <li ng-click='content.savedPlotter = p' ng-repeat='p in getPossiblePlotters()'><a>{{ p }}</a></li> + </ul> + </div> + <div class='btn-group' role='group'> + <button + ng-class='{disabled: getPossibleConfigs().length === 1}' + class='btn btn-default dropdown-toggle' + type="button" + data-toggle="dropdown" + aria-haspopup="true" + aria-expanded="false"> + Config: {{ content.savedConfig }} + <span class="caret"></span> + </button> + <ul class="dropdown-menu"> + <li ng-click='content.savedConfig = c' ng-repeat='c in getPossibleConfigs()'><a>{{ c }}</a></li> + </ul> + </div> + </div> + </header> + <content> + <div id='{{ renderDivId }}'></div> + </content> </div> ` - }; + }; }]); diff --git a/beat/web/reports/static/reports/app/directives/publish.js b/beat/web/reports/static/reports/app/directives/publish.js index 96d295aa9..fe68cb35a 100644 --- a/beat/web/reports/static/reports/app/directives/publish.js +++ b/beat/web/reports/static/reports/app/directives/publish.js @@ -23,94 +23,94 @@ /* * reportPublish * Desc: - * Displays a modal for publishing the current report. - * It also lets the user select which algorithms will be made - * Visible or Public. + * Displays a modal for publishing the current report. + * It also lets the user select which algorithms will be made + * Visible or Public. */ angular.module('reportApp') .directive("reportPublish", ['reportFactory', 'ReportService', '$timeout', 'ErrorService', function(reportFactory, ReportService, $timeout, ErrorService){ - return { - scope: { - }, - restrict: 'E', - link: function(scope, el){ - scope.algorithms = []; - scope.radios = {}; + return { + scope: { + }, + restrict: 'E', + link: function(scope, el){ + scope.algorithms = []; + scope.radios = {}; - // sets up the data for the modal and opens it - scope.buildPublishModal = () => { - return reportFactory.publishReportAlgorithms(ReportService.author, ReportService.name, '') - .then(res => { - // rm past data - scope.algorithms.splice(0, scope.algorithms.length); - Object.keys(scope.radios).forEach(k => delete scope.radios[k]); + // sets up the data for the modal and opens it + scope.buildPublishModal = () => { + return reportFactory.publishReportAlgorithms(ReportService.author, ReportService.name, '') + .then(res => { + // rm past data + scope.algorithms.splice(0, scope.algorithms.length); + Object.keys(scope.radios).forEach(k => delete scope.radios[k]); - // init new data - res.data.forEach(a => scope.algorithms.push(a)); - scope.algorithms.forEach(a => { scope.radios[a] = ''; }); - }) - .catch(e => { - $('#publishReportModal').modal('hide'); - // wait for the modal to close before logging error - setTimeout(() => ErrorService.logError(e, `Could not fetch the algorithms of the report, which is required to publish it.`), 500); - }) - ; - }; + // init new data + res.data.forEach(a => scope.algorithms.push(a)); + scope.algorithms.forEach(a => { scope.radios[a] = ''; }); + }) + .catch(e => { + $('#publishReportModal').modal('hide'); + // wait for the modal to close before logging error + setTimeout(() => ErrorService.logError(e, `Could not fetch the algorithms of the report, which is required to publish it.`), 500); + }) + ; + }; - $timeout(() => $('#publishReportModal').on('show.bs.modal', function(e){ return scope.buildPublishModal(); }), 0); + $timeout(() => $('#publishReportModal').on('show.bs.modal', function(e){ return scope.buildPublishModal(); }), 0); - // sends the request to publish the report, along with the algs chosen to be OS - scope.publishReport = () => { - const openSourceAlgs = Object.entries(scope.radios) - .filter(([alg, val]) => val === 'openSource') - .map(([alg, val]) => alg); + // sends the request to publish the report, along with the algs chosen to be OS + scope.publishReport = () => { + const openSourceAlgs = Object.entries(scope.radios) + .filter(([alg, val]) => val === 'openSource') + .map(([alg, val]) => alg); - const data = JSON.stringify({ - visible_algorithms: openSourceAlgs - }); + const data = JSON.stringify({ + visible_algorithms: openSourceAlgs + }); - return ReportService.publishReport(openSourceAlgs) - .then(() => { - window.location.reload(); - }) - .catch(e => { - ErrorService.logError(e, `Could not publish the report.`); - }) - ; - }; - }, - template: ` + return ReportService.publishReport(openSourceAlgs) + .then(() => { + window.location.reload(); + }) + .catch(e => { + ErrorService.logError(e, `Could not publish the report.`); + }) + ; + }; + }, + template: ` <bootstrap-modal dom-id='publishReportModal' button-submit-text='Publish' button-submit-func='publishReport'> - <b-title> - Publish Report - </b-title> - <b-content> - <p>Publishing your report will make it accessible to anyone. Your report will not be editable anymore.</p> - <div ng-if='algorithms.length > 0'> - <p>The following algorithms will become public. - They can either be <i>Visible</i> (no-one else can see inside the algorithm, but others can still use it) or <i>Open-Source</i> (the entire algorithm is visible as well as useable). - Choose which algorithms will be Visible, and which will be Open-Source:</p> - <form> - <table class='table table-striped'> - <thead> - <tr> - <td>Visible</td> - <td>Open-Source</td> - <td>Algorithm</td> - </tr> - </thead> - <tbody> - <tr ng-repeat='alg in algorithms'> - <td><input type='radio' name='{{ alg }}' ng-model='radios[alg]' value='visible'></td> - <td><input type='radio' name='{{ alg }}' ng-model='radios[alg]' value='openSource' checked></td> - <td>{{ alg }}</td> - </tr> - </tbody> - </table> - </form> - </div> - </b-content> + <b-title> + Publish Report + </b-title> + <b-content> + <p>Publishing your report will make it accessible to anyone. Your report will not be editable anymore.</p> + <div ng-if='algorithms.length > 0'> + <p>The following algorithms will become public. + They can either be <i>Visible</i> (no-one else can see inside the algorithm, but others can still use it) or <i>Open-Source</i> (the entire algorithm is visible as well as useable). + Choose which algorithms will be Visible, and which will be Open-Source:</p> + <form> + <table class='table table-striped'> + <thead> + <tr> + <td>Visible</td> + <td>Open-Source</td> + <td>Algorithm</td> + </tr> + </thead> + <tbody> + <tr ng-repeat='alg in algorithms'> + <td><input type='radio' name='{{ alg }}' ng-model='radios[alg]' value='visible'></td> + <td><input type='radio' name='{{ alg }}' ng-model='radios[alg]' value='openSource' checked></td> + <td>{{ alg }}</td> + </tr> + </tbody> + </table> + </form> + </div> + </b-content> </bootstrap-modal> ` - }; + }; }]); diff --git a/beat/web/reports/static/reports/app/directives/save.js b/beat/web/reports/static/reports/app/directives/save.js index 60d7c576b..ebaadea9b 100644 --- a/beat/web/reports/static/reports/app/directives/save.js +++ b/beat/web/reports/static/reports/app/directives/save.js @@ -23,38 +23,38 @@ /* * reportSave * Desc: - * saves the current report + * saves the current report */ angular.module('reportApp') .directive("reportSave", ['GroupsService', 'ReportService', 'reportFactory', 'ErrorService', 'ExperimentsService', function(GroupsService, ReportService, reportFactory, ErrorService, ExperimentsService){ - return { - restrict: 'A', - link: function(scope, el){ + return { + restrict: 'A', + link: function(scope, el){ - const saveReport = () => { - // save the serialized group data... - // the rest of the state is reconstructed from it and the URL - let saveData = { - content: { - 'groups': GroupsService.serializeGroups() - } - }; + const saveReport = () => { + // save the serialized group data... + // the rest of the state is reconstructed from it and the URL + let saveData = { + content: { + 'groups': GroupsService.serializeGroups() + } + }; - return reportFactory.removeExperiments(ExperimentsService.cachedDeletedExperiments) - .then(() => reportFactory.updateReport(ReportService.author, ReportService.name, saveData, '')) - .then(() => { - const lastEditedEl = document.querySelector('.lastEdited'); - lastEditedEl.classList.remove('lastEditedAnimating'); - void lastEditedEl.offsetWidth; - lastEditedEl.classList.add('lastEditedAnimating'); - scope.$apply(); - }) - .catch(e => { - ErrorService.logError(e, `Could not save the report.`); - }); - }; + return reportFactory.removeExperiments(ExperimentsService.cachedDeletedExperiments) + .then(() => reportFactory.updateReport(ReportService.author, ReportService.name, saveData, '')) + .then(() => { + const lastEditedEl = document.querySelector('.lastEdited'); + lastEditedEl.classList.remove('lastEditedAnimating'); + void lastEditedEl.offsetWidth; + lastEditedEl.classList.add('lastEditedAnimating'); + scope.$apply(); + }) + .catch(e => { + ErrorService.logError(e, `Could not save the report.`); + }); + }; - el.bind('click', saveReport); - }, - }; + el.bind('click', saveReport); + }, + }; }]); diff --git a/beat/web/reports/static/reports/app/directives/tableItem.js b/beat/web/reports/static/reports/app/directives/tableItem.js index e157b69f6..036c1678d 100644 --- a/beat/web/reports/static/reports/app/directives/tableItem.js +++ b/beat/web/reports/static/reports/app/directives/tableItem.js @@ -23,271 +23,271 @@ /* * tableItem * Desc: - * displays a table report item and lets the user - * manage this table's selected cols and float precision + * displays a table report item and lets the user + * manage this table's selected cols and float precision */ angular.module('reportApp') .directive("groupTableItem", ['GroupsService', 'ExperimentsService', 'UrlService', function(GroupsService, ExperimentsService, UrlService){ - return { - scope: { - group: '=', - itemId: '=', - content: '=' - }, - link: function(scope){ - // aliases - scope.fields = scope.content.fields; - // ids - scope.domId = `${scope.group.name}_${scope.itemId}`; - scope.colSelectorId = `${scope.domId}_columnSelector`; + return { + scope: { + group: '=', + itemId: '=', + content: '=' + }, + link: function(scope){ + // aliases + scope.fields = scope.content.fields; + // ids + scope.domId = `${scope.group.name}_${scope.itemId}`; + scope.colSelectorId = `${scope.domId}_columnSelector`; - // 1 - 10 - // probably the most concise way of generating 1-10 computationally - // also vastly more complex than writing [1,2,3,4,5,6,7,9,10] - scope.floatingPointRange = [...(new Array(10)).keys()].map(i => i + 1); + // 1 - 10 + // probably the most concise way of generating 1-10 computationally + // also vastly more complex than writing [1,2,3,4,5,6,7,9,10] + scope.floatingPointRange = [...(new Array(10)).keys()].map(i => i + 1); - // init the chosen cols for the table with the saved cols - scope.chosenCols = Array.from(scope.fields); + // init the chosen cols for the table with the saved cols + scope.chosenCols = Array.from(scope.fields); - // save new cols choice - // due to how angular handles functions passed as props to an element, - // it must return a function that does the actual work. - scope.saveChosenCols = () => () => { - // we want to keep the selected columns in order, - // so try to add new columns in their correct indices, - // and rm cols by mutating the array + // save new cols choice + // due to how angular handles functions passed as props to an element, + // it must return a function that does the actual work. + scope.saveChosenCols = () => () => { + // we want to keep the selected columns in order, + // so try to add new columns in their correct indices, + // and rm cols by mutating the array - const newCols = scope.chosenCols - .filter(c => !scope.fields.includes(c)); + const newCols = scope.chosenCols + .filter(c => !scope.fields.includes(c)); - const rmCols = scope.fields - .filter(f => !scope.chosenCols.includes(f) && f !== 'Experiment'); + const rmCols = scope.fields + .filter(f => !scope.chosenCols.includes(f) && f !== 'Experiment'); - rmCols.forEach(rf => scope.fields.splice(scope.fields.indexOf(rf), 1)); + rmCols.forEach(rf => scope.fields.splice(scope.fields.indexOf(rf), 1)); - newCols.forEach(nf => scope.fields.push(nf)); - }; + newCols.forEach(nf => scope.fields.push(nf)); + }; - // toggle val for viewing CSV - scope.isViewingCSV = { val: false }; - scope.toggleViewingCSV = () => { - scope.isViewingCSV.val = !scope.isViewingCSV.val; - }; + // toggle val for viewing CSV + scope.isViewingCSV = { val: false }; + scope.toggleViewingCSV = () => { + scope.isViewingCSV.val = !scope.isViewingCSV.val; + }; - // aliases - scope.fields = scope.content.fields; + // aliases + scope.fields = scope.content.fields; - // add 'expName' to the beginning of the fields to show in the table - // if it isnt already there - if(scope.fields.length === 0 || scope.fields[0] !== 'Experiment'){ - scope.fields.unshift('Experiment'); - } + // add 'expName' to the beginning of the fields to show in the table + // if it isnt already there + if(scope.fields.length === 0 || scope.fields[0] !== 'Experiment'){ + scope.fields.unshift('Experiment'); + } - // get possible table entries - scope.tableables = ExperimentsService.tableables || {}; + // get possible table entries + scope.tableables = ExperimentsService.tableables || {}; - // gets the field type (int, float, string, nothing) - scope.getFieldType = (field) => { - if(field === scope.fields[0]){ - return 'string'; - } + // gets the field type (int, float, string, nothing) + scope.getFieldType = (field) => { + if(field === scope.fields[0]){ + return 'string'; + } - let hasFieldObj = Object.values(scope.tableables) - .find(o => o[field]); - let fVal = hasFieldObj ? hasFieldObj[field] : {}; - let type; - if(fVal.type){ - type = fVal.type; - } else if(Number.isSafeInteger(fVal)){ - type = 'integer'; - } else if(Number.isFinite(fVal)){ - type = 'float'; - } else if(typeof fVal === 'string'){ - type = 'string'; - } else { - type = undefined; - } + let hasFieldObj = Object.values(scope.tableables) + .find(o => o[field]); + let fVal = hasFieldObj ? hasFieldObj[field] : {}; + let type; + if(fVal.type){ + type = fVal.type; + } else if(Number.isSafeInteger(fVal)){ + type = 'integer'; + } else if(Number.isFinite(fVal)){ + type = 'float'; + } else if(typeof fVal === 'string'){ + type = 'string'; + } else { + type = undefined; + } - return type; - }; - // gets the field val for the given exp - scope.getFieldVal = (expName, field) => { - const alias = scope.group.aliases[expName].length > 0 ? - scope.group.aliases[expName] : expName; + return type; + }; + // gets the field val for the given exp + scope.getFieldVal = (expName, field) => { + const alias = scope.group.aliases[expName].length > 0 ? + scope.group.aliases[expName] : expName; - const name = scope.tableables[alias] ? - alias : expName; + const name = scope.tableables[alias] ? + alias : expName; - let fVal = scope.tableables[name] ? scope.tableables[name][field] : undefined; - let val; + let fVal = scope.tableables[name] ? scope.tableables[name][field] : undefined; + let val; - if(field === scope.fields[0]){ - val = alias; - } else if(!fVal){ - val = '-'; - } else { - let tmp; - if(fVal.value){ - tmp = fVal.value; - } else { - tmp = fVal; - } + if(field === scope.fields[0]){ + val = alias; + } else if(!fVal){ + val = '-'; + } else { + let tmp; + if(fVal.value){ + tmp = fVal.value; + } else { + tmp = fVal; + } - let type = scope.getFieldType(field); - if(type && type.startsWith('float')){ - val = tmp.toFixed(parseInt(scope.content.precision)); - } else { - val = tmp; - } - } + let type = scope.getFieldType(field); + if(type && type.startsWith('float')){ + val = tmp.toFixed(parseInt(scope.content.precision)); + } else { + val = tmp; + } + } - return val; - }; + return val; + }; - // need to nest actual value in an obj to get angular - // to watch it correctly - scope.sortField = { val: 'Experiment', isReversed: false }; - // sort rows (one row per exp) - scope.sortFunc = (expName) => { - return scope.getFieldType(scope.sortField.val) ? - scope.getFieldVal(expName, scope.sortField.val) : expName; - }; - // sets the new sort field and direction - scope.setSortField = (field) => { - if(scope.sortField.val === field){ - scope.sortField.isReversed = !scope.sortField.isReversed; - } else { - scope.sortField.val = field; - scope.sortField.isReversed = false; - } - }; + // need to nest actual value in an obj to get angular + // to watch it correctly + scope.sortField = { val: 'Experiment', isReversed: false }; + // sort rows (one row per exp) + scope.sortFunc = (expName) => { + return scope.getFieldType(scope.sortField.val) ? + scope.getFieldVal(expName, scope.sortField.val) : expName; + }; + // sets the new sort field and direction + scope.setSortField = (field) => { + if(scope.sortField.val === field){ + scope.sortField.isReversed = !scope.sortField.isReversed; + } else { + scope.sortField.val = field; + scope.sortField.isReversed = false; + } + }; - // a different view of the table - scope.getCSV = () => { - let fields = scope.fields; - let exps = scope.group.experiments - // clone arr - .map(e => `${e}`) - .sort((ea, eb) => (scope.sortField.isReversed ? -1 : 1) * (scope.sortFunc(ea) < scope.sortFunc(eb) ? -1 : 1)) - ; + // a different view of the table + scope.getCSV = () => { + let fields = scope.fields; + let exps = scope.group.experiments + // clone arr + .map(e => `${e}`) + .sort((ea, eb) => (scope.sortField.isReversed ? -1 : 1) * (scope.sortFunc(ea) < scope.sortFunc(eb) ? -1 : 1)) + ; - let str = ''; + let str = ''; - let fieldsStr = fields - .map(f => `${f}(${scope.getFieldType(f)})`) - .join(','); + let fieldsStr = fields + .map(f => `${f}(${scope.getFieldType(f)})`) + .join(','); - let expsStrs = exps - .map(e => fields.map(f => `${scope.getFieldVal(e, f)}`).join(',')) - .join('\n'); + let expsStrs = exps + .map(e => fields.map(f => `${scope.getFieldVal(e, f)}`).join(',')) + .join('\n'); - str = `${fieldsStr}\n${expsStrs}`; + str = `${fieldsStr}\n${expsStrs}`; - return str; - }; + return str; + }; - // get experiment url for linking to exps - // returns the url if successful else false - scope.getExperimentUrl = (expName) => { - // if theres more than 2 '/' in the expName, - // its the absolute URL, and the user has access - // to view the experiment. - if(expName.split('/').length > 3){ - return UrlService.getExperimentUrl(expName); - } + // get experiment url for linking to exps + // returns the url if successful else false + scope.getExperimentUrl = (expName) => { + // if theres more than 2 '/' in the expName, + // its the absolute URL, and the user has access + // to view the experiment. + if(expName.split('/').length > 3){ + return UrlService.getExperimentUrl(expName); + } - // else, its the short name - // (just the exp name, not including author/toolchain) - // and the user cant see the exp - return false; - }; + // else, its the short name + // (just the exp name, not including author/toolchain) + // and the user cant see the exp + return false; + }; - scope.sortableOptions = { - items: `th:not(:first-child)` - }; + scope.sortableOptions = { + items: `th:not(:first-child)` + }; - scope.isViewmode = UrlService.isViewmode; - }, - template: ` + scope.isViewmode = UrlService.isViewmode; + }, + template: ` <div panel-container dom-id='domId'> - <header> - <editable-label obj='content' field='itemName'></editable-label> - <div ng-if='!isViewmode()' class='btn-group action-buttons'> - <span - ng-click='group.removeReportItem(itemId)' - class="btn btn-default btn-delete" - data-toggle="tooltip" data-placement="top" title="Delete Table"> - <i class="fa fa-times fa-lg"></i> - </span> - <span drag-handle handle-helper-class='dragItem'></span> - </div> - <div class="btn-group" role="group" role='tab'> - <div ng-if='!isViewmode()' class="btn-group" role="group" - group-table-field-selector - id='colSelectorId' - group='group' - cols-selected='chosenCols' - button-action='saveChosenCols()' - title="Choose Columns" - button-text="Save Column Choices" - > - </div> - <div class='btn-group' role='group'> - <button class='btn btn-default' id="{{domId}}-precision" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> - Float Precision: {{ content.precision }} - <span class="caret"></span> - </button> - <ul class="dropdown-menu" aria-labelledby="{{domId}}-precision"> - <li ng-click='content.precision = i' ng-repeat='i in floatingPointRange'><a>{{ i }}</a></li> - </ul> - </div> - <button class='btn btn-default' ng-click='toggleViewingCSV()'> - Toggle CSV View - </button> - </div> - </header> - <content> - <div class='panel-body'> - <div ng-if='isViewingCSV.val'> - <pre>{{ getCSV() }}</pre> - </div> - <div ng-if='!isViewingCSV.val' style='height: 100%; overflow-x: auto;'> - <table class="table table-striped table-hover"> - <thead> - <tr ui-sortable='sortableOptions' ng-model='fields'> - <th ng-repeat='field in fields'> - <span - ng-if="sortField.val == field" - class='glyphicon' - ng-class="{ - 'glyphicon-chevron-up': sortField.isReversed, - 'glyphicon-chevron-down': !sortField.isReversed - }" - > - </span> - <a role='button' ng-click='setSortField(field)'> - {{ field }} <i>({{ getFieldType(field) }})</i> - </a> - </th> - </tr> - </thead> - <tbody> - <tr ng-repeat="exp in group.experiments | orderBy:sortFunc:sortField.isReversed"> - <td ng-repeat='field in fields'> - <a ng-if='$index == 0 && getExperimentUrl(exp)' href='{{ getExperimentUrl(exp) }}'> - {{ getFieldVal(exp, field) }} - </a> - <span ng-if='!$index == 0 || !getExperimentUrl(exp)'> - {{ getFieldVal(exp, field) }} - </span> - </td> - </tr> - </tbody> - </table> - </div> - </div> - </content> + <header> + <editable-label obj='content' field='itemName'></editable-label> + <div ng-if='!isViewmode()' class='btn-group action-buttons'> + <span + ng-click='group.removeReportItem(itemId)' + class="btn btn-default btn-delete" + data-toggle="tooltip" data-placement="top" title="Delete Table"> + <i class="fa fa-times fa-lg"></i> + </span> + <span drag-handle handle-helper-class='dragItem'></span> + </div> + <div class="btn-group" role="group" role='tab'> + <div ng-if='!isViewmode()' class="btn-group" role="group" + group-table-field-selector + id='colSelectorId' + group='group' + cols-selected='chosenCols' + button-action='saveChosenCols()' + title="Choose Columns" + button-text="Save Column Choices" + > + </div> + <div class='btn-group' role='group'> + <button class='btn btn-default' id="{{domId}}-precision" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> + Float Precision: {{ content.precision }} + <span class="caret"></span> + </button> + <ul class="dropdown-menu" aria-labelledby="{{domId}}-precision"> + <li ng-click='content.precision = i' ng-repeat='i in floatingPointRange'><a>{{ i }}</a></li> + </ul> + </div> + <button class='btn btn-default' ng-click='toggleViewingCSV()'> + Toggle CSV View + </button> + </div> + </header> + <content> + <div class='panel-body'> + <div ng-if='isViewingCSV.val'> + <pre>{{ getCSV() }}</pre> + </div> + <div ng-if='!isViewingCSV.val' style='height: 100%; overflow-x: auto;'> + <table class="table table-striped table-hover"> + <thead> + <tr ui-sortable='sortableOptions' ng-model='fields'> + <th ng-repeat='field in fields'> + <span + ng-if="sortField.val == field" + class='glyphicon' + ng-class="{ + 'glyphicon-chevron-up': sortField.isReversed, + 'glyphicon-chevron-down': !sortField.isReversed + }" + > + </span> + <a role='button' ng-click='setSortField(field)'> + {{ field }} <i>({{ getFieldType(field) }})</i> + </a> + </th> + </tr> + </thead> + <tbody> + <tr ng-repeat="exp in group.experiments | orderBy:sortFunc:sortField.isReversed"> + <td ng-repeat='field in fields'> + <a ng-if='$index == 0 && getExperimentUrl(exp)' href='{{ getExperimentUrl(exp) }}'> + {{ getFieldVal(exp, field) }} + </a> + <span ng-if='!$index == 0 || !getExperimentUrl(exp)'> + {{ getFieldVal(exp, field) }} + </span> + </td> + </tr> + </tbody> + </table> + </div> + </div> + </content> </div> ` - }; + }; }]); diff --git a/beat/web/reports/static/reports/app/directives/textItem.js b/beat/web/reports/static/reports/app/directives/textItem.js index 72a2c9967..e1879d512 100644 --- a/beat/web/reports/static/reports/app/directives/textItem.js +++ b/beat/web/reports/static/reports/app/directives/textItem.js @@ -23,130 +23,130 @@ /* * groupTextItem * Desc: - * displays a text report item: - * - sends the raw RST to the server to be compiled, - * and displays the returned HTML - * - the user can edit the RST using codemirror - * - the compile is async and doesnt require refreshing the page + * displays a text report item: + * - sends the raw RST to the server to be compiled, + * and displays the returned HTML + * - the user can edit the RST using codemirror + * - the compile is async and doesnt require refreshing the page */ angular.module('reportApp') .directive("groupTextItem", ['GroupsService', '$sce', 'UrlService', 'reportFactory', function(GroupsService, $sce, UrlService, reportFactory){ - return { - scope: { - group: '=', - reportItem: '=', - }, - link: function(scope){ - // aliases - // angular requires that compiling raw html be sanitized - scope.trustAsHtml = $sce.trustAsHtml; - scope.item = scope.reportItem; - scope.domId = `${scope.group.name}_${scope.item.id}`; + return { + scope: { + group: '=', + reportItem: '=', + }, + link: function(scope){ + // aliases + // angular requires that compiling raw html be sanitized + scope.trustAsHtml = $sce.trustAsHtml; + scope.item = scope.reportItem; + scope.domId = `${scope.group.name}_${scope.item.id}`; - scope.isViewmode = UrlService.isViewmode; + scope.isViewmode = UrlService.isViewmode; - // codemirror options - scope.uicmOptions = { - mode: 'rst', - readOnly: scope.isViewmode() - }; + // codemirror options + scope.uicmOptions = { + mode: 'rst', + readOnly: scope.isViewmode() + }; - // handle compiling content - // holds the last response from the server - scope.compiledContent = { val: '' }; - scope.compileContent = () => { - let url = UrlService.getCompileRstUrl(); - let content = !scope.isViewmode() ? scope.item.content.text : - `${scope.group.name}|${scope.group.reportItems.indexOf(scope.reportItem)}`; + // handle compiling content + // holds the last response from the server + scope.compiledContent = { val: '' }; + scope.compileContent = () => { + let url = UrlService.getCompileRstUrl(); + let content = !scope.isViewmode() ? scope.item.content.text : + `${scope.group.name}|${scope.group.reportItems.indexOf(scope.reportItem)}`; - return reportFactory.compileRST(url, content) - .then(data => { - // when compiled, save the raw html - scope.compiledContent.val = data.data.html_str; - }); - }; + return reportFactory.compileRST(url, content) + .then(data => { + // when compiled, save the raw html + scope.compiledContent.val = data.data.html_str; + }); + }; - // handle edit/save/cancel buttons - scope.isSrcMode = { val: false }; - // when editing, use a buffer to hold the raw text - scope.unsavedContent = { val: `${scope.item.content.text}` }; - // save the buffer to the actual report item content - scope.saveAction = () => { - scope.item.content.text = scope.unsavedContent.val; - scope.compileContent(); - scope.isSrcMode.val = false; - }; - // discard buffer and use report item content - scope.cancelAction = () => { - scope.unsavedContent.val = `${scope.item.content.text}`; - scope.isSrcMode.val = false; - }; + // handle edit/save/cancel buttons + scope.isSrcMode = { val: false }; + // when editing, use a buffer to hold the raw text + scope.unsavedContent = { val: `${scope.item.content.text}` }; + // save the buffer to the actual report item content + scope.saveAction = () => { + scope.item.content.text = scope.unsavedContent.val; + scope.compileContent(); + scope.isSrcMode.val = false; + }; + // discard buffer and use report item content + scope.cancelAction = () => { + scope.unsavedContent.val = `${scope.item.content.text}`; + scope.isSrcMode.val = false; + }; - // compile the content when loaded - scope.compileContent(); - }, - template: ` + // compile the content when loaded + scope.compileContent(); + }, + template: ` <div panel-container dom-id='domId'> - <header> - <editable-label obj='item.content' field='itemName'></editable-label> - <div ng-if='!isViewmode()' class='btn-group action-buttons'> - <span - ng-click='group.removeReportItem(item.id)' - class="btn btn-default btn-delete" - data-toggle="tooltip" data-placement="top" title="Delete Text Block"> - <i class="fa fa-times fa-lg"></i> - </span> - <span drag-handle handle-helper-class='dragItem'></span> - </div> - <button ng-if='isViewmode()' class='btn btn-primary' ng-click='isSrcMode.val = !isSrcMode.val'> - Toggle Source View - </button> - </header> - <content> - <div ng-if='!isViewmode()' class='row'> - <div class='col-sm-10'> - <div ng-if='!isSrcMode.val' ng-bind-html='trustAsHtml(compiledContent.val)'></div> - <div ng-if='isSrcMode.val'> - <ui-codemirror ng-model='unsavedContent.val' ui-codemirror-opts='uicmOptions'></ui-codemirror> - <p class='help-block'> - Describe the object thoroughly using <a href="http://docutils.sourceforge.net/rst.html">reStructuredText mark-up</a><br><i class="fa fa-thumbs-up"></i> The ruler at 80 columns indicate suggested <a href="https://en.wikipedia.org/wiki/POSIX">POSIX line breaks</a> (for readability).<br><i class="fa fa-thumbs-up"></i> The editor will automatically enlarge to accomodate the entirety of your input<br><i class="fa fa-thumbs-up"></i> Use <a href="http://codemirror.net/doc/manual.html#commands">keyboard shortcuts</a> for search/replace and faster editing. For example, use Ctrl-F (PC) or Cmd-F (Mac) to search through this box - </p> - </div> - </div> - <div class='col-sm-2'> - <div class="pull-right action-buttons"> - <a - ng-if='!isSrcMode.val' - ng-click='isSrcMode.val = !isSrcMode.val' - class="btn btn-primary btn-sm"> - <i class="fa fa-edit fa-lg"></i> Edit - </a> - <a - ng-if='isSrcMode.val' - ng-click='cancelAction()' - class="btn btn-danger btn-sm"> - <i class="fa fa-times fa-lg"></i> Cancel - </a> - <a - ng-if='isSrcMode.val' - ng-click='saveAction()' - class="btn btn-success btn-sm"> - <i class="fa fa-save fa-lg"></i> Save - </a> - </div> - </div> - </div> - <div ng-if='isViewmode()' class='row'> - <div class='col-sm-12'> - <div ng-if='!isSrcMode.val' ng-bind-html='trustAsHtml(compiledContent.val)'></div> - <div ng-if='isSrcMode.val'> - <i>Readonly view</i> - <ui-codemirror ng-model='item.content.text' ui-codemirror-opts='uicmOptions'></ui-codemirror> - </div> - </div> - </div> - </content> + <header> + <editable-label obj='item.content' field='itemName'></editable-label> + <div ng-if='!isViewmode()' class='btn-group action-buttons'> + <span + ng-click='group.removeReportItem(item.id)' + class="btn btn-default btn-delete" + data-toggle="tooltip" data-placement="top" title="Delete Text Block"> + <i class="fa fa-times fa-lg"></i> + </span> + <span drag-handle handle-helper-class='dragItem'></span> + </div> + <button ng-if='isViewmode()' class='btn btn-primary' ng-click='isSrcMode.val = !isSrcMode.val'> + Toggle Source View + </button> + </header> + <content> + <div ng-if='!isViewmode()' class='row'> + <div class='col-sm-10'> + <div ng-if='!isSrcMode.val' ng-bind-html='trustAsHtml(compiledContent.val)'></div> + <div ng-if='isSrcMode.val'> + <ui-codemirror ng-model='unsavedContent.val' ui-codemirror-opts='uicmOptions'></ui-codemirror> + <p class='help-block'> + Describe the object thoroughly using <a href="http://docutils.sourceforge.net/rst.html">reStructuredText mark-up</a><br><i class="fa fa-thumbs-up"></i> The ruler at 80 columns indicate suggested <a href="https://en.wikipedia.org/wiki/POSIX">POSIX line breaks</a> (for readability).<br><i class="fa fa-thumbs-up"></i> The editor will automatically enlarge to accomodate the entirety of your input<br><i class="fa fa-thumbs-up"></i> Use <a href="http://codemirror.net/doc/manual.html#commands">keyboard shortcuts</a> for search/replace and faster editing. For example, use Ctrl-F (PC) or Cmd-F (Mac) to search through this box + </p> + </div> + </div> + <div class='col-sm-2'> + <div class="pull-right action-buttons"> + <a + ng-if='!isSrcMode.val' + ng-click='isSrcMode.val = !isSrcMode.val' + class="btn btn-primary btn-sm"> + <i class="fa fa-edit fa-lg"></i> Edit + </a> + <a + ng-if='isSrcMode.val' + ng-click='cancelAction()' + class="btn btn-danger btn-sm"> + <i class="fa fa-times fa-lg"></i> Cancel + </a> + <a + ng-if='isSrcMode.val' + ng-click='saveAction()' + class="btn btn-success btn-sm"> + <i class="fa fa-save fa-lg"></i> Save + </a> + </div> + </div> + </div> + <div ng-if='isViewmode()' class='row'> + <div class='col-sm-12'> + <div ng-if='!isSrcMode.val' ng-bind-html='trustAsHtml(compiledContent.val)'></div> + <div ng-if='isSrcMode.val'> + <i>Readonly view</i> + <ui-codemirror ng-model='item.content.text' ui-codemirror-opts='uicmOptions'></ui-codemirror> + </div> + </div> + </div> + </content> </div> ` - }; + }; }]); diff --git a/beat/web/reports/static/reports/app/factories/experimentFactory.js b/beat/web/reports/static/reports/app/factories/experimentFactory.js index 6ef4f0b32..630ce188b 100644 --- a/beat/web/reports/static/reports/app/factories/experimentFactory.js +++ b/beat/web/reports/static/reports/app/factories/experimentFactory.js @@ -21,29 +21,29 @@ */ //This factory retrieves data from the REST API and associates it with the $scope angular.module('reportApp').factory('experimentFactory', function($http, $q){ - return { - getExperimentInformation: function (url_prefix, experiment_id){ - urlBase = url_prefix + '/api/v1/experiments'; - return $http.get(urlBase + '/' + experiment_id + '/') - .then(function(response){ - return response.data; - }); - }, + return { + getExperimentInformation: function (url_prefix, experiment_id){ + urlBase = url_prefix + '/api/v1/experiments'; + return $http.get(urlBase + '/' + experiment_id + '/') + .then(function(response){ + return response.data; + }); + }, - getAllExperimentResultsForAuthor: function (user, report_id, url_prefix){ - urlBase = url_prefix + '/api/v1/reports'; - return $http.get(urlBase + '/' + user + '/' + report_id + '/results_author/') - .then(function(response){ - return response.data; - }); - }, + getAllExperimentResultsForAuthor: function (user, report_id, url_prefix){ + urlBase = url_prefix + '/api/v1/reports'; + return $http.get(urlBase + '/' + user + '/' + report_id + '/results_author/') + .then(function(response){ + return response.data; + }); + }, - getAllExperimentResults: function (url_prefix, report_number){ - urlBase = url_prefix + '/api/v1/reports'; - return $http.get(urlBase + '/' + report_number + '/results/') - .then(function(response){ - return response.data; - }); - } - }; + getAllExperimentResults: function (url_prefix, report_number){ + urlBase = url_prefix + '/api/v1/reports'; + return $http.get(urlBase + '/' + report_number + '/results/') + .then(function(response){ + return response.data; + }); + } + }; }); diff --git a/beat/web/reports/static/reports/app/factories/plotterFactory.js b/beat/web/reports/static/reports/app/factories/plotterFactory.js index 232d70245..705093f8d 100644 --- a/beat/web/reports/static/reports/app/factories/plotterFactory.js +++ b/beat/web/reports/static/reports/app/factories/plotterFactory.js @@ -21,23 +21,23 @@ */ //This factory retrieves data from the REST API and associates it with the $scope angular.module('reportApp').factory('plotterFactory', ['$http', function($http){ - let plotterFactory = {}; + let plotterFactory = {}; - plotterFactory.getPlotters = function (url_prefix){ - urlBase = url_prefix + '/api/v1/plotters'; - return $http.get(urlBase + '/'); - }; + plotterFactory.getPlotters = function (url_prefix){ + urlBase = url_prefix + '/api/v1/plotters'; + return $http.get(urlBase + '/'); + }; - plotterFactory.getDefaultPlotters = function (url_prefix){ - urlBase = url_prefix + '/api/v1/plotters/defaultplotters'; - return $http.get(urlBase + '/'); - }; + plotterFactory.getDefaultPlotters = function (url_prefix){ + urlBase = url_prefix + '/api/v1/plotters/defaultplotters'; + return $http.get(urlBase + '/'); + }; - plotterFactory.getPlotterParameter = function (url_prefix) - { - urlBase = url_prefix + '/api/v1/plotters/plotterparameters'; - return $http.get(urlBase + '/'); - }; + plotterFactory.getPlotterParameter = function (url_prefix) + { + urlBase = url_prefix + '/api/v1/plotters/plotterparameters'; + return $http.get(urlBase + '/'); + }; - return plotterFactory; + return plotterFactory; }]); diff --git a/beat/web/reports/static/reports/app/factories/reportFactory.js b/beat/web/reports/static/reports/app/factories/reportFactory.js index 7c06a2143..1177ca93a 100644 --- a/beat/web/reports/static/reports/app/factories/reportFactory.js +++ b/beat/web/reports/static/reports/app/factories/reportFactory.js @@ -21,96 +21,96 @@ */ //This factory retrieves data from the REST API and associates it with the $scope angular.module('reportApp').factory('reportFactory', ['$http', 'experimentFactory', 'UrlService', function($http, experimentFactory, UrlService) { - let urlBase = '/api/v1/reports'; - let reportFactory = {}; - - reportFactory.getReportInformation = function (user, report_id, url_prefix){ - urlBase = url_prefix + '/api/v1/reports'; - return $http.get(urlBase + '/' + user + '/' + report_id + '/'); - }; - - reportFactory.getReportInformationFromNumber = function (report_number, url_prefix){ - urlBase = url_prefix + '/api/v1/reports'; - return $http.get(urlBase + '/' + report_number + '/'); - }; - - reportFactory.updateReport = function (user, report_id, reportData, url_prefix){ - urlBase = url_prefix + '/api/v1/reports'; - //return $http.put(urlBase + '/' + user + '/' + report_id + '/'); - - return $http({ - headers: {'Content-Type': 'application/json'}, - url: urlBase + '/' + user + '/' + report_id + '/', - method: "PUT", - data: reportData, - }); - }; - - reportFactory.lockReport = function (user, report_id, url_prefix){ - urlBase = url_prefix + '/api/v1/reports'; - //return $http.put(urlBase + '/' + user + '/' + report_id + '/'); - - return $http({ - headers: {'Content-Type': 'application/json'}, - url: urlBase + '/' + user + '/' + report_id + '/lock/', - method: "POST", - //data: reportData, - }); - }; - - reportFactory.publishReport = function (url_prefix, user, report_id, reportData){ - urlBase = url_prefix + '/api/v1/reports'; - //return $http.put(urlBase + '/' + user + '/' + report_id + '/'); - - return $http({ - headers: {'Content-Type': 'application/json'}, - url: urlBase + '/' + user + '/' + report_id + '/publish/', - method: "POST", - data: reportData, - }); - }; - - reportFactory.publishReportAlgorithms = function (user, report_id, url_prefix){ - urlBase = url_prefix + '/api/v1/reports'; - //return $http.put(urlBase + '/' + user + '/' + report_id + '/'); - - return $http({ - headers: {'Content-Type': 'application/json'}, - url: urlBase + '/' + user + '/' + report_id + '/algorithms/', - method: "GET", - //data: reportData, - }); - }; - - reportFactory.removeExperiments = function (expNames){ - if(!expNames || expNames.length === 0){ - return Promise.resolve(); - } - - let url = UrlService.getRemoveExperimentUrl(); - return $http({ - headers: {'Content-Type': 'application/json'}, - url, - method: "POST", - data: { - experiments: expNames - } - }) - }; - - reportFactory.compileRST = (url, raw) => { - let data = { - raw - }; - - return $http({ - headers: {'Content-Type': 'application/json'}, - url, - method: "POST", - data - }); - }; - - - return reportFactory; + let urlBase = '/api/v1/reports'; + let reportFactory = {}; + + reportFactory.getReportInformation = function (user, report_id, url_prefix){ + urlBase = url_prefix + '/api/v1/reports'; + return $http.get(urlBase + '/' + user + '/' + report_id + '/'); + }; + + reportFactory.getReportInformationFromNumber = function (report_number, url_prefix){ + urlBase = url_prefix + '/api/v1/reports'; + return $http.get(urlBase + '/' + report_number + '/'); + }; + + reportFactory.updateReport = function (user, report_id, reportData, url_prefix){ + urlBase = url_prefix + '/api/v1/reports'; + //return $http.put(urlBase + '/' + user + '/' + report_id + '/'); + + return $http({ + headers: {'Content-Type': 'application/json'}, + url: urlBase + '/' + user + '/' + report_id + '/', + method: "PUT", + data: reportData, + }); + }; + + reportFactory.lockReport = function (user, report_id, url_prefix){ + urlBase = url_prefix + '/api/v1/reports'; + //return $http.put(urlBase + '/' + user + '/' + report_id + '/'); + + return $http({ + headers: {'Content-Type': 'application/json'}, + url: urlBase + '/' + user + '/' + report_id + '/lock/', + method: "POST", + //data: reportData, + }); + }; + + reportFactory.publishReport = function (url_prefix, user, report_id, reportData){ + urlBase = url_prefix + '/api/v1/reports'; + //return $http.put(urlBase + '/' + user + '/' + report_id + '/'); + + return $http({ + headers: {'Content-Type': 'application/json'}, + url: urlBase + '/' + user + '/' + report_id + '/publish/', + method: "POST", + data: reportData, + }); + }; + + reportFactory.publishReportAlgorithms = function (user, report_id, url_prefix){ + urlBase = url_prefix + '/api/v1/reports'; + //return $http.put(urlBase + '/' + user + '/' + report_id + '/'); + + return $http({ + headers: {'Content-Type': 'application/json'}, + url: urlBase + '/' + user + '/' + report_id + '/algorithms/', + method: "GET", + //data: reportData, + }); + }; + + reportFactory.removeExperiments = function (expNames){ + if(!expNames || expNames.length === 0){ + return Promise.resolve(); + } + + let url = UrlService.getRemoveExperimentUrl(); + return $http({ + headers: {'Content-Type': 'application/json'}, + url, + method: "POST", + data: { + experiments: expNames + } + }) + }; + + reportFactory.compileRST = (url, raw) => { + let data = { + raw + }; + + return $http({ + headers: {'Content-Type': 'application/json'}, + url, + method: "POST", + data + }); + }; + + + return reportFactory; }]); diff --git a/beat/web/reports/static/reports/app/services/errorService.js b/beat/web/reports/static/reports/app/services/errorService.js index 562c8a326..cecadebc8 100644 --- a/beat/web/reports/static/reports/app/services/errorService.js +++ b/beat/web/reports/static/reports/app/services/errorService.js @@ -23,36 +23,36 @@ /* * ErrorService * Desc: - * Centralizes user-facing error-handling in the reports app. - * Other parts of the app can register errors (i.e. 404s, server errors, - * invalid input, etc.) with the ErrorService. - * The ErrorService will (one at a time & synchronously!) pop up an error - * modal (directives/error.js, "report-error") to let the user know. + * Centralizes user-facing error-handling in the reports app. + * Other parts of the app can register errors (i.e. 404s, server errors, + * invalid input, etc.) with the ErrorService. + * The ErrorService will (one at a time & synchronously!) pop up an error + * modal (directives/error.js, "report-error") to let the user know. */ angular.module('reportApp').factory('ErrorService', ['$rootScope', function($rootScope){ - const es = { - }; + const es = { + }; - class ReportError { - constructor (errorObj, message) { - this._error = errorObj; - this._message = message || ''; - } + class ReportError { + constructor (errorObj, message) { + this._error = errorObj; + this._message = message || ''; + } - get error () { - return this._error; - } + get error () { + return this._error; + } - get message () { - return this._message; - } - } + get message () { + return this._message; + } + } - es.logError = (error, message) => { - const newErr = new ReportError(error, message); + es.logError = (error, message) => { + const newErr = new ReportError(error, message); - $rootScope.$broadcast('user:error', newErr); - }; + $rootScope.$broadcast('user:error', newErr); + }; - return es; + return es; }]); diff --git a/beat/web/reports/static/reports/app/services/experimentsService.js b/beat/web/reports/static/reports/app/services/experimentsService.js index 8a9818da1..87083a144 100644 --- a/beat/web/reports/static/reports/app/services/experimentsService.js +++ b/beat/web/reports/static/reports/app/services/experimentsService.js @@ -23,211 +23,211 @@ /* * ExperimentsService * Desc: - * Manages the experiments data, including storing it and generating - * different views of the data + * Manages the experiments data, including storing it and generating + * different views of the data */ angular.module('reportApp').factory('ExperimentsService', ['experimentFactory', 'GroupsService', 'UrlService', function(experimentFactory, GroupsService, UrlService){ - // holds the raw exp data received from the server - const expData = {}; - // holds the view generated from `getTableablesFromExpName` - const tableData = {}; - // holds the view generated from `getPlottablesFromExpName` - const plotData = {}; - const rawExpNames = []; - - // lots of experiments have fields that are differently-named - // but mean the same thing. - // These fields are the *only* available field in the obj. - const getVarProp = (obj, dontLogErrors) => { - let keys = Object.keys(obj); - return obj[keys[0]]; - }; - - // analyzer - const getAnalyzerFromExpName = (expName) => getVarProp(expData[expName].declaration.analyzers, true).algorithm; - - // get all possible objects that could be shown in a table - const getTableablesFromExpName = (expName) => { - let currExp = expData[expName]; - - // get all result objs that have a type that doesnt start with 'plot/' - // these results can be displayed in a table - let results = Object.entries(getVarProp(currExp.results)) - // type not starting with plot - .filter(([res_name, obj]) => !obj.type.startsWith('plot/')) - // after we've filtered fields, make it into an obj - .map(([res_name, obj]) => { return {[res_name]: obj}; }) - // concat objs via reduce - .reduce((o, curr) => Object.assign(o, curr), {}) - ; - - // get block parameters - let blockVars = Object.entries(currExp.declaration.blocks) - // only look at blocks with parameters - .filter(([blockName, block]) => block.parameters) - // loop through each block - .map(([blockName, block]) => { - // flatten the block's execution infos - return Object.entries(block.parameters) - .map(([fieldName, field]) => { - return { [`${blockName}.${fieldName}`]: field }; - }) - .reduce((o, fieldObj) => Object.assign(o, fieldObj), {}) - ; - }) - // flatten the exp's blocks' params - .reduce((o, blockObj) => Object.assign(o, blockObj), {}) - ; - - // get global experiment variables - let globalVars = Object.entries(currExp.declaration.globals) - // properties that have a '/' in the name - .filter(([name, obj]) => name.includes('/')) - .map(([algName, alg]) => { - // flatten the alg's fields - return Object.entries(alg) - .map(([fieldName, field]) => { - return { [`${algName}.${fieldName}`]: field }; - }) - .reduce((o, fieldObj) => Object.assign(o, fieldObj), {}) - ; - }) - // flatten the exp's algs' fields - .reduce((o, algObj) => Object.assign(o, algObj), {}) - ; - - // get block timing infos - let blockTiming = Object.entries(currExp.execution_info) - // loop through each block - .map(([blockName, block]) => { - // flatten the block's execution infos - return Object.entries(block) - .map(([fieldName, field]) => { - return { [`${blockName}.${fieldName}`]: field }; - }) - .reduce((o, fieldObj) => Object.assign(o, fieldObj), {}) - ; - }) - // flatten the exp's execution infos - .reduce((o, blockObj) => Object.assign(o, blockObj), {}) - ; - - // get total timing info - let expTiming = { - 'total_execution_time': Object.entries(blockTiming) - .filter(([name, val]) => name.includes('linear_execution_time')) - .map(([name, val]) => val) - .reduce((n, v) => n + v, 0) - }; - - // concat all objs - let allObj = Object.assign({}, - results, - globalVars, - blockVars, - blockTiming, - expTiming - ); - - return allObj; - }; - - // get all result objs that have a type that starts with 'plot/' - // these results can be displayed in a plot - const getPlottablesFromExpName = (expName) => { - return Object.entries(getVarProp(expData[expName].results)) - .filter(([res_name, obj]) => obj.type.startsWith('plot/')) - .map(([res_name, obj]) => Object.assign(obj, { label: res_name })); - }; - - // make sure there are no invalid experiments in the report's groups - // invalid -> in a group but not in the report - const cleanGroups = (validExpNames) => { - GroupsService.groups.forEach(g => { - g.experiments.forEach(e => { - // when the user can see the exp, the full name of the exp is available, - // including the user who made it, and the toolchain - const fullNameValid = validExpNames.includes(e); - // when the exp isnt able to be seen by the user, - // only the actual name of the exp is available - const partNameValid = validExpNames.includes(e.split('/').pop()); - if(!(fullNameValid || partNameValid)){ - g.removeExperiment(e); - } - }); - }); - }; - - // fetch the exp data and process it - const loadExperiments = () => { - let expFetch; - const namePath = UrlService.getByNamePath(); - const numberPath = UrlService.getByNumberPath(); - - if(namePath && namePath.length > 0){ - // get exps by report num - const user = namePath.split('/')[1]; - const name = namePath.split('/')[2]; - expFetch = experimentFactory.getAllExperimentResultsForAuthor(user, name, ''); - } else if(numberPath && numberPath.length > 0){ - //by report author - const num = numberPath.split('/')[1]; - expFetch = experimentFactory.getAllExperimentResults('', num); - } - - return expFetch - .then((exps) => { - // save the exp names as they were given by the server - Object.keys(exps).forEach(e => rawExpNames.push(e)); - - // process the given exp data - Object.entries(exps) - .forEach(([expName, exp]) => { - // depending on the permissions of the user - // when viewing this report - // the author/toolchain may be hidden. - // to be safe, always process - // both the given name (either the full or short name) - const shortName = expName.split('/').pop(); - for(let n of [shortName, expName]){ - expData[n] = exp; - tableData[n] = getTableablesFromExpName(n); - plotData[n] = getPlottablesFromExpName(n); - } - }); - - cleanGroups(rawExpNames); - - return expData; - }); - }; - - const cachedDeletedExperiments = []; - - const deleteExperiment = function(expName) { - // delete from all groups - GroupsService.groups.forEach(g => g.removeExperiment(expName)); - - // delete from expNames - const expNameIdx = this.experimentNames.indexOf(expName); - this.experimentNames.splice(expNameIdx, 1); - - // delete from expData - delete this.experiments[expName]; - - cachedDeletedExperiments.push(expName); - }; - - loadExperiments(); - - return { - experimentNames: rawExpNames, - experiments: expData, - plottables: plotData, - tableables: tableData, - getAnalyzerFromExpName, - loadExperiments, - deleteExperiment, - cachedDeletedExperiments - }; + // holds the raw exp data received from the server + const expData = {}; + // holds the view generated from `getTableablesFromExpName` + const tableData = {}; + // holds the view generated from `getPlottablesFromExpName` + const plotData = {}; + const rawExpNames = []; + + // lots of experiments have fields that are differently-named + // but mean the same thing. + // These fields are the *only* available field in the obj. + const getVarProp = (obj, dontLogErrors) => { + let keys = Object.keys(obj); + return obj[keys[0]]; + }; + + // analyzer + const getAnalyzerFromExpName = (expName) => getVarProp(expData[expName].declaration.analyzers, true).algorithm; + + // get all possible objects that could be shown in a table + const getTableablesFromExpName = (expName) => { + let currExp = expData[expName]; + + // get all result objs that have a type that doesnt start with 'plot/' + // these results can be displayed in a table + let results = Object.entries(getVarProp(currExp.results)) + // type not starting with plot + .filter(([res_name, obj]) => !obj.type.startsWith('plot/')) + // after we've filtered fields, make it into an obj + .map(([res_name, obj]) => { return {[res_name]: obj}; }) + // concat objs via reduce + .reduce((o, curr) => Object.assign(o, curr), {}) + ; + + // get block parameters + let blockVars = Object.entries(currExp.declaration.blocks) + // only look at blocks with parameters + .filter(([blockName, block]) => block.parameters) + // loop through each block + .map(([blockName, block]) => { + // flatten the block's execution infos + return Object.entries(block.parameters) + .map(([fieldName, field]) => { + return { [`${blockName}.${fieldName}`]: field }; + }) + .reduce((o, fieldObj) => Object.assign(o, fieldObj), {}) + ; + }) + // flatten the exp's blocks' params + .reduce((o, blockObj) => Object.assign(o, blockObj), {}) + ; + + // get global experiment variables + let globalVars = Object.entries(currExp.declaration.globals) + // properties that have a '/' in the name + .filter(([name, obj]) => name.includes('/')) + .map(([algName, alg]) => { + // flatten the alg's fields + return Object.entries(alg) + .map(([fieldName, field]) => { + return { [`${algName}.${fieldName}`]: field }; + }) + .reduce((o, fieldObj) => Object.assign(o, fieldObj), {}) + ; + }) + // flatten the exp's algs' fields + .reduce((o, algObj) => Object.assign(o, algObj), {}) + ; + + // get block timing infos + let blockTiming = Object.entries(currExp.execution_info) + // loop through each block + .map(([blockName, block]) => { + // flatten the block's execution infos + return Object.entries(block) + .map(([fieldName, field]) => { + return { [`${blockName}.${fieldName}`]: field }; + }) + .reduce((o, fieldObj) => Object.assign(o, fieldObj), {}) + ; + }) + // flatten the exp's execution infos + .reduce((o, blockObj) => Object.assign(o, blockObj), {}) + ; + + // get total timing info + let expTiming = { + 'total_execution_time': Object.entries(blockTiming) + .filter(([name, val]) => name.includes('linear_execution_time')) + .map(([name, val]) => val) + .reduce((n, v) => n + v, 0) + }; + + // concat all objs + let allObj = Object.assign({}, + results, + globalVars, + blockVars, + blockTiming, + expTiming + ); + + return allObj; + }; + + // get all result objs that have a type that starts with 'plot/' + // these results can be displayed in a plot + const getPlottablesFromExpName = (expName) => { + return Object.entries(getVarProp(expData[expName].results)) + .filter(([res_name, obj]) => obj.type.startsWith('plot/')) + .map(([res_name, obj]) => Object.assign(obj, { label: res_name })); + }; + + // make sure there are no invalid experiments in the report's groups + // invalid -> in a group but not in the report + const cleanGroups = (validExpNames) => { + GroupsService.groups.forEach(g => { + g.experiments.forEach(e => { + // when the user can see the exp, the full name of the exp is available, + // including the user who made it, and the toolchain + const fullNameValid = validExpNames.includes(e); + // when the exp isnt able to be seen by the user, + // only the actual name of the exp is available + const partNameValid = validExpNames.includes(e.split('/').pop()); + if(!(fullNameValid || partNameValid)){ + g.removeExperiment(e); + } + }); + }); + }; + + // fetch the exp data and process it + const loadExperiments = () => { + let expFetch; + const namePath = UrlService.getByNamePath(); + const numberPath = UrlService.getByNumberPath(); + + if(namePath && namePath.length > 0){ + // get exps by report num + const user = namePath.split('/')[1]; + const name = namePath.split('/')[2]; + expFetch = experimentFactory.getAllExperimentResultsForAuthor(user, name, ''); + } else if(numberPath && numberPath.length > 0){ + //by report author + const num = numberPath.split('/')[1]; + expFetch = experimentFactory.getAllExperimentResults('', num); + } + + return expFetch + .then((exps) => { + // save the exp names as they were given by the server + Object.keys(exps).forEach(e => rawExpNames.push(e)); + + // process the given exp data + Object.entries(exps) + .forEach(([expName, exp]) => { + // depending on the permissions of the user + // when viewing this report + // the author/toolchain may be hidden. + // to be safe, always process + // both the given name (either the full or short name) + const shortName = expName.split('/').pop(); + for(let n of [shortName, expName]){ + expData[n] = exp; + tableData[n] = getTableablesFromExpName(n); + plotData[n] = getPlottablesFromExpName(n); + } + }); + + cleanGroups(rawExpNames); + + return expData; + }); + }; + + const cachedDeletedExperiments = []; + + const deleteExperiment = function(expName) { + // delete from all groups + GroupsService.groups.forEach(g => g.removeExperiment(expName)); + + // delete from expNames + const expNameIdx = this.experimentNames.indexOf(expName); + this.experimentNames.splice(expNameIdx, 1); + + // delete from expData + delete this.experiments[expName]; + + cachedDeletedExperiments.push(expName); + }; + + loadExperiments(); + + return { + experimentNames: rawExpNames, + experiments: expData, + plottables: plotData, + tableables: tableData, + getAnalyzerFromExpName, + loadExperiments, + deleteExperiment, + cachedDeletedExperiments + }; }]); diff --git a/beat/web/reports/static/reports/app/services/groupsService.js b/beat/web/reports/static/reports/app/services/groupsService.js index 29d6f6da8..bb77b6951 100644 --- a/beat/web/reports/static/reports/app/services/groupsService.js +++ b/beat/web/reports/static/reports/app/services/groupsService.js @@ -23,237 +23,237 @@ /* * GroupsService * Desc: - * The main datastore for the reports app, holding the tree of - * relationships between groups, experiments, aliases, and report items + * The main datastore for the reports app, holding the tree of + * relationships between groups, experiments, aliases, and report items */ angular.module('reportApp').factory('GroupsService', ['reportFactory', function(reportFactory){ - let groupsServiceInstance = {}; - // experiments of reports are in arbitrary groups, - // in a many-to-many relationship - let groupData = []; - - // is the report editable? - // set by the reportController when saving report data - groupsServiceInstance.isEditable = undefined; - groupsServiceInstance.setEditable = (val) => { - if(val !== true && val !== false){ - throw new Error(`invalid isEditable: ${JSON.stringify(val)}`); - } - if(groupsServiceInstance.isEditable !== undefined){ - throw new Error(`isEditable already set: ${JSON.stringify(groupsServiceInstance.isEditable)}`); - } - groupsServiceInstance.isEditable = val; - }; - - // represents a Group in the report - // has a name and a list of experiments that belong to it - class Group { - constructor (name) { - this._name = name; - this._analyzer = ''; - this._experimentNames = new Set(); - this._reportItems = []; - this._aliases = {}; - } - - // get the experiment names in this group - get experiments () { - return Array.from(this._experimentNames); - } - - // get the report items in this group - get reportItems () { - return Array.from(this._reportItems); - } - - // get the group name - get name () { - return this._name; - } - - // get the analyzer of the experiments in this group - get analyzer () { - return this._analyzer; - } - - // gets the aliases for the experiments in the group - get aliases () { - return this._aliases; - } - - set analyzer (analyzer) { - this._analyzer = analyzer; - } - - // add an exp to this group - // optionally sets the analyzer - // initializes the new exp's alias to its name - addExperiment (expName, analyzer) { - let res = this._experimentNames.add(expName); - if(this._experimentNames.size === 1 && analyzer && analyzer.length > 0){ - this.analyzer = analyzer; - } - if(!this.aliases[expName]){ - let autoAlias = expName.split('/').pop(); - this.setAliasToExperiment(autoAlias, expName); - } - - return res; - } - - // rm an exp from this group as well as its alias - removeExperiment (expName) { - let res = this._experimentNames.delete(expName); - if(this._experimentNames.size === 0){ - this.analyzer = ''; - this.reportItems.forEach(i => this.removeReportItem(i.id)); - } - this.unsetExperimentAlias(expName); - - return res; - } - - // add an item (table, plot, or text block) to this group - // if the id already exists, it just replaces the old - // element with the new one - addReportItem (id, content) { - let newEl = { - id, - content - }; - - let alreadyAddedEl = this._reportItems.find(i => i.id === id); - if(alreadyAddedEl){ - let idx = this._reportItems.indexOf(alreadyAddedEl); - return this._reportItems.splice(idx, 1, newEl); - } else { - return this._reportItems.push(newEl); - } - } - - // rm a report item from this group - removeReportItem (id) { - let idx = this._reportItems - .indexOf(this._reportItems.find(o => o.id === id)); - - return this._reportItems.splice(idx, 1); - } - - // (re)sets an alias to an experiment in the group - setAliasToExperiment (alias, expName) { - if(!this.experiments.includes(expName)){ - return false; - } - - this._aliases[expName] = alias; - } - - // unsets an alias for an experiment - unsetExperimentAlias (expName) { - return delete this._aliases[expName]; - } - }; - - // gets groups - groupsServiceInstance.groups = groupData; - - // serializes groups as an object with form: - // { - // <group name 1>: { - // experiments: [], - // reportItems: [], - // analyzer: '', - // aliases: {}, - // idx: 1 - // }, - // ... - // } - // the 'idx' saves the ordering of the groups - groupsServiceInstance.serializeGroups = () => { - return groupData - .map((g, i) => { return { - [g.name]: { - experiments: g.experiments, - reportItems: g.reportItems, - analyzer: g.analyzer, - aliases: g.aliases, - idx: i - } - }; - }) - .reduce((o, g) => Object.assign(o, g), {}); - }; - - // create a new group for the report - // returns false if it already exists - // returns the newly added group if successful - groupsServiceInstance.createGroup = (name) => { - if(typeof name !== 'string'){ - throw new Error(`new group name is not a string: ${JSON.stringify(name)}`); - } - - if(groupData.find(g => g.name === name)){ - return false; - } - - let g = new Group(name); - - groupData.push(g); - return g; - }; - - // delete a group - // via MUTATING the groupdata - groupsServiceInstance.deleteGroup = (name) => { - let idx = groupData.indexOf(groupData.find(g => g.name === name)); - if (idx > -1) { - groupData.splice(idx, 1); - } - }; - - // load group info from the serialized format: - // { - // <group name 1>: { - // experiments: [], - // reportItems: [], - // analyzer: '', - // aliases: {}, - // idx: 1 - // }, - // ... - // } - groupsServiceInstance.loadGroups = (data) => { - // wipe data - groupData.splice(0, groupData.length); - let safeData = data || {}; - - Object.entries(safeData) - // sometimes we get an empty string for name for some reason - .filter(([groupName, gData]) => groupName.length > 0) - // sort using the saved index values - .sort(([aName, a], [bName, b]) => { - // if these are undefined, the sort func handles it - let ia = a.idx; - let ib = b.idx; - return ia - ib; - }) - .forEach(([groupName, gData]) => { - let g = groupsServiceInstance.createGroup(groupName); - - // default group data to empty - let analyzer = gData.analyzer || ''; - let experiments = gData.experiments || []; - let reportItems = gData.reportItems || []; - let aliases = gData.aliases || {}; - - // save fields to group - // by MUTATING the group obj - g.analyzer = analyzer; - experiments.forEach(n => g.addExperiment(n)); - reportItems.forEach(i => g.addReportItem(i.id, i.content)); - Object.entries(aliases).forEach(([e, a]) => g.setAliasToExperiment(a, e)); - }); - }; - - return groupsServiceInstance; + let groupsServiceInstance = {}; + // experiments of reports are in arbitrary groups, + // in a many-to-many relationship + let groupData = []; + + // is the report editable? + // set by the reportController when saving report data + groupsServiceInstance.isEditable = undefined; + groupsServiceInstance.setEditable = (val) => { + if(val !== true && val !== false){ + throw new Error(`invalid isEditable: ${JSON.stringify(val)}`); + } + if(groupsServiceInstance.isEditable !== undefined){ + throw new Error(`isEditable already set: ${JSON.stringify(groupsServiceInstance.isEditable)}`); + } + groupsServiceInstance.isEditable = val; + }; + + // represents a Group in the report + // has a name and a list of experiments that belong to it + class Group { + constructor (name) { + this._name = name; + this._analyzer = ''; + this._experimentNames = new Set(); + this._reportItems = []; + this._aliases = {}; + } + + // get the experiment names in this group + get experiments () { + return Array.from(this._experimentNames); + } + + // get the report items in this group + get reportItems () { + return Array.from(this._reportItems); + } + + // get the group name + get name () { + return this._name; + } + + // get the analyzer of the experiments in this group + get analyzer () { + return this._analyzer; + } + + // gets the aliases for the experiments in the group + get aliases () { + return this._aliases; + } + + set analyzer (analyzer) { + this._analyzer = analyzer; + } + + // add an exp to this group + // optionally sets the analyzer + // initializes the new exp's alias to its name + addExperiment (expName, analyzer) { + let res = this._experimentNames.add(expName); + if(this._experimentNames.size === 1 && analyzer && analyzer.length > 0){ + this.analyzer = analyzer; + } + if(!this.aliases[expName]){ + let autoAlias = expName.split('/').pop(); + this.setAliasToExperiment(autoAlias, expName); + } + + return res; + } + + // rm an exp from this group as well as its alias + removeExperiment (expName) { + let res = this._experimentNames.delete(expName); + if(this._experimentNames.size === 0){ + this.analyzer = ''; + this.reportItems.forEach(i => this.removeReportItem(i.id)); + } + this.unsetExperimentAlias(expName); + + return res; + } + + // add an item (table, plot, or text block) to this group + // if the id already exists, it just replaces the old + // element with the new one + addReportItem (id, content) { + let newEl = { + id, + content + }; + + let alreadyAddedEl = this._reportItems.find(i => i.id === id); + if(alreadyAddedEl){ + let idx = this._reportItems.indexOf(alreadyAddedEl); + return this._reportItems.splice(idx, 1, newEl); + } else { + return this._reportItems.push(newEl); + } + } + + // rm a report item from this group + removeReportItem (id) { + let idx = this._reportItems + .indexOf(this._reportItems.find(o => o.id === id)); + + return this._reportItems.splice(idx, 1); + } + + // (re)sets an alias to an experiment in the group + setAliasToExperiment (alias, expName) { + if(!this.experiments.includes(expName)){ + return false; + } + + this._aliases[expName] = alias; + } + + // unsets an alias for an experiment + unsetExperimentAlias (expName) { + return delete this._aliases[expName]; + } + }; + + // gets groups + groupsServiceInstance.groups = groupData; + + // serializes groups as an object with form: + // { + // <group name 1>: { + // experiments: [], + // reportItems: [], + // analyzer: '', + // aliases: {}, + // idx: 1 + // }, + // ... + // } + // the 'idx' saves the ordering of the groups + groupsServiceInstance.serializeGroups = () => { + return groupData + .map((g, i) => { return { + [g.name]: { + experiments: g.experiments, + reportItems: g.reportItems, + analyzer: g.analyzer, + aliases: g.aliases, + idx: i + } + }; + }) + .reduce((o, g) => Object.assign(o, g), {}); + }; + + // create a new group for the report + // returns false if it already exists + // returns the newly added group if successful + groupsServiceInstance.createGroup = (name) => { + if(typeof name !== 'string'){ + throw new Error(`new group name is not a string: ${JSON.stringify(name)}`); + } + + if(groupData.find(g => g.name === name)){ + return false; + } + + let g = new Group(name); + + groupData.push(g); + return g; + }; + + // delete a group + // via MUTATING the groupdata + groupsServiceInstance.deleteGroup = (name) => { + let idx = groupData.indexOf(groupData.find(g => g.name === name)); + if (idx > -1) { + groupData.splice(idx, 1); + } + }; + + // load group info from the serialized format: + // { + // <group name 1>: { + // experiments: [], + // reportItems: [], + // analyzer: '', + // aliases: {}, + // idx: 1 + // }, + // ... + // } + groupsServiceInstance.loadGroups = (data) => { + // wipe data + groupData.splice(0, groupData.length); + let safeData = data || {}; + + Object.entries(safeData) + // sometimes we get an empty string for name for some reason + .filter(([groupName, gData]) => groupName.length > 0) + // sort using the saved index values + .sort(([aName, a], [bName, b]) => { + // if these are undefined, the sort func handles it + let ia = a.idx; + let ib = b.idx; + return ia - ib; + }) + .forEach(([groupName, gData]) => { + let g = groupsServiceInstance.createGroup(groupName); + + // default group data to empty + let analyzer = gData.analyzer || ''; + let experiments = gData.experiments || []; + let reportItems = gData.reportItems || []; + let aliases = gData.aliases || {}; + + // save fields to group + // by MUTATING the group obj + g.analyzer = analyzer; + experiments.forEach(n => g.addExperiment(n)); + reportItems.forEach(i => g.addReportItem(i.id, i.content)); + Object.entries(aliases).forEach(([e, a]) => g.setAliasToExperiment(a, e)); + }); + }; + + return groupsServiceInstance; }]); diff --git a/beat/web/reports/static/reports/app/services/plotService.js b/beat/web/reports/static/reports/app/services/plotService.js index 4a11fa0fc..e5b576d01 100644 --- a/beat/web/reports/static/reports/app/services/plotService.js +++ b/beat/web/reports/static/reports/app/services/plotService.js @@ -23,198 +23,198 @@ /* * PlotService * Desc: - * Manages the plots in the report, - * including: - * - Adding new/saved - * - Configuring - * - Deleting - * - Rendering + * Manages the plots in the report, + * including: + * - Adding new/saved + * - Configuring + * - Deleting + * - Rendering */ angular.module('reportApp').factory('PlotService', ['UrlService', function(UrlService){ - const ps = { - // these are provided by ReportService - plotters: [], - defaultPlotters: [], - plotterParameters: [], - reportNumber: undefined - }; - - // this 'queue' idea is the solution to trying to load plots before we receive all the data - // from the server that we need (report info, exp info, etc.). - // until the queue is processed (after which, no queue is needed), - // plots that want to be rendered are added to a queue to wait. - const queue = []; - let noQueueNeeded = false; - - const getDefaults = (plotType) => ps.defaultPlotters.find(p => p.dataformat === plotType); - - // data to be used in to interact with the plot - // users can change the plotter used if theres more than 1 available for that - // plot type - const getPossiblePlotters = (itemContent) => - ps.plotters.filter(p => p.dataformat === itemContent.type).map(p => p.name); - - // users can choose which params to use if more than 1 available - // for that plotter - const getPossibleConfigs = (plotter) => - ps.plotterParameters.filter(pp => plotter.id === pp.plotter).map(pp => pp.name); - - const getPlotter = (itemContent) => { - const savedPlotterName = itemContent.savedPlotter; - // defaults obj, in case we're using defaults - const defaults = getDefaults(itemContent.type); - - // a plot obj can have different plotters, - const plotter = ps.plotters.find(p => p.name === itemContent.savedPlotter) || - ps.plotters.find(p => p.name === defaults.plotter); - - return plotter; - }; - - const getPlotterConfig = (itemContent) => { - // defaults obj, in case we're using defaults - const defaults = getDefaults(itemContent.type); - - // each plotter obj can each have different configurations (plotter parameter instances) - const config = ps.plotterParameters.find(pp => pp.name === itemContent.savedConfig) || - ps.plotterParameters.find(pp => pp.name === defaults.parameter); - - return config; - }; - - // constructs the payload to send to the server - const constructPlotInfo = (group, itemId) => { - const content = group.reportItems.find(i => i.id === itemId).content; - const plotter = getPlotter(content); - const config = getPlotterConfig(content); - - // the data to be sent to the server - const requestData = { - report_number: ps.reportNumber, - // exps in the group - experiment: group.experiments, - // group's analyzer - analyzer: [group.analyzer], - // ? - output: [content.name], - // plotter to use - plotter: plotter.name, - // config to use - parameter: config.name, - // string for making the legend in the plot - legend: group.experiments.map(e => group.aliases[e]).join('&'), - // whether one plot with all the exps' data, - // or one plot for each exp - merged: content.merged === undefined ? true : content.merged - }; - - const possiblePlotters = getPossiblePlotters(content); - const possibleConfigs = getPossibleConfigs(plotter); - - const returnStruct = [requestData, possiblePlotters, possibleConfigs]; - - return returnStruct; - }; - - const fetchDownload = (requestData, contentType) => { - const urlPrefix = ''; - - // override the 'merged' property to always request the merged version. - // requested the unmerged version only downloads the first image. - requestData[0] = Object.assign({}, requestData[0], { merged: true }); - - return new Promise((resolve, reject) => { - beat.experiments.utils.getPlotData( - // url_prefix - urlPrefix, - // spread out the request data to fill the next 3 spots - ...requestData, - // content type: png, jpeg, pdf - contentType, - // callback - (...args) => { - // resolve promise - resolve(...args); - } - ); - }); - }; - - // makes the call to the server, via some helper funcs found in the global namespace - const fetchRender = (requestData, containerId, onRenderCallback) => { - // the 'url_prefix' field found throughout the BEAT code is confusing, - // but it seems to always be an empty string now - const urlPrefix = '';//UrlService.getApiSegment().split('/').filter(s => s.length > 0).join('/'); - - // this func *cannot* be promisified! the callback is fired whenever the plot is re-rendered. - beat.experiments.utils.displayPlot( - // url_prefix - urlPrefix, - // element to append render to - document.querySelector(`#${containerId}`), - // spread out the request data to fill the next 3 spots - ...requestData, - // dont replace inner content - false, - // callback - (plotter, config, merged) => { - onRenderCallback(plotter, config, merged); - } - ); - }; - - // helper func to process a request to plot - const processDownload = (group, itemId, contentType) => { - const reqData = constructPlotInfo(group, itemId); - - return fetchDownload(reqData, contentType); - }; - - // helper func to process a request to plot - const processItem = (group, itemId, containerId, onRenderCallback) => { - const reqData = constructPlotInfo(group, itemId); - - fetchRender(reqData, containerId, onRenderCallback); - }; - - // used if we arent ready to directly service requests - const addItemToQueue = (group, itemId, containerId, onRenderCallback) => { - queue.push([ - group, - itemId, - containerId, - onRenderCallback - ]); - }; - - // called by ReportService, - // or by someone having this report-level info. - // processes the queue and sets the plotService state such that we dont need to use - // the queue after this - ps.processQueue = (rsPlotters, rsDefaultPlotters, rsPlotterParameters, rsReportNumber) => { - rsPlotters.forEach(p => ps.plotters.push(p)); - rsDefaultPlotters.forEach(dp => ps.defaultPlotters.push(dp)); - rsPlotterParameters.forEach(pp => ps.plotterParameters.push(pp)); - ps.reportNumber = rsReportNumber; - - noQueueNeeded = true; - const promises = queue.map(q => processItem(...q)); - queue.length = 0; - - return promises; - }; - - // chooses whether to add the plot request to a queue or service directly - // args: group, itemId, containerId, onRenderCallback - ps.addPlot = (...args) => noQueueNeeded ? processItem(...args) : addItemToQueue(...args); - - // args: group, itemId, contentType - ps.downloadPlot = (...args) => processDownload(...args); - - ps.getPlotter = getPlotter; - ps.getPlotterConfig = getPlotterConfig; - ps.getPossiblePlotters = getPossiblePlotters; - ps.getPossibleConfigs = getPossibleConfigs; - - return ps; + const ps = { + // these are provided by ReportService + plotters: [], + defaultPlotters: [], + plotterParameters: [], + reportNumber: undefined + }; + + // this 'queue' idea is the solution to trying to load plots before we receive all the data + // from the server that we need (report info, exp info, etc.). + // until the queue is processed (after which, no queue is needed), + // plots that want to be rendered are added to a queue to wait. + const queue = []; + let noQueueNeeded = false; + + const getDefaults = (plotType) => ps.defaultPlotters.find(p => p.dataformat === plotType); + + // data to be used in to interact with the plot + // users can change the plotter used if theres more than 1 available for that + // plot type + const getPossiblePlotters = (itemContent) => + ps.plotters.filter(p => p.dataformat === itemContent.type).map(p => p.name); + + // users can choose which params to use if more than 1 available + // for that plotter + const getPossibleConfigs = (plotter) => + ps.plotterParameters.filter(pp => plotter.id === pp.plotter).map(pp => pp.name); + + const getPlotter = (itemContent) => { + const savedPlotterName = itemContent.savedPlotter; + // defaults obj, in case we're using defaults + const defaults = getDefaults(itemContent.type); + + // a plot obj can have different plotters, + const plotter = ps.plotters.find(p => p.name === itemContent.savedPlotter) || + ps.plotters.find(p => p.name === defaults.plotter); + + return plotter; + }; + + const getPlotterConfig = (itemContent) => { + // defaults obj, in case we're using defaults + const defaults = getDefaults(itemContent.type); + + // each plotter obj can each have different configurations (plotter parameter instances) + const config = ps.plotterParameters.find(pp => pp.name === itemContent.savedConfig) || + ps.plotterParameters.find(pp => pp.name === defaults.parameter); + + return config; + }; + + // constructs the payload to send to the server + const constructPlotInfo = (group, itemId) => { + const content = group.reportItems.find(i => i.id === itemId).content; + const plotter = getPlotter(content); + const config = getPlotterConfig(content); + + // the data to be sent to the server + const requestData = { + report_number: ps.reportNumber, + // exps in the group + experiment: group.experiments, + // group's analyzer + analyzer: [group.analyzer], + // ? + output: [content.name], + // plotter to use + plotter: plotter.name, + // config to use + parameter: config.name, + // string for making the legend in the plot + legend: group.experiments.map(e => group.aliases[e]).join('&'), + // whether one plot with all the exps' data, + // or one plot for each exp + merged: content.merged === undefined ? true : content.merged + }; + + const possiblePlotters = getPossiblePlotters(content); + const possibleConfigs = getPossibleConfigs(plotter); + + const returnStruct = [requestData, possiblePlotters, possibleConfigs]; + + return returnStruct; + }; + + const fetchDownload = (requestData, contentType) => { + const urlPrefix = ''; + + // override the 'merged' property to always request the merged version. + // requested the unmerged version only downloads the first image. + requestData[0] = Object.assign({}, requestData[0], { merged: true }); + + return new Promise((resolve, reject) => { + beat.experiments.utils.getPlotData( + // url_prefix + urlPrefix, + // spread out the request data to fill the next 3 spots + ...requestData, + // content type: png, jpeg, pdf + contentType, + // callback + (...args) => { + // resolve promise + resolve(...args); + } + ); + }); + }; + + // makes the call to the server, via some helper funcs found in the global namespace + const fetchRender = (requestData, containerId, onRenderCallback) => { + // the 'url_prefix' field found throughout the BEAT code is confusing, + // but it seems to always be an empty string now + const urlPrefix = '';//UrlService.getApiSegment().split('/').filter(s => s.length > 0).join('/'); + + // this func *cannot* be promisified! the callback is fired whenever the plot is re-rendered. + beat.experiments.utils.displayPlot( + // url_prefix + urlPrefix, + // element to append render to + document.querySelector(`#${containerId}`), + // spread out the request data to fill the next 3 spots + ...requestData, + // dont replace inner content + false, + // callback + (plotter, config, merged) => { + onRenderCallback(plotter, config, merged); + } + ); + }; + + // helper func to process a request to plot + const processDownload = (group, itemId, contentType) => { + const reqData = constructPlotInfo(group, itemId); + + return fetchDownload(reqData, contentType); + }; + + // helper func to process a request to plot + const processItem = (group, itemId, containerId, onRenderCallback) => { + const reqData = constructPlotInfo(group, itemId); + + fetchRender(reqData, containerId, onRenderCallback); + }; + + // used if we arent ready to directly service requests + const addItemToQueue = (group, itemId, containerId, onRenderCallback) => { + queue.push([ + group, + itemId, + containerId, + onRenderCallback + ]); + }; + + // called by ReportService, + // or by someone having this report-level info. + // processes the queue and sets the plotService state such that we dont need to use + // the queue after this + ps.processQueue = (rsPlotters, rsDefaultPlotters, rsPlotterParameters, rsReportNumber) => { + rsPlotters.forEach(p => ps.plotters.push(p)); + rsDefaultPlotters.forEach(dp => ps.defaultPlotters.push(dp)); + rsPlotterParameters.forEach(pp => ps.plotterParameters.push(pp)); + ps.reportNumber = rsReportNumber; + + noQueueNeeded = true; + const promises = queue.map(q => processItem(...q)); + queue.length = 0; + + return promises; + }; + + // chooses whether to add the plot request to a queue or service directly + // args: group, itemId, containerId, onRenderCallback + ps.addPlot = (...args) => noQueueNeeded ? processItem(...args) : addItemToQueue(...args); + + // args: group, itemId, contentType + ps.downloadPlot = (...args) => processDownload(...args); + + ps.getPlotter = getPlotter; + ps.getPlotterConfig = getPlotterConfig; + ps.getPossiblePlotters = getPossiblePlotters; + ps.getPossibleConfigs = getPossibleConfigs; + + return ps; }]); diff --git a/beat/web/reports/static/reports/app/services/reportService.js b/beat/web/reports/static/reports/app/services/reportService.js index ad68469fe..ff9a97f58 100644 --- a/beat/web/reports/static/reports/app/services/reportService.js +++ b/beat/web/reports/static/reports/app/services/reportService.js @@ -23,118 +23,118 @@ /* * ReportService * Desc: - * Consumes the "report" object from the API and digests it into helper - * funcs and report-wide info. Basically an adaptor & bootstrap-er. + * Consumes the "report" object from the API and digests it into helper + * funcs and report-wide info. Basically an adaptor & bootstrap-er. */ angular.module('reportApp').factory('ReportService', ['GroupsService', 'plotterFactory', 'PlotService', 'reportFactory', 'UrlService', 'ErrorService', function(GroupsService, plotterFactory, PlotService, reportFactory, UrlService, ErrorService){ - const rs = {}; - - rs.isAnonymous = undefined; - rs.isOwner = undefined; - rs.status = undefined; - rs.number = undefined; - rs.author = undefined; - rs.name = undefined; - - rs.plotters = []; - rs.defaultPlotters = []; - rs.plotterParameters = []; - - // processed the report data received from the server, - // and bootstraps the state of various services - rs.processReport = (report) => { - - // useful info about the app - rs.isAnonymous = report.anonymous; - rs.isOwner = report.is_owner; - rs.status = report.status; - rs.number = report.number; - rs.author = report.author; - rs.name = report.name.split('/').length > 1 ? report.name.split('/')[1] : null; - - // start up our GroupsService - GroupsService.loadGroups(report.content.groups); - // if the report should not change, - // add a nice layer of immutability by freezing the Group tree - // (see GroupsService for more info) - const isEditable = rs.isOwner && rs.status === 'editable' && !rs.isAnonymous; - GroupsService.setEditable(isEditable); - - // fetch all our plotter data - // these three fetches do not depend on eachother - const pPlotters = plotterFactory.getPlotters('') - .then(res => { - res.data.forEach(p => rs.plotters.push(p)); - }); - - const pDefaults = plotterFactory.getDefaultPlotters('') - .then(res => { - res.data.forEach(p => rs.defaultPlotters.push(p)); - }); - - const pParams = plotterFactory.getPlotterParameter('') - .then(res => { - res.data.forEach(p => rs.plotterParameters.push(p)); - }); - - // process the fetched plot info - return Promise.all([pPlotters, pDefaults, pParams]) - .then(() => PlotService.processQueue(rs.plotters, rs.defaultPlotters, rs.plotterParameters, rs.number)) - ; - }; - - // fetch the report data using either the by-name or by-number scheme, - // according to what URLService found - rs.fetchReport = () => { - const nameSeg = UrlService.getNameSegment(); - const numSeg = UrlService.getNumberSegment(); - - if(nameSeg){ - // the nameSeg is something like 'report/<user>/<report name>/' - let [user, reportId] = nameSeg.split('/').filter(s => s.length > 0).slice(1); - - return reportFactory.getReportInformation(user, reportId, '') - .then(res => rs.processReport(res.data)); - } else if(numSeg) { - // the numSeg is something like 'report/<report number>/' - let [number] = numSeg.split('/').filter(s => s.length > 0).slice(1); - - return reportFactory.getReportInformationFromNumber(number, '') - .then(res => rs.processReport(res.data)); - } else { - throw new Error('UrlService could not parse the current URL'); - } - }; - - // publish the report - rs.publishReport = (openSourceAlgs) => { - return reportFactory.publishReport( - '', - rs.author, - rs.name, - openSourceAlgs.length && openSourceAlgs.length > 0 ? data : undefined - ) - .catch(error => { - throw error; - }); - ; - }; - - // lock the report - rs.lockReport = () => { - return reportFactory.lockReport( - rs.author, - rs.name, - '' - ) - .catch(error => { - throw error; - }); - ; - }; - - rs.fetchReport() - .catch(e => ErrorService.logError(e, `Could not load report.`)); - - return rs; + const rs = {}; + + rs.isAnonymous = undefined; + rs.isOwner = undefined; + rs.status = undefined; + rs.number = undefined; + rs.author = undefined; + rs.name = undefined; + + rs.plotters = []; + rs.defaultPlotters = []; + rs.plotterParameters = []; + + // processed the report data received from the server, + // and bootstraps the state of various services + rs.processReport = (report) => { + + // useful info about the app + rs.isAnonymous = report.anonymous; + rs.isOwner = report.is_owner; + rs.status = report.status; + rs.number = report.number; + rs.author = report.author; + rs.name = report.name.split('/').length > 1 ? report.name.split('/')[1] : null; + + // start up our GroupsService + GroupsService.loadGroups(report.content.groups); + // if the report should not change, + // add a nice layer of immutability by freezing the Group tree + // (see GroupsService for more info) + const isEditable = rs.isOwner && rs.status === 'editable' && !rs.isAnonymous; + GroupsService.setEditable(isEditable); + + // fetch all our plotter data + // these three fetches do not depend on eachother + const pPlotters = plotterFactory.getPlotters('') + .then(res => { + res.data.forEach(p => rs.plotters.push(p)); + }); + + const pDefaults = plotterFactory.getDefaultPlotters('') + .then(res => { + res.data.forEach(p => rs.defaultPlotters.push(p)); + }); + + const pParams = plotterFactory.getPlotterParameter('') + .then(res => { + res.data.forEach(p => rs.plotterParameters.push(p)); + }); + + // process the fetched plot info + return Promise.all([pPlotters, pDefaults, pParams]) + .then(() => PlotService.processQueue(rs.plotters, rs.defaultPlotters, rs.plotterParameters, rs.number)) + ; + }; + + // fetch the report data using either the by-name or by-number scheme, + // according to what URLService found + rs.fetchReport = () => { + const nameSeg = UrlService.getNameSegment(); + const numSeg = UrlService.getNumberSegment(); + + if(nameSeg){ + // the nameSeg is something like 'report/<user>/<report name>/' + let [user, reportId] = nameSeg.split('/').filter(s => s.length > 0).slice(1); + + return reportFactory.getReportInformation(user, reportId, '') + .then(res => rs.processReport(res.data)); + } else if(numSeg) { + // the numSeg is something like 'report/<report number>/' + let [number] = numSeg.split('/').filter(s => s.length > 0).slice(1); + + return reportFactory.getReportInformationFromNumber(number, '') + .then(res => rs.processReport(res.data)); + } else { + throw new Error('UrlService could not parse the current URL'); + } + }; + + // publish the report + rs.publishReport = (openSourceAlgs) => { + return reportFactory.publishReport( + '', + rs.author, + rs.name, + openSourceAlgs.length && openSourceAlgs.length > 0 ? data : undefined + ) + .catch(error => { + throw error; + }); + ; + }; + + // lock the report + rs.lockReport = () => { + return reportFactory.lockReport( + rs.author, + rs.name, + '' + ) + .catch(error => { + throw error; + }); + ; + }; + + rs.fetchReport() + .catch(e => ErrorService.logError(e, `Could not load report.`)); + + return rs; }]); diff --git a/beat/web/reports/static/reports/app/services/urlService.js b/beat/web/reports/static/reports/app/services/urlService.js index f42d6af73..861f52a65 100644 --- a/beat/web/reports/static/reports/app/services/urlService.js +++ b/beat/web/reports/static/reports/app/services/urlService.js @@ -23,78 +23,78 @@ /* * UrlService * Desc: - * Helper functionality to generate URLs for the reports app + * Helper functionality to generate URLs for the reports app */ angular.module('reportApp').factory('UrlService', [function(){ - // const path segments - const experimentSegment = 'experiments/'; - const blockSegment = 'algorithms/'; - const databaseSegment = 'databases/'; - const apiSegment = 'api/v1/'; + // const path segments + const experimentSegment = 'experiments/'; + const blockSegment = 'algorithms/'; + const databaseSegment = 'databases/'; + const apiSegment = 'api/v1/'; - // the protocol, address, and port number - let prefix = ''; - // the path to the current report, by username & report name - let reportByName; - // the path to the current report, by report number - let reportByNumber; + // the protocol, address, and port number + let prefix = ''; + // the path to the current report, by username & report name + let reportByName; + // the path to the current report, by report number + let reportByNumber; - // extracts info from jQuery's ajaxSettings current URL - const extractUsingCurrentUrl = () => { - const url = $.ajaxSettings.url; - const idxSplit = url.indexOf('reports'); - const path = url.slice(idxSplit).replace(/#\/?$/, ''); + // extracts info from jQuery's ajaxSettings current URL + const extractUsingCurrentUrl = () => { + const url = $.ajaxSettings.url; + const idxSplit = url.indexOf('reports'); + const path = url.slice(idxSplit).replace(/#\/?$/, ''); - prefix = url.slice(0, idxSplit); - // find how many '/' are in path via splitting str on '/' - if(path.split('/').length === 3){ - // report number - reportByNumber = path; - } else { - // report user & name - reportByName = path; - } - }; + prefix = url.slice(0, idxSplit); + // find how many '/' are in path via splitting str on '/' + if(path.split('/').length === 3){ + // report number + reportByNumber = path; + } else { + // report user & name + reportByName = path; + } + }; - const getPrefix = () => prefix; + const getPrefix = () => prefix; - const getApiSegment = () => apiSegment; - const getNameSegment = () => reportByName; - const getNumberSegment = () => reportByNumber; + const getApiSegment = () => apiSegment; + const getNameSegment = () => reportByName; + const getNumberSegment = () => reportByNumber; - const experimentPath = () => `${prefix}${experimentSegment}`; - const blockPath = () => `${prefix}${blockSegment}`; - const databasePath = () => `${prefix}${databaseSegment}`; - const apiPath = () => `${prefix}${apiSegment}`; + const experimentPath = () => `${prefix}${experimentSegment}`; + const blockPath = () => `${prefix}${blockSegment}`; + const databasePath = () => `${prefix}${databaseSegment}`; + const apiPath = () => `${prefix}${apiSegment}`; - const getExperimentUrl = (experimentName) => `${experimentPath()}${experimentName}/`; - const getBlockUrl = (blockName) => `${blockPath()}${blockName}/`; - const getDatabaseUrl = (databaseName) => `${databasePath()}${databaseName}/`; - const getApiUrl = (apiSubpath) => `${apiPath()}${apiSubpath}`; + const getExperimentUrl = (experimentName) => `${experimentPath()}${experimentName}/`; + const getBlockUrl = (blockName) => `${blockPath()}${blockName}/`; + const getDatabaseUrl = (databaseName) => `${databasePath()}${databaseName}/`; + const getApiUrl = (apiSubpath) => `${apiPath()}${apiSubpath}`; - const getCompileRstUrl = () => `${getApiUrl(`${reportByName || reportByNumber}rst/`)}`; - const getRemoveExperimentUrl = () => `${getApiUrl(`${reportByName}remove/`)}`; - const getByNamePath = () => reportByName; - const getByNumberPath = () => reportByNumber; - const getExperimentListPath = () => experimentPath(); + const getCompileRstUrl = () => `${getApiUrl(`${reportByName || reportByNumber}rst/`)}`; + const getRemoveExperimentUrl = () => `${getApiUrl(`${reportByName}remove/`)}`; + const getByNamePath = () => reportByName; + const getByNumberPath = () => reportByNumber; + const getExperimentListPath = () => experimentPath(); - const isViewmode = () => reportByNumber ? true : false; + const isViewmode = () => reportByNumber ? true : false; - extractUsingCurrentUrl(); + extractUsingCurrentUrl(); - return { - getExperimentUrl, - getBlockUrl, - getDatabaseUrl, - getCompileRstUrl, - getRemoveExperimentUrl, - getByNamePath, - getByNumberPath, - getApiSegment, - getNameSegment, - getNumberSegment, - getPrefix, - getExperimentListPath, - isViewmode - }; + return { + getExperimentUrl, + getBlockUrl, + getDatabaseUrl, + getCompileRstUrl, + getRemoveExperimentUrl, + getByNamePath, + getByNumberPath, + getApiSegment, + getNameSegment, + getNumberSegment, + getPrefix, + getExperimentListPath, + isViewmode + }; }]); diff --git a/beat/web/reports/static/reports/js/base_64_encoder_decoder.js b/beat/web/reports/static/reports/js/base_64_encoder_decoder.js index 6d794c27d..ccf3564f3 100644 --- a/beat/web/reports/static/reports/js/base_64_encoder_decoder.js +++ b/beat/web/reports/static/reports/js/base_64_encoder_decoder.js @@ -1,21 +1,21 @@ /* * Copyright (c) 2016 Idiap Research Institute, http://www.idiap.ch/ * Contact: beat.support@idiap.ch - * + * * This file is part of the beat.web module of the BEAT platform. - * + * * Commercial License Usage * Licensees holding valid commercial BEAT licenses may use this file in * accordance with the terms contained in a written agreement between you * and Idiap. For further information contact tto@idiap.ch - * + * * Alternatively, this file may be used under the terms of the GNU Affero * Public License version 3 as published by the Free Software and appearing * in the file LICENSE.AGPL included in the packaging of this file. * The BEAT platform 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. - * + * * You should have received a copy of the GNU Affero Public License along * with the BEAT platform. If not, see http://www.gnu.org/licenses/. */ diff --git a/beat/web/reports/static/reports/js/download.js b/beat/web/reports/static/reports/js/download.js index d2f393192..14d1c8d8c 100644 --- a/beat/web/reports/static/reports/js/download.js +++ b/beat/web/reports/static/reports/js/download.js @@ -1,137 +1,137 @@ -//download.js v4.0, by dandavis; 2008-2015. [CCBY2] see http://danml.com/download.html for tests/usage -// v1 landed a FF+Chrome compat way of downloading strings to local un-named files, upgraded to use a hidden frame and optional mime -// v2 added named files via a[download], msSaveBlob, IE (10+) support, and window.URL support for larger+faster saves than dataURLs -// v3 added dataURL and Blob Input, bind-toggle arity, and legacy dataURL fallback was improved with force-download mime and base64 support. 3.1 improved safari handling. -// v4 adds AMD/UMD, commonJS, and plain browser support -// https://github.com/rndme/download - -(function (root, factory) { - if (typeof define === 'function' && define.amd) { - // AMD. Register as an anonymous module. - define([], factory); - } else if (typeof exports === 'object') { - // Node. Does not work with strict CommonJS, but - // only CommonJS-like environments that support module.exports, - // like Node. - module.exports = factory(); - } else { - // Browser globals (root is window) - root.download = factory(); - } -}(this, function () { - - return function download(data, strFileName, strMimeType) { - - var self = window, // this script is only for browsers anyway... - u = "application/octet-stream", // this default mime also triggers iframe downloads - m = strMimeType || u, - x = data, - D = document, - a = D.createElement("a"), - z = function(a){return String(a);}, - B = (self.Blob || self.MozBlob || self.WebKitBlob || z); - B=B.call ? B.bind(self) : Blob ; - var fn = strFileName || "download", - blob, - fr; - - - if(String(this)==="true"){ //reverse arguments, allowing download.bind(true, "text/xml", "export.xml") to act as a callback - x=[x, m]; - m=x[0]; - x=x[1]; - } - - - - - //go ahead and download dataURLs right away - if(String(x).match(/^data\:[\w+\-]+\/[\w+\-]+[,;]/)){ - return navigator.msSaveBlob ? // IE10 can't do a[download], only Blobs: - navigator.msSaveBlob(d2b(x), fn) : - saver(x) ; // everyone else can save dataURLs un-processed - }//end if dataURL passed? - - blob = x instanceof B ? - x : - new B([x], {type: m}) ; - - - function d2b(u) { - var p= u.split(/[:;,]/), - t= p[1], - dec= p[2] == "base64" ? atob : decodeURIComponent, - bin= dec(p.pop()), - mx= bin.length, - i= 0, - uia= new Uint8Array(mx); - - for(i;i<mx;++i) uia[i]= bin.charCodeAt(i); - - return new B([uia], {type: t}); - } - - function saver(url, winMode){ - - if ('download' in a) { //html5 A[download] - a.href = url; - a.setAttribute("download", fn); - a.innerHTML = "downloading..."; - D.body.appendChild(a); - setTimeout(function() { - a.click(); - D.body.removeChild(a); - if(winMode===true){setTimeout(function(){ self.URL.revokeObjectURL(a.href);}, 250 );} - }, 66); - return true; - } - - if(typeof safari !=="undefined" ){ // handle non-a[download] safari as best we can: - url="data:"+url.replace(/^data:([\w\/\-\+]+)/, u); - if(!window.open(url)){ // popup blocked, offer direct download: - if(confirm("Displaying New Document\n\nUse Save As... to download, then click back to return to this page.")){ location.href=url; } - } - return true; - } - - //do iframe dataURL download (old ch+FF): - var f = D.createElement("iframe"); - D.body.appendChild(f); - - if(!winMode){ // force a mime that will download: - url="data:"+url.replace(/^data:([\w\/\-\+]+)/, u); - } - f.src=url; - setTimeout(function(){ D.body.removeChild(f); }, 333); - - }//end saver - - - - - if (navigator.msSaveBlob) { // IE10+ : (has Blob, but not a[download] or URL) - return navigator.msSaveBlob(blob, fn); - } - - if(self.URL){ // simple fast and modern way using Blob and URL: - saver(self.URL.createObjectURL(blob), true); - }else{ - // handle non-Blob()+non-URL browsers: - if(typeof blob === "string" || blob.constructor===z ){ - try{ - return saver( "data:" + m + ";base64," + self.btoa(blob) ); - }catch(y){ - return saver( "data:" + m + "," + encodeURIComponent(blob) ); - } - } - - // Blob but not URL: - fr=new FileReader(); - fr.onload=function(e){ - saver(this.result); - }; - fr.readAsDataURL(blob); - } - return true; - }; /* end download() */ -})); \ No newline at end of file +//download.js v4.0, by dandavis; 2008-2015. [CCBY2] see http://danml.com/download.html for tests/usage +// v1 landed a FF+Chrome compat way of downloading strings to local un-named files, upgraded to use a hidden frame and optional mime +// v2 added named files via a[download], msSaveBlob, IE (10+) support, and window.URL support for larger+faster saves than dataURLs +// v3 added dataURL and Blob Input, bind-toggle arity, and legacy dataURL fallback was improved with force-download mime and base64 support. 3.1 improved safari handling. +// v4 adds AMD/UMD, commonJS, and plain browser support +// https://github.com/rndme/download + +(function (root, factory) { + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define([], factory); + } else if (typeof exports === 'object') { + // Node. Does not work with strict CommonJS, but + // only CommonJS-like environments that support module.exports, + // like Node. + module.exports = factory(); + } else { + // Browser globals (root is window) + root.download = factory(); + } +}(this, function () { + + return function download(data, strFileName, strMimeType) { + + var self = window, // this script is only for browsers anyway... + u = "application/octet-stream", // this default mime also triggers iframe downloads + m = strMimeType || u, + x = data, + D = document, + a = D.createElement("a"), + z = function(a){return String(a);}, + B = (self.Blob || self.MozBlob || self.WebKitBlob || z); + B=B.call ? B.bind(self) : Blob ; + var fn = strFileName || "download", + blob, + fr; + + + if(String(this)==="true"){ //reverse arguments, allowing download.bind(true, "text/xml", "export.xml") to act as a callback + x=[x, m]; + m=x[0]; + x=x[1]; + } + + + + + //go ahead and download dataURLs right away + if(String(x).match(/^data\:[\w+\-]+\/[\w+\-]+[,;]/)){ + return navigator.msSaveBlob ? // IE10 can't do a[download], only Blobs: + navigator.msSaveBlob(d2b(x), fn) : + saver(x) ; // everyone else can save dataURLs un-processed + }//end if dataURL passed? + + blob = x instanceof B ? + x : + new B([x], {type: m}) ; + + + function d2b(u) { + var p= u.split(/[:;,]/), + t= p[1], + dec= p[2] == "base64" ? atob : decodeURIComponent, + bin= dec(p.pop()), + mx= bin.length, + i= 0, + uia= new Uint8Array(mx); + + for(i;i<mx;++i) uia[i]= bin.charCodeAt(i); + + return new B([uia], {type: t}); + } + + function saver(url, winMode){ + + if ('download' in a) { //html5 A[download] + a.href = url; + a.setAttribute("download", fn); + a.innerHTML = "downloading..."; + D.body.appendChild(a); + setTimeout(function() { + a.click(); + D.body.removeChild(a); + if(winMode===true){setTimeout(function(){ self.URL.revokeObjectURL(a.href);}, 250 );} + }, 66); + return true; + } + + if(typeof safari !=="undefined" ){ // handle non-a[download] safari as best we can: + url="data:"+url.replace(/^data:([\w\/\-\+]+)/, u); + if(!window.open(url)){ // popup blocked, offer direct download: + if(confirm("Displaying New Document\n\nUse Save As... to download, then click back to return to this page.")){ location.href=url; } + } + return true; + } + + //do iframe dataURL download (old ch+FF): + var f = D.createElement("iframe"); + D.body.appendChild(f); + + if(!winMode){ // force a mime that will download: + url="data:"+url.replace(/^data:([\w\/\-\+]+)/, u); + } + f.src=url; + setTimeout(function(){ D.body.removeChild(f); }, 333); + + }//end saver + + + + + if (navigator.msSaveBlob) { // IE10+ : (has Blob, but not a[download] or URL) + return navigator.msSaveBlob(blob, fn); + } + + if(self.URL){ // simple fast and modern way using Blob and URL: + saver(self.URL.createObjectURL(blob), true); + }else{ + // handle non-Blob()+non-URL browsers: + if(typeof blob === "string" || blob.constructor===z ){ + try{ + return saver( "data:" + m + ";base64," + self.btoa(blob) ); + }catch(y){ + return saver( "data:" + m + "," + encodeURIComponent(blob) ); + } + } + + // Blob but not URL: + fr=new FileReader(); + fr.onload=function(e){ + saver(this.result); + }; + fr.readAsDataURL(blob); + } + return true; + }; /* end download() */ +})); diff --git a/beat/web/reports/static/reports/js/new_report_dialog.js b/beat/web/reports/static/reports/js/new_report_dialog.js index fc6f90cdb..71843c41c 100644 --- a/beat/web/reports/static/reports/js/new_report_dialog.js +++ b/beat/web/reports/static/reports/js/new_report_dialog.js @@ -1,21 +1,21 @@ /* * Copyright (c) 2016 Idiap Research Institute, http://www.idiap.ch/ * Contact: beat.support@idiap.ch - * + * * This file is part of the beat.web module of the BEAT platform. - * + * * Commercial License Usage * Licensees holding valid commercial BEAT licenses may use this file in * accordance with the terms contained in a written agreement between you * and Idiap. For further information contact tto@idiap.ch - * + * * Alternatively, this file may be used under the terms of the GNU Affero * Public License version 3 as published by the Free Software and appearing * in the file LICENSE.AGPL included in the packaging of this file. * The BEAT platform 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. - * + * * You should have received a copy of the GNU Affero Public License along * with the BEAT platform. If not, see http://www.gnu.org/licenses/. */ diff --git a/beat/web/reports/static/reports/test/report-spec.js b/beat/web/reports/static/reports/test/report-spec.js index aee6bb819..0667e3516 100644 --- a/beat/web/reports/static/reports/test/report-spec.js +++ b/beat/web/reports/static/reports/test/report-spec.js @@ -1,956 +1,956 @@ // general tests for the reports app describe('reports app', function(){ - // contain helpers for browser.wait - const until = protractor.ExpectedConditions; - // just use enter.perform() to send the enter key - const enter = browser.actions().sendKeys(protractor.Key.ENTER); - // since angular isnt configured correctly for protractor, - // dont make protractor wait for angular - browser.ignoreSynchronization = true; - // just to make sure the window is maximized during these tests. - // helps with button clicking & such - browser.driver.manage().window().maximize(); - - // login to the default user ('user') once before running all these tests - beforeAll(function(){ - browser.get('http://localhost:8000/login/?next=/'); - //browser.findElement(by.partialLinkText('Sign-in')).click(); - browser.findElement(by.id('id_username')).sendKeys('user'); - browser.findElement(by.id('id_password')).sendKeys('user'); - browser.findElement(by.partialButtonText('Sign-in')).click(); - return browser.wait(function(){ - return browser.getCurrentUrl().then(function(url){ - const rxUserLoggedIn = /events\/user\//; - return rxUserLoggedIn.test(url); - }); - }); - }); - - // if there's an error in the web browser's console, - // fail the test and print the error - afterEach(function() { - let util = require('util'); - browser.manage().logs().get('browser').then(function(logs) { - // 'failed to load resource' is a familiar log error - // when running adblockers. Don't fail on those. - let failingLogs = logs - .filter(l => !(/net::ERR_FAILED/.test(l.message))); - - expect(failingLogs.length).toEqual(0); - - if (failingLogs.length > 0) { - console.log(`logs: ${util.inspect(failingLogs)}`); - } - }); - }); - - // /reports - describe('home', function(){ - beforeEach(function(){ - browser.get('http://localhost:8000/reports/user'); - }); - - it('should load', function(){ - expect(browser.getTitle()).toEqual("BEAT - user's Reports"); - }); - }); - - // /reports/user - describe('home for the test user', function(){ - - // go to user's reports page before each test - beforeAll(function(){ - browser.get('http://localhost:8000/reports/user/'); - }); - - // before adding a report, there shouldn't be any - it('should not have the "user/test" report', function(){ - let noReportsText = browser.findElement(by.css('.name > a')); - expect(noReportsText.getText()).not.toBe('user/test'); - }); - - // create a report - it('should create a new report called "test"', function(){ - let newReportButton = browser.findElement(by.partialLinkText('New')); - newReportButton.click(); - - // wait for dialog box to pop up - browser.sleep(1000); - - let reportNameInput = browser.findElement(by.css('.has-error input')); - reportNameInput.sendKeys('test'); - - let submitButton = element(by.buttonText('Create')); - expect(submitButton.isPresent()).toBeTruthy(); - submitButton.click(); - - // wait for page to refresh - browser.sleep(1000); - - let newReportLink = browser.findElement(by.linkText('user/test')); - newReportLink.click(); - expect(browser.getTitle()).toBe('BEAT - Report'); - }); - }); - - // go to the experiments page and add up to 5 already-ran experiments - describe('adding experiments to the "test" report', function(){ - // go to experiments page - beforeAll(function(){ - browser.get('http://localhost:8000/experiments/user/'); - }); - - it('should show the experiments list page', function(){ - expect(browser.getTitle()).toEqual("BEAT - user's Experiments"); - }); - - it('should list successfully-ran experiments accessible by user', function(){ - expect(browser.isElementPresent(by.css('.Done'))).toBeTruthy(); - }); - - it('should add up to the first 5 experiments to the "test" report', function(){ - let finishedExpTableRows = element.all(by.css('.Done')); - let addButton = element(by.css('#add-to-report')); - expect(addButton.getAttribute('disabled')).toBe('true'); - - let fiveRows = finishedExpTableRows.filter((r, i) => i < 5); - - fiveRows - .then(rs => Promise.all(rs.map(r => r.element(by.css('.report-checkbox')).element(by.css('input')).click()))) - .then(() => { - browser.wait(until.elementToBeClickable(addButton), 5000, 'Button still isnt clickable!'); - return browser.executeScript('arguments[0].click();', addButton.getWebElement()); - }) - .then(() => browser.wait(until.presenceOf(element(by.css('.modal'))), 5000, 'Element taking too long to appear in the DOM')) - .then(() => element(by.css('.chosen-single')).click()) - .then(() => element(by.css('.chosen-results')).element(by.css('.active-result')).click()) - .then(() => { - let submitButton = element(by.buttonText('Add')); - return submitButton.click(); - }) - .then(() => browser.wait(until.presenceOf(element(by.buttonText("View Report"))), 5000)) - .then(() => { - let headerText = element(by.css('.report-results > h5')); - expect(headerText.getText()).toContain('Successfully added'); - - return browser.get('http://localhost:8000/reports/user/test/'); - }) - .then(() => { - expect(element.all(by.css('#experiment-list-test > tbody > tr')).count()).toBeGreaterThan(0); - }) - ; - }); - }); - - // create 2 groups, 'group1' & 'group2' - describe('creating groups', function(){ - let newGroupInput = element(by.css('#createNewGroupInput')); - - it('should create the "group1" group using the enter key', function(){ - newGroupInput.sendKeys('group1') - .then(() => enter.perform()) - .then(() => browser.wait(until.textToBePresentInElementValue(element(by.css('#createNewGroupInput')), ''), 1000)) - .then(() => expect(element.all(by.css('#groupsLayout > div')).count()).toBe(1)) - ; - }); - - it('should create the "group2" group using the "+" button', function(){ - newGroupInput.sendKeys('group2') - .then(() => element(by.css('#space-for-report-items + div button')).click()) - .then(() => browser.wait(until.textToBePresentInElementValue(element(by.css('#createNewGroupInput')), ''), 1000)) - .then(() => expect(element.all(by.css('#groupsLayout > div')).count()).toBe(2)) - ; - }); - }); - - // make sure the initial report layouts page is correct - describe('report page state with <6 experiments & two groups', function(){ - describe('header block', function(){ - const header = element(by.css('.col-sm-12 > p.bs-callout.bs-callout-danger')); - - it('has 4 labels', function() { - expect(header.all(by.tagName('br')).count()).toBe(4); - }); - - it('shows the unique report id', function() { - expect(header.element(by.css('.fa-arrow-circle-right + a')).getAttribute('href')).toMatch(/\/reports\/[0-9]+/); - }); - - it('shows the created date', function() { - expect(header.element(by.css('.fa-calendar-o + strong')).getText()).toMatch(/.+ago/); - }); - - it('shows the "last edited" date', function() { - expect(header.element(by.css('.fa-calendar-o + strong + br + .fa-calendar-o + strong')).getText()).toMatch(/.+ago/); - }); - - it('shows that the report is editable', function() { - expect(header.element(by.css('.fa-warning + strong')).getText()).toBe('Editable'); - }); - }); - - describe('documentation panel', function(){ - it('shows the empty warning', function() { - expect(element(by.css('#description-display > div')).getAttribute('class')).toContain('alert-warning'); - }); - - it('has an "Add" button', function() { - expect(element(by.css('#btn-edit-doc > i')).getAttribute('class')).toContain('fa-edit'); - }); - }); - - describe('experiments list panel', function(){ - describe('table', function(){ - const table = element(by.css('#experiment-list-test')); - - it('has 8 columns', function(){ - const cols = table.all(by.css('thead th')); - expect(cols.count()).toBe(8); - - expect(cols.get(0).getAttribute('class')).toBe('delete'); - expect(cols.get(1).getAttribute('class')).toBe('attestation'); - expect(cols.get(2).getAttribute('class')).toBe('privacy'); - expect(cols.get(3).getAttribute('class')).toBe('status'); - expect(cols.get(4).getAttribute('class')).toBe('date'); - expect(cols.get(5).getText()).toBe('Name'); - expect(cols.get(6).getAttribute('class')).toBe('datasets'); - expect(cols.get(7).getAttribute('class')).toBe('analyzers'); - }); - - it('has 5 or less rows', function() { - const rows = table.all(by.css('tbody > tr')); - expect(rows.count()).toBeLessThan(6); - }); - - it('has a "Remove Selected Experiments" button', function() { - const b = element(by.buttonText('Remove Selected Experiments')); - expect(b.getAttribute('disabled')).toBeDefined(); - }); - }); - }); - - describe('report content block', function(){ - const groupsLayout = element(by.css('#groupsLayout')); - - describe('group1 container header', function(){ - const header = element(by.css('#group1-heading')); - const children = element.all(by.css('#group1-heading > h4 > *')); - - it('has 5 children: a collapse link, name widget, a button group, and the add items menu', function(){ - expect(children.count()).toBe(4); - }); - - describe('collapse link', function(){ - it('toggles "#collapse-group1"', function(){ - expect(children.get(0).getAttribute('href')).toContain('#collapse-group1'); - }); - }); - - describe('group name widget', function(){ - const widgetEls = children.get(1).all(by.tagName('span')); - const label = widgetEls.get(0); - const button = widgetEls.get(1); - - it('has value "group1"', function(){ - expect(label.getText()).toBe('group1'); - }); - - describe('edit button', function(){ - it('has the pencil glyphicon', function(){ - expect(button.getAttribute('class')).toBe('glyphicon glyphicon-pencil'); - }); - - it('is clickable', function(){ - // no direct way to check if clickable... - // so make sure that its not disabled & its displayed instead - expect(button.getAttribute('disabled')).toBeNull(); - expect(button.isDisplayed()).toBeTruthy(); - }); - }); - - }); - - describe('button group', function(){ - const grp = children.get(2); - const btnChildren = grp.all(by.className('btn')); - - it('is a btn group', function(){ - expect(grp.getAttribute('class')).toContain('btn-group'); - }); - - it('is an action buttons', function(){ - expect(grp.getAttribute('class')).toContain('action-buttons'); - }); - - it('has btn children', function() { - const btnChildren = grp.all(by.className('btn')); - expect(btnChildren.count()).toBe(2); - }); - - it('has a button to delete a group with a red "X"', function(){ - expect(btnChildren.get(0).getAttribute('title')).toBe('Delete Group'); - expect(btnChildren.get(0).element(by.css('i')).getAttribute('class')).toContain('fa-times'); - }); - - it('has a button to drag & sort the group with a 4-directional arrow', function(){ - expect(btnChildren.get(1).getAttribute('title')).toBe('Drag to re-order group'); - expect(btnChildren.get(1).element(by.css('i')).getAttribute('class')).toContain('fa-arrows'); - }); - }); - - describe('add items menu', function(){ - const grp = children.get(3); - const buttons = grp.all(by.tagName('button')); - - it('has 3 buttons', function(){ - expect(buttons.count()).toBe(3); - }); - - it('is all disabled', function(){ - expect(buttons.filter(b => b.getAttribute('disabled').then(d => d)).count()).toBe(3); - }); - - it('has a button to add plots', function(){ - const el = element(by.buttonText('Add Plot')); - expect(el).toBeDefined(); - }); - - it('has a button to add tables', function(){ - const el = element(by.buttonText('Add Table')); - expect(el).toBeDefined(); - }); - - it('has a button to add text blocks', function(){ - const el = element(by.buttonText('Add Text Block')); - expect(el).toBeDefined(); - }); - }); - }); - - describe('group1 experiments panel', function(){ - const header = element(by.css('#group1-explist-heading')); - const body = element(by.css('#collapse-group1-explist')); - - it('is visible', function(){ - expect(header.isDisplayed()).toBeTruthy(); - expect(body.isDisplayed()).toBeTruthy(); - }); - - it('is empty', function(){ - expect(element.all(by.css('#collapse-group1-explist > .panel-body > *')).count()).toBe(0); - }); - - describe('header button', function(){ - const button = element(by.css('#group1_exp_add_dropdown')); - - it('is non-disabled', function(){ - expect(button.getAttribute('disabled')).toBeNull(); - }); - - it('has the text "Add Experiment"', function(){ - expect(button.getText()).toBe('Add Experiment'); - }); - }); - }); - - describe('group1 content panel', function(){ - it('doesnt exist', function(){ - expect(element.all(by.css('#collapse-group1 > *')).count()).toBe(1); - }); - }); - }); - }); - - describe('group experiments panel management', function(){ - const g1ExpPanel = element(by.css('#collapse-group1 > .panel-body > .panel')); - const addButton = element(by.css('#group1_exp_add_dropdown')); - - // add back the exp to group1 - afterAll(function(){ - const parent = addButton.element(by.xpath('..')); - - parent.getAttribute('class') - .then(cls => cls.includes('open') || addButton.click()) - .then(() => { - // make sure list opened correctly & add an exp - const list = parent.element(by.css('ul')); - const first = list.element(by.tagName('li')); - browser.wait(until.elementToBeClickable(first), 5000, 'First add button isnt becoming clickable') - - return first.click(); - }); - }); - - it('adds 1 experiment to group1', function(){ - // open add exp menu - addButton.click() - .then(() => { - // make sure list opened correctly & add an exp - const parent = addButton.element(by.xpath('..')); - expect(parent.getAttribute('class')).toContain('open'); - const list = parent.element(by.css('ul')); - const lis = list.all(by.css('li')); - expect(lis.count()).toBeGreaterThan(0); - - return lis.get(0).element(by.css('a')).click(); - }) - .then(() => { - // check state of group after adding exp - const analyzer = element(by.css('#group1-explist-heading > h4 > i')); - expect(analyzer.isDisplayed()).toBeTruthy(); - expect(analyzer.getText()).not.toBe(''); - - const expsBody = element(by.css('#collapse-group1-explist > .panel-body')); - expect(expsBody.getText()).not.toBe(''); - }); - }); - - describe('group1 experiment table state with 1 experiment', function(){ - const expsTable = element(by.css('#collapse-group1-explist > .panel-body > table')); - const headers = expsTable.all(by.css('thead > tr > th')); - const row = expsTable.element(by.css('tbody > tr')); - - describe('column layout', function(){ - it('has 4 columns', function(){ - expect(headers.count()).toBe(4); - }); - - it('has an empty col for rm exp buttons', function() { - expect(headers.get(0).getText()).toBe(''); - }); - - it('has experiment names col', function() { - expect(headers.get(1).getText()).toBe('Experiment'); - }); - - it('has dbs/protocols col', function() { - expect(headers.get(2).getText()).toBe('Databases/Protocols'); - }); - - it('has aliases col', function() { - expect(headers.get(3).getText()).toBe('Alias'); - }); - }); - - describe('row layout', function(){ - const cells = row.all(by.css('td')); - - it('has 4 entries', function() { - expect(cells.count()).toBe(4); - }); - - it('has a delete button in the first row', function() { - expect(cells.get(0).element(by.tagName('span')).getAttribute('class')).toContain('btn-delete'); - }); - - it('has the experiment name and a link to the experiment in the second row', function() { - const a = cells.get(1).element(by.css('a')); - expect(a.getText()).not.toBe(''); - }); - - it('has a list of formatted dbs & protocols in the third row', function() { - const els = cells.get(2).all(by.css('span')); - expect(els.count()).toBeGreaterThan(0); - expect(els.get(0).element(by.css('a')).getText()).toMatch(/\S+@\S+/); - }); - - it('has an editable alias input in the fourth row', function() { - const input = cells.get(3).element(by.css('input')); - const pExpName = cells.get(1).element(by.css('a')).getText(); - pExpName - .then(expName => { - const lastSeg = expName.split('/').filter(s => s.length > 0).pop(); - expect(input.getAttribute('value')).toBe(lastSeg); - expect(input.getAttribute('disabled')).toBeNull(); - }); - }); - - }); - }); - - describe('removing 1 experiment', function(){ - beforeAll(function(){ - const rmExpButton = element(by.css('#collapse-group1-explist tbody .btn-delete')); - return rmExpButton.click(); - }); - - it('removes the exp table', function(){ - const explistPanel = element(by.css('#collapse-group1-explist > .panel-body')); - expect(explistPanel.getText()).toBe(''); - }); - - it('removes the analyzer tag', function() { - expect(element.all(by.css('#group1-explist-heading > h4 > *')).count()).toBe(2); - }); - - it('lets the user add any experiment again', function() { - const expRowsCount = element.all(by.css('#experiment-list-test tbody > tr')).count(); - - Promise.all([expRowsCount, element(by.css('#group1_exp_add_dropdown')).click()]) - .then(([expCount]) => - Promise.all([ - expCount, - element('#group1_exp_add_dropdown').element(by.xpath('..')).element(by.css('ul')).all(by.css('li')).count() - ]) - ) - .then(([expCount, optsCount]) => { - expect(expCount).toBe(optsCount); - }); - }); - }); - }); - - // should have 1 exp in group - describe('group report items panel management', function(){ - const addPlot = () => { - const addButton = element(by.partialButtonText('Add Plot')); - return addButton.click() - .then(() => { - const parent = addButton.element(by.xpath('..')); - const firstPlotLi = parent.element(by.css('ul > li')); - return firstPlotLi.click(); - }) - ; - }; - - const addTable = () => { - const addButton = element(by.partialButtonText('Add Table')); - return addButton.click(); - }; - - const addTextBlock = () => { - const addButton = element(by.partialButtonText('Add Text Block')); - return addButton.click(); - }; - - afterAll(function() { - // add a plot, table, & text block - return addPlot() - .then(() => addTable()) - .then(() => addTextBlock()) - ; - }); - - describe('plot items', function(){ - describe('adding a plot', function() { - beforeAll(function() { - // add plot here - return addPlot(); - }); - - it('adds a plot', function() { - const plotContainer = element(by.css('#collapse-group1_plot_0')); - expect(plotContainer.isDisplayed()).toBeTruthy(); - }); - - it('eventually renders the plot', function() { - const plotContainer = element(by.css('#collapse-group1_plot_0 > .panel-body > div')); - - browser.wait(until.presenceOf(plotContainer.element(by.css('img'))), - 5000, 'Plot render hasnt been inserted in Angular for 5s'); - - const img = plotContainer.element(by.css('img')); - expect(img.isDisplayed()).toBeTruthy(); - }); - }); - - describe('removing a plot', function() { - beforeAll(function() { - // rm plot here - const delButton = element(by.css('#group1_plot_0-heading .btn-delete')); - - return delButton.click(); - }); - - it('removes the plot and has no report items', function() { - const groupBodyChildren = element(by.model('group._reportItems')).all(by.css('*')); - expect(groupBodyChildren.count()).toBe(0); - }); - }); - }); - - describe('table items', function() { - describe('adding a table', function() { - beforeAll(function() { - // add table here - return addTable(); - }); - - it('adds a table', function() { - const tableContainer = element(by.css('#collapse-group1_table_0 .panel-body')); - expect(tableContainer.isDisplayed()).toBeTruthy(); - }); - - describe('layout', function() { - const panelHeaderButtonGroup = element.all(by.css('#group1_table_0-heading > h4 > .btn-group')).get(1); - const colsButton = panelHeaderButtonGroup.element(by.css('#group1_table_0_columnSelector')); - const precButton = panelHeaderButtonGroup.element(by.css('#group1_table_0-precision')); - const tcsvButton = panelHeaderButtonGroup.element(by.buttonText('Toggle CSV View')); - - const table = element(by.css('#collapse-group1_table_0 table')); - const headers = table.all(by.css('thead > tr > th')); - const rows = table.all(by.css('tbody > tr')); - - it('has at least one column for the name of the experiments', function() { - expect(headers.count()).toBeGreaterThan(0); - expect(headers.get(0).element(by.css('a')).getText()).toContain('Experiment'); - }); - - it('has a default precision of 10', function() { - expect(precButton.getText()).toBe('Float Precision: 10'); - }); - - it('has enabled button for choosing columns', function() { - expect(colsButton.getAttribute('disabled')).toBeNull(); - }); - - it('has enabled button for choosing precision', function() { - expect(precButton.getAttribute('disabled')).toBeNull(); - }); - - it('has enabled button for toggling the csv view', function() { - expect(tcsvButton.getAttribute('disabled')).toBeNull(); - }); - - it('has 1 row with the name col matching the alias', function() { - const pName = rows.get(0).element(by.css('td')).getText(); - const pCurrAlias = element(by.css('#collapse-group1-explist table > tbody input')).getAttribute('value'); - Promise.all([pName, pCurrAlias]) - .then(([name, currAlias]) => expect(name).toBe(currAlias)); - }); - }); - }); - - describe('removing a table', function() { - beforeAll(function() { - // rm table here - const delButton = element(by.css('#group1_table_0-heading .btn-delete')); - - return delButton.click(); - }); - - it('removes the table and has no report items', function() { - const groupBodyChildren = element(by.model('group._reportItems')).all(by.css('*')); - expect(groupBodyChildren.count()).toBe(0); - }); - }); - }); - - describe('text block items', function() { - describe('adding a text block', function() { - beforeAll(function(){ - // add text block here - return addTextBlock(); - }); - - it('added a text block to the group', function() { - const textContainer = element(by.css('#collapse-group1_text_0 .panel-body')); - expect(textContainer.isDisplayed()).toBeTruthy(); - }); - - it('is blank/empty', function() { - const emptyContainer = element(by.css('#collapse-group1_text_0 > .panel-body > .row > .col-sm-10 > div')); - expect(emptyContainer.getText()).toBe(''); - }); - - it('shows the "edit" button', function() { - const editButton = element(by.css('#collapse-group1_text_0 > .panel-body > .row > .col-sm-2 > div > a')); - expect(editButton.getText()).toContain('Edit'); - }); - }); - - describe('removing a text block', function() { - beforeAll(function() { - // rm text here - const delButton = element(by.css('#group1_text_0-heading .btn-delete')); - - return delButton.click(); - }); - - it('removes the text and has no report items', function() { - const groupBodyChildren = element(by.model('group._reportItems')).all(by.css('*')); - expect(groupBodyChildren.count()).toBe(0); - }); - }); - }); - }); - - describe('collapse functionality', function() { - // why are there all these 'sleep' calls before expanding the collapsed panel? - // cuz, for some reason, theres a slight delay in it changing to the collapsed state in the DOM, - // and it being available for being expanded - describe('of groups', function() { - const collapseButton = element(by.css('#group1-heading > h4 > a')); - - it('should collapse', function() { - browser.executeScript('arguments[0].click();', collapseButton.getWebElement()) - .then(() => expect(collapseButton.getAttribute('class')).toBe('collapsed')) - ; - }); - - it('should expand', function() { - browser.sleep(500); - browser.executeScript('arguments[0].click();', collapseButton.getWebElement()) - .then(() => expect(collapseButton.getAttribute('class')).toBe('')) - ; - }); - }); - - describe('of experiments panel', function() { - const collapseButton = element(by.css('#group1-explist-heading > h4 > a')); - - it('should collapse', function() { - browser.executeScript('arguments[0].click();', collapseButton.getWebElement()) - .then(() => expect(collapseButton.getAttribute('class')).toBe('collapsed')) - ; - }); - - it('should expand', function() { - browser.sleep(500); - browser.executeScript('arguments[0].click();', collapseButton.getWebElement()) - .then(() => expect(collapseButton.getAttribute('class')).toBe('')) - ; - }); - }); - - describe('of plots', function() { - const collapseButton = element(by.css('#group1_plot_0-heading > h4 > a')); - - it('should collapse', function() { - browser.executeScript('arguments[0].click();', collapseButton.getWebElement()) - .then(() => expect(collapseButton.getAttribute('class')).toBe('collapsed')) - ; - }); - - it('should expand', function() { - browser.sleep(500); - browser.executeScript('arguments[0].click();', collapseButton.getWebElement()) - .then(() => expect(collapseButton.getAttribute('class')).toBe('')) - ; - }); - }); - - describe('of tables', function() { - const collapseButton = element(by.css('#group1_table_0-heading > h4 > a')); - - it('should collapse', function() { - browser.executeScript('arguments[0].click();', collapseButton.getWebElement()) - .then(() => expect(collapseButton.getAttribute('class')).toBe('collapsed')) - ; - }); - - it('should expand', function() { - browser.sleep(500); - browser.executeScript('arguments[0].click();', collapseButton.getWebElement()) - .then(() => expect(collapseButton.getAttribute('class')).toBe('')) - ; - }); - }); - - describe('of text blocks', function() { - const collapseButton = element(by.css('#group1_text_0-heading > h4 > a')); - - it('should collapse', function() { - browser.executeScript('arguments[0].click();', collapseButton.getWebElement()) - .then(() => expect(collapseButton.getAttribute('class')).toBe('collapsed')) - ; - }); - - it('should expand', function() { - browser.sleep(500); - browser.executeScript('arguments[0].click();', collapseButton.getWebElement()) - .then(() => expect(collapseButton.getAttribute('class')).toBe('')) - ; - }); - }); - }); - - describe('group experiment panel functionality', function() { - const firstRowCells = element(by.css('#collapse-group1-explist tbody > tr')).all(by.css('td')); - - describe('alias field', function() { - const alias = element(by.css('#collapse-group1-explist table > tbody input')); - const strToType = 'asdf'; - - it('lets you change the alias', function() { - alias.sendKeys(strToType) - .then(() => expect(alias.getAttribute('value')).toContain(strToType)) - ; - }); - - it('changed the experiment name in the first row in the table item', function(){ - const nameEl = element(by.css('#collapse-group1_table_0 td')); - - expect(nameEl.getText()).toContain(strToType); - }); - }); - }); - - describe('group report items functionality', function() { - describe('plot', function() { - const plotContainer = element(by.css('#group1_plot_0-render')); - }); - - describe('table', function() { - const panelHeaderButtonGroup = element.all(by.css('#group1_table_0-heading > h4 > .btn-group')).get(1); - const colsButton = panelHeaderButtonGroup.element(by.css('#group1_table_0_columnSelector')); - const precButton = panelHeaderButtonGroup.element(by.css('#group1_table_0-precision')); - const tcsvButton = panelHeaderButtonGroup.element(by.buttonText('Toggle CSV View')); - - const table = element(by.css('#collapse-group1_table_0 table')); - const headers = table.all(by.css('thead > tr > th')); - const rows = table.all(by.css('tbody > tr')); - - it('changes columns to include "total_execution_time"', function() { - const b = element(by.partialButtonText('Choose Columns')); - b.click() - .then(() => { - const execTimeCol = element.all(by.css('#colSelectorId > .dropdown-menu > form > fieldset > div')) - .filter(e => e.element(by.tagName('input')).getAttribute('value').then(v => v == 'total_execution_time')) - .first() - ; - - return execTimeCol.click(); - }) - .then(() => element(by.buttonText('Save Column Choices')).click()) - .then(() => { - const timeCols = element.all(by.css('#collapse-group1_table_0 thead a')) - .filter(a => a.getText().then(t => t.includes('total_execution_time'))); - - return expect(timeCols.count()).toBe(1); - }) - ; - }); - - it('changes precision from 10 to 7', function() { - const b = element(by.partialButtonText('Float Precision')); - b.click() - .then(() => element(by.linkText('7')).click()) - .then(() => expect(element(by.partialButtonText('Float Precision')).getText()).toContain('7')) - .then(() => maps = element.all(by.css('#collapse-group1_table_0 tbody tr td')) - .map(td => td.getText())) - .then(txts => txts - .filter(t => !isNaN(t) && t.includes('.')) - .forEach(t => expect(t.split('.')[1].length).toBeLessThan(8)) - ) - ; - }); - }); - - /* - * the 'x' cancels tests from running - * unfortunately there seems to be a selenium/protractor/webdriver bug - * with looking into the codemirror editor (`ui-codemirror`) - * none of the codemirror HTML elements are registering with - * selenium/protractor as being visible - */ - xdescribe('text block', function() { - const getHtmlContainer = () => element(by.css('#collapse-group1_text_0 > .panel-body > .row > .col-sm-10 > div')); - const getEditButton = () => element(by.css('#collapse-group1_text_0')).element(by.partialLinkText('Edit')); - - const text = '1. asdf'; - - describe('edit mode', function() { - it('turns on after clicking the edit button', function(){ - getEditButton().click() - .then(() => { - browser.wait(until.presenceOf(element(by.css('.CodeMirror'))), 5000, 'Nope'); - const editor = element.all(by.css('.CodeMirror')); - expect(editor.count()).toBeGreaterThan(0); - }) - ; - }) - - it('has cancel button', function() { - expect(element(by.css('#collapse-group1_text_0')).element(by.partialLinkText('Cancel')) - .isDisplayed()).toBeTruthy(); - }); - - it('has save button', function() { - expect(element(by.css('#collapse-group1_text_0')).element(by.partialLinkText('Save')) - .isDisplayed()).toBeTruthy(); - }); - - it('lets you edit the RST', function() { - const textArea = element(by.css('.CodeMirror textarea')); - expect(textArea.isDisplayed()).toBeTruthy(); - - textArea.sendKeys(text) - .then(() => expect(textArea.getAttribute('value')).toBe(text)) - ; - }); - }); - - describe('cancelling edits', function() { - it('moves to view mode', function() { - element(by.css('#collapse-group1_text_0')).element(by.partialLinkText('Cancel')) - .click() - .then(() => expect(getEditButton().isDisplayed()).toBeTruthy()) - }); - - it('didnt save the typed RST', function(){ - expect(getHtmlContainer().getText()).toBe(''); - }); - }); - - describe('submitting edits', function() { - beforeAll(function(){ - getEditButton().click() - .then(() => element(by.css('#collapse-group1_text_0 textarea')).sendKeys(text)) - .then(() => element(by.css('#collapse-group1_text_0')).element(by.partialLinkText('Save')).click()) - .then(() => browser.wait(until.presenceOf(element(by.css('#collapse-group1_text_0 ol.arabic.simple'))), 5000, 'HTML didnt render correctly')) - ; - }); - - it('renders the text correctly', function() { - expect(getHtmlContainer().isDisplayed()).toBeTruthy(); - - expect(getHtmlContainer().getText()).toBe( - `<ol class="arabic simple"> - <li>asdf</li> - </ol>` - ); - }); - }); - }); - }); - - // need to save report before doing these - describe('links outside the report page', function(){ - beforeAll(function(){ - const saveButton = element(by.css('#save-button')); - - return browser.executeScript('arguments[0].click();', saveButton.getWebElement()); - }); - - afterEach(function(){ - return browser.get('http://localhost:8000/reports/user/test/'); - }); - - const firstRowCells = element(by.css('#collapse-group1-explist tbody > tr')).all(by.css('td')); - const link1 = firstRowCells.get(1).element(by.css('a')); - const link2 = firstRowCells.get(2).element(by.css('a')); - - describe('experiment link', function() { - it('links to a valid experiment', function() { - Promise.all([link1.getText(), link1.click()]) - .then(([expName]) => expect(browser.getTitle()).toBe(`BEAT - ${expName}`)) - ; - }); - }); - - describe('database~protocol link', function() { - afterAll(function(){ - return browser.navigate().back(); - }); - - it('links to a valid database~protocol', function() { - Promise.all([link2.getText(), link2.click()]) - .then(([dbProtName]) => { - expect(browser.getTitle()).toBe(`BEAT - ${dbProtName.split('@')[0]}`) - }) - ; - }); - }); - }); + // contain helpers for browser.wait + const until = protractor.ExpectedConditions; + // just use enter.perform() to send the enter key + const enter = browser.actions().sendKeys(protractor.Key.ENTER); + // since angular isnt configured correctly for protractor, + // dont make protractor wait for angular + browser.ignoreSynchronization = true; + // just to make sure the window is maximized during these tests. + // helps with button clicking & such + browser.driver.manage().window().maximize(); + + // login to the default user ('user') once before running all these tests + beforeAll(function(){ + browser.get('http://localhost:8000/login/?next=/'); + //browser.findElement(by.partialLinkText('Sign-in')).click(); + browser.findElement(by.id('id_username')).sendKeys('user'); + browser.findElement(by.id('id_password')).sendKeys('user'); + browser.findElement(by.partialButtonText('Sign-in')).click(); + return browser.wait(function(){ + return browser.getCurrentUrl().then(function(url){ + const rxUserLoggedIn = /events\/user\//; + return rxUserLoggedIn.test(url); + }); + }); + }); + + // if there's an error in the web browser's console, + // fail the test and print the error + afterEach(function() { + let util = require('util'); + browser.manage().logs().get('browser').then(function(logs) { + // 'failed to load resource' is a familiar log error + // when running adblockers. Don't fail on those. + let failingLogs = logs + .filter(l => !(/net::ERR_FAILED/.test(l.message))); + + expect(failingLogs.length).toEqual(0); + + if (failingLogs.length > 0) { + console.log(`logs: ${util.inspect(failingLogs)}`); + } + }); + }); + + // /reports + describe('home', function(){ + beforeEach(function(){ + browser.get('http://localhost:8000/reports/user'); + }); + + it('should load', function(){ + expect(browser.getTitle()).toEqual("BEAT - user's Reports"); + }); + }); + + // /reports/user + describe('home for the test user', function(){ + + // go to user's reports page before each test + beforeAll(function(){ + browser.get('http://localhost:8000/reports/user/'); + }); + + // before adding a report, there shouldn't be any + it('should not have the "user/test" report', function(){ + let noReportsText = browser.findElement(by.css('.name > a')); + expect(noReportsText.getText()).not.toBe('user/test'); + }); + + // create a report + it('should create a new report called "test"', function(){ + let newReportButton = browser.findElement(by.partialLinkText('New')); + newReportButton.click(); + + // wait for dialog box to pop up + browser.sleep(1000); + + let reportNameInput = browser.findElement(by.css('.has-error input')); + reportNameInput.sendKeys('test'); + + let submitButton = element(by.buttonText('Create')); + expect(submitButton.isPresent()).toBeTruthy(); + submitButton.click(); + + // wait for page to refresh + browser.sleep(1000); + + let newReportLink = browser.findElement(by.linkText('user/test')); + newReportLink.click(); + expect(browser.getTitle()).toBe('BEAT - Report'); + }); + }); + + // go to the experiments page and add up to 5 already-ran experiments + describe('adding experiments to the "test" report', function(){ + // go to experiments page + beforeAll(function(){ + browser.get('http://localhost:8000/experiments/user/'); + }); + + it('should show the experiments list page', function(){ + expect(browser.getTitle()).toEqual("BEAT - user's Experiments"); + }); + + it('should list successfully-ran experiments accessible by user', function(){ + expect(browser.isElementPresent(by.css('.Done'))).toBeTruthy(); + }); + + it('should add up to the first 5 experiments to the "test" report', function(){ + let finishedExpTableRows = element.all(by.css('.Done')); + let addButton = element(by.css('#add-to-report')); + expect(addButton.getAttribute('disabled')).toBe('true'); + + let fiveRows = finishedExpTableRows.filter((r, i) => i < 5); + + fiveRows + .then(rs => Promise.all(rs.map(r => r.element(by.css('.report-checkbox')).element(by.css('input')).click()))) + .then(() => { + browser.wait(until.elementToBeClickable(addButton), 5000, 'Button still isnt clickable!'); + return browser.executeScript('arguments[0].click();', addButton.getWebElement()); + }) + .then(() => browser.wait(until.presenceOf(element(by.css('.modal'))), 5000, 'Element taking too long to appear in the DOM')) + .then(() => element(by.css('.chosen-single')).click()) + .then(() => element(by.css('.chosen-results')).element(by.css('.active-result')).click()) + .then(() => { + let submitButton = element(by.buttonText('Add')); + return submitButton.click(); + }) + .then(() => browser.wait(until.presenceOf(element(by.buttonText("View Report"))), 5000)) + .then(() => { + let headerText = element(by.css('.report-results > h5')); + expect(headerText.getText()).toContain('Successfully added'); + + return browser.get('http://localhost:8000/reports/user/test/'); + }) + .then(() => { + expect(element.all(by.css('#experiment-list-test > tbody > tr')).count()).toBeGreaterThan(0); + }) + ; + }); + }); + + // create 2 groups, 'group1' & 'group2' + describe('creating groups', function(){ + let newGroupInput = element(by.css('#createNewGroupInput')); + + it('should create the "group1" group using the enter key', function(){ + newGroupInput.sendKeys('group1') + .then(() => enter.perform()) + .then(() => browser.wait(until.textToBePresentInElementValue(element(by.css('#createNewGroupInput')), ''), 1000)) + .then(() => expect(element.all(by.css('#groupsLayout > div')).count()).toBe(1)) + ; + }); + + it('should create the "group2" group using the "+" button', function(){ + newGroupInput.sendKeys('group2') + .then(() => element(by.css('#space-for-report-items + div button')).click()) + .then(() => browser.wait(until.textToBePresentInElementValue(element(by.css('#createNewGroupInput')), ''), 1000)) + .then(() => expect(element.all(by.css('#groupsLayout > div')).count()).toBe(2)) + ; + }); + }); + + // make sure the initial report layouts page is correct + describe('report page state with <6 experiments & two groups', function(){ + describe('header block', function(){ + const header = element(by.css('.col-sm-12 > p.bs-callout.bs-callout-danger')); + + it('has 4 labels', function() { + expect(header.all(by.tagName('br')).count()).toBe(4); + }); + + it('shows the unique report id', function() { + expect(header.element(by.css('.fa-arrow-circle-right + a')).getAttribute('href')).toMatch(/\/reports\/[0-9]+/); + }); + + it('shows the created date', function() { + expect(header.element(by.css('.fa-calendar-o + strong')).getText()).toMatch(/.+ago/); + }); + + it('shows the "last edited" date', function() { + expect(header.element(by.css('.fa-calendar-o + strong + br + .fa-calendar-o + strong')).getText()).toMatch(/.+ago/); + }); + + it('shows that the report is editable', function() { + expect(header.element(by.css('.fa-warning + strong')).getText()).toBe('Editable'); + }); + }); + + describe('documentation panel', function(){ + it('shows the empty warning', function() { + expect(element(by.css('#description-display > div')).getAttribute('class')).toContain('alert-warning'); + }); + + it('has an "Add" button', function() { + expect(element(by.css('#btn-edit-doc > i')).getAttribute('class')).toContain('fa-edit'); + }); + }); + + describe('experiments list panel', function(){ + describe('table', function(){ + const table = element(by.css('#experiment-list-test')); + + it('has 8 columns', function(){ + const cols = table.all(by.css('thead th')); + expect(cols.count()).toBe(8); + + expect(cols.get(0).getAttribute('class')).toBe('delete'); + expect(cols.get(1).getAttribute('class')).toBe('attestation'); + expect(cols.get(2).getAttribute('class')).toBe('privacy'); + expect(cols.get(3).getAttribute('class')).toBe('status'); + expect(cols.get(4).getAttribute('class')).toBe('date'); + expect(cols.get(5).getText()).toBe('Name'); + expect(cols.get(6).getAttribute('class')).toBe('datasets'); + expect(cols.get(7).getAttribute('class')).toBe('analyzers'); + }); + + it('has 5 or less rows', function() { + const rows = table.all(by.css('tbody > tr')); + expect(rows.count()).toBeLessThan(6); + }); + + it('has a "Remove Selected Experiments" button', function() { + const b = element(by.buttonText('Remove Selected Experiments')); + expect(b.getAttribute('disabled')).toBeDefined(); + }); + }); + }); + + describe('report content block', function(){ + const groupsLayout = element(by.css('#groupsLayout')); + + describe('group1 container header', function(){ + const header = element(by.css('#group1-heading')); + const children = element.all(by.css('#group1-heading > h4 > *')); + + it('has 5 children: a collapse link, name widget, a button group, and the add items menu', function(){ + expect(children.count()).toBe(4); + }); + + describe('collapse link', function(){ + it('toggles "#collapse-group1"', function(){ + expect(children.get(0).getAttribute('href')).toContain('#collapse-group1'); + }); + }); + + describe('group name widget', function(){ + const widgetEls = children.get(1).all(by.tagName('span')); + const label = widgetEls.get(0); + const button = widgetEls.get(1); + + it('has value "group1"', function(){ + expect(label.getText()).toBe('group1'); + }); + + describe('edit button', function(){ + it('has the pencil glyphicon', function(){ + expect(button.getAttribute('class')).toBe('glyphicon glyphicon-pencil'); + }); + + it('is clickable', function(){ + // no direct way to check if clickable... + // so make sure that its not disabled & its displayed instead + expect(button.getAttribute('disabled')).toBeNull(); + expect(button.isDisplayed()).toBeTruthy(); + }); + }); + + }); + + describe('button group', function(){ + const grp = children.get(2); + const btnChildren = grp.all(by.className('btn')); + + it('is a btn group', function(){ + expect(grp.getAttribute('class')).toContain('btn-group'); + }); + + it('is an action buttons', function(){ + expect(grp.getAttribute('class')).toContain('action-buttons'); + }); + + it('has btn children', function() { + const btnChildren = grp.all(by.className('btn')); + expect(btnChildren.count()).toBe(2); + }); + + it('has a button to delete a group with a red "X"', function(){ + expect(btnChildren.get(0).getAttribute('title')).toBe('Delete Group'); + expect(btnChildren.get(0).element(by.css('i')).getAttribute('class')).toContain('fa-times'); + }); + + it('has a button to drag & sort the group with a 4-directional arrow', function(){ + expect(btnChildren.get(1).getAttribute('title')).toBe('Drag to re-order group'); + expect(btnChildren.get(1).element(by.css('i')).getAttribute('class')).toContain('fa-arrows'); + }); + }); + + describe('add items menu', function(){ + const grp = children.get(3); + const buttons = grp.all(by.tagName('button')); + + it('has 3 buttons', function(){ + expect(buttons.count()).toBe(3); + }); + + it('is all disabled', function(){ + expect(buttons.filter(b => b.getAttribute('disabled').then(d => d)).count()).toBe(3); + }); + + it('has a button to add plots', function(){ + const el = element(by.buttonText('Add Plot')); + expect(el).toBeDefined(); + }); + + it('has a button to add tables', function(){ + const el = element(by.buttonText('Add Table')); + expect(el).toBeDefined(); + }); + + it('has a button to add text blocks', function(){ + const el = element(by.buttonText('Add Text Block')); + expect(el).toBeDefined(); + }); + }); + }); + + describe('group1 experiments panel', function(){ + const header = element(by.css('#group1-explist-heading')); + const body = element(by.css('#collapse-group1-explist')); + + it('is visible', function(){ + expect(header.isDisplayed()).toBeTruthy(); + expect(body.isDisplayed()).toBeTruthy(); + }); + + it('is empty', function(){ + expect(element.all(by.css('#collapse-group1-explist > .panel-body > *')).count()).toBe(0); + }); + + describe('header button', function(){ + const button = element(by.css('#group1_exp_add_dropdown')); + + it('is non-disabled', function(){ + expect(button.getAttribute('disabled')).toBeNull(); + }); + + it('has the text "Add Experiment"', function(){ + expect(button.getText()).toBe('Add Experiment'); + }); + }); + }); + + describe('group1 content panel', function(){ + it('doesnt exist', function(){ + expect(element.all(by.css('#collapse-group1 > *')).count()).toBe(1); + }); + }); + }); + }); + + describe('group experiments panel management', function(){ + const g1ExpPanel = element(by.css('#collapse-group1 > .panel-body > .panel')); + const addButton = element(by.css('#group1_exp_add_dropdown')); + + // add back the exp to group1 + afterAll(function(){ + const parent = addButton.element(by.xpath('..')); + + parent.getAttribute('class') + .then(cls => cls.includes('open') || addButton.click()) + .then(() => { + // make sure list opened correctly & add an exp + const list = parent.element(by.css('ul')); + const first = list.element(by.tagName('li')); + browser.wait(until.elementToBeClickable(first), 5000, 'First add button isnt becoming clickable') + + return first.click(); + }); + }); + + it('adds 1 experiment to group1', function(){ + // open add exp menu + addButton.click() + .then(() => { + // make sure list opened correctly & add an exp + const parent = addButton.element(by.xpath('..')); + expect(parent.getAttribute('class')).toContain('open'); + const list = parent.element(by.css('ul')); + const lis = list.all(by.css('li')); + expect(lis.count()).toBeGreaterThan(0); + + return lis.get(0).element(by.css('a')).click(); + }) + .then(() => { + // check state of group after adding exp + const analyzer = element(by.css('#group1-explist-heading > h4 > i')); + expect(analyzer.isDisplayed()).toBeTruthy(); + expect(analyzer.getText()).not.toBe(''); + + const expsBody = element(by.css('#collapse-group1-explist > .panel-body')); + expect(expsBody.getText()).not.toBe(''); + }); + }); + + describe('group1 experiment table state with 1 experiment', function(){ + const expsTable = element(by.css('#collapse-group1-explist > .panel-body > table')); + const headers = expsTable.all(by.css('thead > tr > th')); + const row = expsTable.element(by.css('tbody > tr')); + + describe('column layout', function(){ + it('has 4 columns', function(){ + expect(headers.count()).toBe(4); + }); + + it('has an empty col for rm exp buttons', function() { + expect(headers.get(0).getText()).toBe(''); + }); + + it('has experiment names col', function() { + expect(headers.get(1).getText()).toBe('Experiment'); + }); + + it('has dbs/protocols col', function() { + expect(headers.get(2).getText()).toBe('Databases/Protocols'); + }); + + it('has aliases col', function() { + expect(headers.get(3).getText()).toBe('Alias'); + }); + }); + + describe('row layout', function(){ + const cells = row.all(by.css('td')); + + it('has 4 entries', function() { + expect(cells.count()).toBe(4); + }); + + it('has a delete button in the first row', function() { + expect(cells.get(0).element(by.tagName('span')).getAttribute('class')).toContain('btn-delete'); + }); + + it('has the experiment name and a link to the experiment in the second row', function() { + const a = cells.get(1).element(by.css('a')); + expect(a.getText()).not.toBe(''); + }); + + it('has a list of formatted dbs & protocols in the third row', function() { + const els = cells.get(2).all(by.css('span')); + expect(els.count()).toBeGreaterThan(0); + expect(els.get(0).element(by.css('a')).getText()).toMatch(/\S+@\S+/); + }); + + it('has an editable alias input in the fourth row', function() { + const input = cells.get(3).element(by.css('input')); + const pExpName = cells.get(1).element(by.css('a')).getText(); + pExpName + .then(expName => { + const lastSeg = expName.split('/').filter(s => s.length > 0).pop(); + expect(input.getAttribute('value')).toBe(lastSeg); + expect(input.getAttribute('disabled')).toBeNull(); + }); + }); + + }); + }); + + describe('removing 1 experiment', function(){ + beforeAll(function(){ + const rmExpButton = element(by.css('#collapse-group1-explist tbody .btn-delete')); + return rmExpButton.click(); + }); + + it('removes the exp table', function(){ + const explistPanel = element(by.css('#collapse-group1-explist > .panel-body')); + expect(explistPanel.getText()).toBe(''); + }); + + it('removes the analyzer tag', function() { + expect(element.all(by.css('#group1-explist-heading > h4 > *')).count()).toBe(2); + }); + + it('lets the user add any experiment again', function() { + const expRowsCount = element.all(by.css('#experiment-list-test tbody > tr')).count(); + + Promise.all([expRowsCount, element(by.css('#group1_exp_add_dropdown')).click()]) + .then(([expCount]) => + Promise.all([ + expCount, + element('#group1_exp_add_dropdown').element(by.xpath('..')).element(by.css('ul')).all(by.css('li')).count() + ]) + ) + .then(([expCount, optsCount]) => { + expect(expCount).toBe(optsCount); + }); + }); + }); + }); + + // should have 1 exp in group + describe('group report items panel management', function(){ + const addPlot = () => { + const addButton = element(by.partialButtonText('Add Plot')); + return addButton.click() + .then(() => { + const parent = addButton.element(by.xpath('..')); + const firstPlotLi = parent.element(by.css('ul > li')); + return firstPlotLi.click(); + }) + ; + }; + + const addTable = () => { + const addButton = element(by.partialButtonText('Add Table')); + return addButton.click(); + }; + + const addTextBlock = () => { + const addButton = element(by.partialButtonText('Add Text Block')); + return addButton.click(); + }; + + afterAll(function() { + // add a plot, table, & text block + return addPlot() + .then(() => addTable()) + .then(() => addTextBlock()) + ; + }); + + describe('plot items', function(){ + describe('adding a plot', function() { + beforeAll(function() { + // add plot here + return addPlot(); + }); + + it('adds a plot', function() { + const plotContainer = element(by.css('#collapse-group1_plot_0')); + expect(plotContainer.isDisplayed()).toBeTruthy(); + }); + + it('eventually renders the plot', function() { + const plotContainer = element(by.css('#collapse-group1_plot_0 > .panel-body > div')); + + browser.wait(until.presenceOf(plotContainer.element(by.css('img'))), + 5000, 'Plot render hasnt been inserted in Angular for 5s'); + + const img = plotContainer.element(by.css('img')); + expect(img.isDisplayed()).toBeTruthy(); + }); + }); + + describe('removing a plot', function() { + beforeAll(function() { + // rm plot here + const delButton = element(by.css('#group1_plot_0-heading .btn-delete')); + + return delButton.click(); + }); + + it('removes the plot and has no report items', function() { + const groupBodyChildren = element(by.model('group._reportItems')).all(by.css('*')); + expect(groupBodyChildren.count()).toBe(0); + }); + }); + }); + + describe('table items', function() { + describe('adding a table', function() { + beforeAll(function() { + // add table here + return addTable(); + }); + + it('adds a table', function() { + const tableContainer = element(by.css('#collapse-group1_table_0 .panel-body')); + expect(tableContainer.isDisplayed()).toBeTruthy(); + }); + + describe('layout', function() { + const panelHeaderButtonGroup = element.all(by.css('#group1_table_0-heading > h4 > .btn-group')).get(1); + const colsButton = panelHeaderButtonGroup.element(by.css('#group1_table_0_columnSelector')); + const precButton = panelHeaderButtonGroup.element(by.css('#group1_table_0-precision')); + const tcsvButton = panelHeaderButtonGroup.element(by.buttonText('Toggle CSV View')); + + const table = element(by.css('#collapse-group1_table_0 table')); + const headers = table.all(by.css('thead > tr > th')); + const rows = table.all(by.css('tbody > tr')); + + it('has at least one column for the name of the experiments', function() { + expect(headers.count()).toBeGreaterThan(0); + expect(headers.get(0).element(by.css('a')).getText()).toContain('Experiment'); + }); + + it('has a default precision of 10', function() { + expect(precButton.getText()).toBe('Float Precision: 10'); + }); + + it('has enabled button for choosing columns', function() { + expect(colsButton.getAttribute('disabled')).toBeNull(); + }); + + it('has enabled button for choosing precision', function() { + expect(precButton.getAttribute('disabled')).toBeNull(); + }); + + it('has enabled button for toggling the csv view', function() { + expect(tcsvButton.getAttribute('disabled')).toBeNull(); + }); + + it('has 1 row with the name col matching the alias', function() { + const pName = rows.get(0).element(by.css('td')).getText(); + const pCurrAlias = element(by.css('#collapse-group1-explist table > tbody input')).getAttribute('value'); + Promise.all([pName, pCurrAlias]) + .then(([name, currAlias]) => expect(name).toBe(currAlias)); + }); + }); + }); + + describe('removing a table', function() { + beforeAll(function() { + // rm table here + const delButton = element(by.css('#group1_table_0-heading .btn-delete')); + + return delButton.click(); + }); + + it('removes the table and has no report items', function() { + const groupBodyChildren = element(by.model('group._reportItems')).all(by.css('*')); + expect(groupBodyChildren.count()).toBe(0); + }); + }); + }); + + describe('text block items', function() { + describe('adding a text block', function() { + beforeAll(function(){ + // add text block here + return addTextBlock(); + }); + + it('added a text block to the group', function() { + const textContainer = element(by.css('#collapse-group1_text_0 .panel-body')); + expect(textContainer.isDisplayed()).toBeTruthy(); + }); + + it('is blank/empty', function() { + const emptyContainer = element(by.css('#collapse-group1_text_0 > .panel-body > .row > .col-sm-10 > div')); + expect(emptyContainer.getText()).toBe(''); + }); + + it('shows the "edit" button', function() { + const editButton = element(by.css('#collapse-group1_text_0 > .panel-body > .row > .col-sm-2 > div > a')); + expect(editButton.getText()).toContain('Edit'); + }); + }); + + describe('removing a text block', function() { + beforeAll(function() { + // rm text here + const delButton = element(by.css('#group1_text_0-heading .btn-delete')); + + return delButton.click(); + }); + + it('removes the text and has no report items', function() { + const groupBodyChildren = element(by.model('group._reportItems')).all(by.css('*')); + expect(groupBodyChildren.count()).toBe(0); + }); + }); + }); + }); + + describe('collapse functionality', function() { + // why are there all these 'sleep' calls before expanding the collapsed panel? + // cuz, for some reason, theres a slight delay in it changing to the collapsed state in the DOM, + // and it being available for being expanded + describe('of groups', function() { + const collapseButton = element(by.css('#group1-heading > h4 > a')); + + it('should collapse', function() { + browser.executeScript('arguments[0].click();', collapseButton.getWebElement()) + .then(() => expect(collapseButton.getAttribute('class')).toBe('collapsed')) + ; + }); + + it('should expand', function() { + browser.sleep(500); + browser.executeScript('arguments[0].click();', collapseButton.getWebElement()) + .then(() => expect(collapseButton.getAttribute('class')).toBe('')) + ; + }); + }); + + describe('of experiments panel', function() { + const collapseButton = element(by.css('#group1-explist-heading > h4 > a')); + + it('should collapse', function() { + browser.executeScript('arguments[0].click();', collapseButton.getWebElement()) + .then(() => expect(collapseButton.getAttribute('class')).toBe('collapsed')) + ; + }); + + it('should expand', function() { + browser.sleep(500); + browser.executeScript('arguments[0].click();', collapseButton.getWebElement()) + .then(() => expect(collapseButton.getAttribute('class')).toBe('')) + ; + }); + }); + + describe('of plots', function() { + const collapseButton = element(by.css('#group1_plot_0-heading > h4 > a')); + + it('should collapse', function() { + browser.executeScript('arguments[0].click();', collapseButton.getWebElement()) + .then(() => expect(collapseButton.getAttribute('class')).toBe('collapsed')) + ; + }); + + it('should expand', function() { + browser.sleep(500); + browser.executeScript('arguments[0].click();', collapseButton.getWebElement()) + .then(() => expect(collapseButton.getAttribute('class')).toBe('')) + ; + }); + }); + + describe('of tables', function() { + const collapseButton = element(by.css('#group1_table_0-heading > h4 > a')); + + it('should collapse', function() { + browser.executeScript('arguments[0].click();', collapseButton.getWebElement()) + .then(() => expect(collapseButton.getAttribute('class')).toBe('collapsed')) + ; + }); + + it('should expand', function() { + browser.sleep(500); + browser.executeScript('arguments[0].click();', collapseButton.getWebElement()) + .then(() => expect(collapseButton.getAttribute('class')).toBe('')) + ; + }); + }); + + describe('of text blocks', function() { + const collapseButton = element(by.css('#group1_text_0-heading > h4 > a')); + + it('should collapse', function() { + browser.executeScript('arguments[0].click();', collapseButton.getWebElement()) + .then(() => expect(collapseButton.getAttribute('class')).toBe('collapsed')) + ; + }); + + it('should expand', function() { + browser.sleep(500); + browser.executeScript('arguments[0].click();', collapseButton.getWebElement()) + .then(() => expect(collapseButton.getAttribute('class')).toBe('')) + ; + }); + }); + }); + + describe('group experiment panel functionality', function() { + const firstRowCells = element(by.css('#collapse-group1-explist tbody > tr')).all(by.css('td')); + + describe('alias field', function() { + const alias = element(by.css('#collapse-group1-explist table > tbody input')); + const strToType = 'asdf'; + + it('lets you change the alias', function() { + alias.sendKeys(strToType) + .then(() => expect(alias.getAttribute('value')).toContain(strToType)) + ; + }); + + it('changed the experiment name in the first row in the table item', function(){ + const nameEl = element(by.css('#collapse-group1_table_0 td')); + + expect(nameEl.getText()).toContain(strToType); + }); + }); + }); + + describe('group report items functionality', function() { + describe('plot', function() { + const plotContainer = element(by.css('#group1_plot_0-render')); + }); + + describe('table', function() { + const panelHeaderButtonGroup = element.all(by.css('#group1_table_0-heading > h4 > .btn-group')).get(1); + const colsButton = panelHeaderButtonGroup.element(by.css('#group1_table_0_columnSelector')); + const precButton = panelHeaderButtonGroup.element(by.css('#group1_table_0-precision')); + const tcsvButton = panelHeaderButtonGroup.element(by.buttonText('Toggle CSV View')); + + const table = element(by.css('#collapse-group1_table_0 table')); + const headers = table.all(by.css('thead > tr > th')); + const rows = table.all(by.css('tbody > tr')); + + it('changes columns to include "total_execution_time"', function() { + const b = element(by.partialButtonText('Choose Columns')); + b.click() + .then(() => { + const execTimeCol = element.all(by.css('#colSelectorId > .dropdown-menu > form > fieldset > div')) + .filter(e => e.element(by.tagName('input')).getAttribute('value').then(v => v == 'total_execution_time')) + .first() + ; + + return execTimeCol.click(); + }) + .then(() => element(by.buttonText('Save Column Choices')).click()) + .then(() => { + const timeCols = element.all(by.css('#collapse-group1_table_0 thead a')) + .filter(a => a.getText().then(t => t.includes('total_execution_time'))); + + return expect(timeCols.count()).toBe(1); + }) + ; + }); + + it('changes precision from 10 to 7', function() { + const b = element(by.partialButtonText('Float Precision')); + b.click() + .then(() => element(by.linkText('7')).click()) + .then(() => expect(element(by.partialButtonText('Float Precision')).getText()).toContain('7')) + .then(() => maps = element.all(by.css('#collapse-group1_table_0 tbody tr td')) + .map(td => td.getText())) + .then(txts => txts + .filter(t => !isNaN(t) && t.includes('.')) + .forEach(t => expect(t.split('.')[1].length).toBeLessThan(8)) + ) + ; + }); + }); + + /* + * the 'x' cancels tests from running + * unfortunately there seems to be a selenium/protractor/webdriver bug + * with looking into the codemirror editor (`ui-codemirror`) + * none of the codemirror HTML elements are registering with + * selenium/protractor as being visible + */ + xdescribe('text block', function() { + const getHtmlContainer = () => element(by.css('#collapse-group1_text_0 > .panel-body > .row > .col-sm-10 > div')); + const getEditButton = () => element(by.css('#collapse-group1_text_0')).element(by.partialLinkText('Edit')); + + const text = '1. asdf'; + + describe('edit mode', function() { + it('turns on after clicking the edit button', function(){ + getEditButton().click() + .then(() => { + browser.wait(until.presenceOf(element(by.css('.CodeMirror'))), 5000, 'Nope'); + const editor = element.all(by.css('.CodeMirror')); + expect(editor.count()).toBeGreaterThan(0); + }) + ; + }) + + it('has cancel button', function() { + expect(element(by.css('#collapse-group1_text_0')).element(by.partialLinkText('Cancel')) + .isDisplayed()).toBeTruthy(); + }); + + it('has save button', function() { + expect(element(by.css('#collapse-group1_text_0')).element(by.partialLinkText('Save')) + .isDisplayed()).toBeTruthy(); + }); + + it('lets you edit the RST', function() { + const textArea = element(by.css('.CodeMirror textarea')); + expect(textArea.isDisplayed()).toBeTruthy(); + + textArea.sendKeys(text) + .then(() => expect(textArea.getAttribute('value')).toBe(text)) + ; + }); + }); + + describe('cancelling edits', function() { + it('moves to view mode', function() { + element(by.css('#collapse-group1_text_0')).element(by.partialLinkText('Cancel')) + .click() + .then(() => expect(getEditButton().isDisplayed()).toBeTruthy()) + }); + + it('didnt save the typed RST', function(){ + expect(getHtmlContainer().getText()).toBe(''); + }); + }); + + describe('submitting edits', function() { + beforeAll(function(){ + getEditButton().click() + .then(() => element(by.css('#collapse-group1_text_0 textarea')).sendKeys(text)) + .then(() => element(by.css('#collapse-group1_text_0')).element(by.partialLinkText('Save')).click()) + .then(() => browser.wait(until.presenceOf(element(by.css('#collapse-group1_text_0 ol.arabic.simple'))), 5000, 'HTML didnt render correctly')) + ; + }); + + it('renders the text correctly', function() { + expect(getHtmlContainer().isDisplayed()).toBeTruthy(); + + expect(getHtmlContainer().getText()).toBe( + `<ol class="arabic simple"> + <li>asdf</li> + </ol>` + ); + }); + }); + }); + }); + + // need to save report before doing these + describe('links outside the report page', function(){ + beforeAll(function(){ + const saveButton = element(by.css('#save-button')); + + return browser.executeScript('arguments[0].click();', saveButton.getWebElement()); + }); + + afterEach(function(){ + return browser.get('http://localhost:8000/reports/user/test/'); + }); + + const firstRowCells = element(by.css('#collapse-group1-explist tbody > tr')).all(by.css('td')); + const link1 = firstRowCells.get(1).element(by.css('a')); + const link2 = firstRowCells.get(2).element(by.css('a')); + + describe('experiment link', function() { + it('links to a valid experiment', function() { + Promise.all([link1.getText(), link1.click()]) + .then(([expName]) => expect(browser.getTitle()).toBe(`BEAT - ${expName}`)) + ; + }); + }); + + describe('database~protocol link', function() { + afterAll(function(){ + return browser.navigate().back(); + }); + + it('links to a valid database~protocol', function() { + Promise.all([link2.getText(), link2.click()]) + .then(([dbProtName]) => { + expect(browser.getTitle()).toBe(`BEAT - ${dbProtName.split('@')[0]}`) + }) + ; + }); + }); + }); }); diff --git a/beat/web/reports/static/reports/test/test-spec.js b/beat/web/reports/static/reports/test/test-spec.js index b22411eb9..6f1a392c1 100644 --- a/beat/web/reports/static/reports/test/test-spec.js +++ b/beat/web/reports/static/reports/test/test-spec.js @@ -1,18 +1,18 @@ // 'describe' blocks can hold other 'describe' blocks and test blocks describe('BEAT platform', function() { - /* - * the BEAT platform does not use Angular in a way that - * Protractor can automatically reason about, - * so disable special Angular features - */ - browser.ignoreSynchronization = true; - // 'it' blocks are individual tests - it('should have the page title of "BEAT"', function() { - // 'browser' is a global object representing the browser - // assumes the BEAT web server is running locally on port 8000 - browser.get('http://localhost:8000'); + /* + * the BEAT platform does not use Angular in a way that + * Protractor can automatically reason about, + * so disable special Angular features + */ + browser.ignoreSynchronization = true; + // 'it' blocks are individual tests + it('should have the page title of "BEAT"', function() { + // 'browser' is a global object representing the browser + // assumes the BEAT web server is running locally on port 8000 + browser.get('http://localhost:8000'); - // simple test to sanity check Protractor - expect(browser.getTitle()).toEqual('BEAT'); - }); + // simple test to sanity check Protractor + expect(browser.getTitle()).toEqual('BEAT'); + }); }); diff --git a/beat/web/toolchains/static/toolchains/js/common.js b/beat/web/toolchains/static/toolchains/js/common.js index 276503c47..15817d48e 100644 --- a/beat/web/toolchains/static/toolchains/js/common.js +++ b/beat/web/toolchains/static/toolchains/js/common.js @@ -1,21 +1,21 @@ /* * Copyright (c) 2016 Idiap Research Institute, http://www.idiap.ch/ * Contact: beat.support@idiap.ch - * + * * This file is part of the beat.web module of the BEAT platform. - * + * * Commercial License Usage * Licensees holding valid commercial BEAT licenses may use this file in * accordance with the terms contained in a written agreement between you * and Idiap. For further information contact tto@idiap.ch - * + * * Alternatively, this file may be used under the terms of the GNU Affero * Public License version 3 as published by the Free Software and appearing * in the file LICENSE.AGPL included in the packaging of this file. * The BEAT platform 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. - * + * * You should have received a copy of the GNU Affero Public License along * with the BEAT platform. If not, see http://www.gnu.org/licenses/. */ diff --git a/beat/web/toolchains/static/toolchains/js/dialogs.js b/beat/web/toolchains/static/toolchains/js/dialogs.js index 32e5b61c6..814aa761a 100644 --- a/beat/web/toolchains/static/toolchains/js/dialogs.js +++ b/beat/web/toolchains/static/toolchains/js/dialogs.js @@ -1,21 +1,21 @@ /* * Copyright (c) 2016 Idiap Research Institute, http://www.idiap.ch/ * Contact: beat.support@idiap.ch - * + * * This file is part of the beat.web module of the BEAT platform. - * + * * Commercial License Usage * Licensees holding valid commercial BEAT licenses may use this file in * accordance with the terms contained in a written agreement between you * and Idiap. For further information contact tto@idiap.ch - * + * * Alternatively, this file may be used under the terms of the GNU Affero * Public License version 3 as published by the Free Software and appearing * in the file LICENSE.AGPL included in the packaging of this file. * The BEAT platform 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. - * + * * You should have received a copy of the GNU Affero Public License along * with the BEAT platform. If not, see http://www.gnu.org/licenses/. */ diff --git a/beat/web/toolchains/static/toolchains/js/editor.js b/beat/web/toolchains/static/toolchains/js/editor.js index 2a90b5c39..88056781b 100644 --- a/beat/web/toolchains/static/toolchains/js/editor.js +++ b/beat/web/toolchains/static/toolchains/js/editor.js @@ -1,21 +1,21 @@ /* * Copyright (c) 2016 Idiap Research Institute, http://www.idiap.ch/ * Contact: beat.support@idiap.ch - * + * * This file is part of the beat.web module of the BEAT platform. - * + * * Commercial License Usage * Licensees holding valid commercial BEAT licenses may use this file in * accordance with the terms contained in a written agreement between you * and Idiap. For further information contact tto@idiap.ch - * + * * Alternatively, this file may be used under the terms of the GNU Affero * Public License version 3 as published by the Free Software and appearing * in the file LICENSE.AGPL included in the packaging of this file. * The BEAT platform 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. - * + * * You should have received a copy of the GNU Affero Public License along * with the BEAT platform. If not, see http://www.gnu.org/licenses/. */ @@ -4306,7 +4306,7 @@ beat.toolchains.editor.ToolchainView.prototype._onMouseWheel = function(event) var previous_zoom = this.zoom; var must_redraw = false; - // With Ctrl key: control the zoom + // With Ctrl key: control the zoom if (event.ctrlKey) { // Scroll up @@ -4320,7 +4320,7 @@ beat.toolchains.editor.ToolchainView.prototype._onMouseWheel = function(event) must_redraw = (previous_zoom != this.zoom); } - // Without modifier key: translate the view + // Without modifier key: translate the view else { var mouse_position = this._getMousePosition(event); @@ -4378,7 +4378,7 @@ beat.toolchains.editor.ToolchainView.prototype._updateSelectionOrActionDataPosit } } } - + // If we are currently importing a toolchain, correctly update the position // of all the blocks and connections else if (this.current_action == beat.toolchains.editor.ACTION_TOOLCHAIN_IMPORTATION) diff --git a/beat/web/toolchains/static/toolchains/js/models.js b/beat/web/toolchains/static/toolchains/js/models.js index ee21ca690..aa03273d1 100644 --- a/beat/web/toolchains/static/toolchains/js/models.js +++ b/beat/web/toolchains/static/toolchains/js/models.js @@ -1,21 +1,21 @@ /* * Copyright (c) 2016 Idiap Research Institute, http://www.idiap.ch/ * Contact: beat.support@idiap.ch - * + * * This file is part of the beat.web module of the BEAT platform. - * + * * Commercial License Usage * Licensees holding valid commercial BEAT licenses may use this file in * accordance with the terms contained in a written agreement between you * and Idiap. For further information contact tto@idiap.ch - * + * * Alternatively, this file may be used under the terms of the GNU Affero * Public License version 3 as published by the Free Software and appearing * in the file LICENSE.AGPL included in the packaging of this file. * The BEAT platform 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. - * + * * You should have received a copy of the GNU Affero Public License along * with the BEAT platform. If not, see http://www.gnu.org/licenses/. */ diff --git a/beat/web/toolchains/static/toolchains/js/utils.js b/beat/web/toolchains/static/toolchains/js/utils.js index 654735cef..96fd6380c 100644 --- a/beat/web/toolchains/static/toolchains/js/utils.js +++ b/beat/web/toolchains/static/toolchains/js/utils.js @@ -1,21 +1,21 @@ /* * Copyright (c) 2016 Idiap Research Institute, http://www.idiap.ch/ * Contact: beat.support@idiap.ch - * + * * This file is part of the beat.web module of the BEAT platform. - * + * * Commercial License Usage * Licensees holding valid commercial BEAT licenses may use this file in * accordance with the terms contained in a written agreement between you * and Idiap. For further information contact tto@idiap.ch - * + * * Alternatively, this file may be used under the terms of the GNU Affero * Public License version 3 as published by the Free Software and appearing * in the file LICENSE.AGPL included in the packaging of this file. * The BEAT platform 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. - * + * * You should have received a copy of the GNU Affero Public License along * with the BEAT platform. If not, see http://www.gnu.org/licenses/. */ diff --git a/beat/web/toolchains/static/toolchains/js/viewer.js b/beat/web/toolchains/static/toolchains/js/viewer.js index f3736f024..ac886c0f9 100644 --- a/beat/web/toolchains/static/toolchains/js/viewer.js +++ b/beat/web/toolchains/static/toolchains/js/viewer.js @@ -1,21 +1,21 @@ /* * Copyright (c) 2016 Idiap Research Institute, http://www.idiap.ch/ * Contact: beat.support@idiap.ch - * + * * This file is part of the beat.web module of the BEAT platform. - * + * * Commercial License Usage * Licensees holding valid commercial BEAT licenses may use this file in * accordance with the terms contained in a written agreement between you * and Idiap. For further information contact tto@idiap.ch - * + * * Alternatively, this file may be used under the terms of the GNU Affero * Public License version 3 as published by the Free Software and appearing * in the file LICENSE.AGPL included in the packaging of this file. * The BEAT platform 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. - * + * * You should have received a copy of the GNU Affero Public License along * with the BEAT platform. If not, see http://www.gnu.org/licenses/. */ @@ -661,7 +661,7 @@ beat.toolchains.viewer.ToolchainViewer.prototype._onMouseWheel = function(event) var previous_zoom = this.zoom; var must_redraw = false; - // With Ctrl key: control the zoom + // With Ctrl key: control the zoom if (event.ctrlKey) { // Scroll up @@ -681,7 +681,7 @@ beat.toolchains.viewer.ToolchainViewer.prototype._onMouseWheel = function(event) must_redraw = (previous_zoom != this.zoom); } - // Without modifier key: translate the view + // Without modifier key: translate the view else { var mouse_position = this._getMousePosition(event); diff --git a/beat/web/ui/static/ui/js/history.js b/beat/web/ui/static/ui/js/history.js index 028d94ca2..c204a79c7 100644 --- a/beat/web/ui/static/ui/js/history.js +++ b/beat/web/ui/static/ui/js/history.js @@ -1,21 +1,21 @@ /* * Copyright (c) 2016 Idiap Research Institute, http://www.idiap.ch/ * Contact: beat.support@idiap.ch - * + * * This file is part of the beat.web module of the BEAT platform. - * + * * Commercial License Usage * Licensees holding valid commercial BEAT licenses may use this file in * accordance with the terms contained in a written agreement between you * and Idiap. For further information contact tto@idiap.ch - * + * * Alternatively, this file may be used under the terms of the GNU Affero * Public License version 3 as published by the Free Software and appearing * in the file LICENSE.AGPL included in the packaging of this file. * The BEAT platform 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. - * + * * You should have received a copy of the GNU Affero Public License along * with the BEAT platform. If not, see http://www.gnu.org/licenses/. */ diff --git a/beat/web/ui/static/ui/js/listselector.js b/beat/web/ui/static/ui/js/listselector.js index 83ca04104..f77e644ba 100644 --- a/beat/web/ui/static/ui/js/listselector.js +++ b/beat/web/ui/static/ui/js/listselector.js @@ -1,21 +1,21 @@ /* * Copyright (c) 2016 Idiap Research Institute, http://www.idiap.ch/ * Contact: beat.support@idiap.ch - * + * * This file is part of the beat.web module of the BEAT platform. - * + * * Commercial License Usage * Licensees holding valid commercial BEAT licenses may use this file in * accordance with the terms contained in a written agreement between you * and Idiap. For further information contact tto@idiap.ch - * + * * Alternatively, this file may be used under the terms of the GNU Affero * Public License version 3 as published by the Free Software and appearing * in the file LICENSE.AGPL included in the packaging of this file. * The BEAT platform 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. - * + * * You should have received a copy of the GNU Affero Public License along * with the BEAT platform. If not, see http://www.gnu.org/licenses/. */ diff --git a/beat/web/ui/static/ui/js/multipleselector.js b/beat/web/ui/static/ui/js/multipleselector.js index 4bc4fe541..1911cf830 100644 --- a/beat/web/ui/static/ui/js/multipleselector.js +++ b/beat/web/ui/static/ui/js/multipleselector.js @@ -1,21 +1,21 @@ /* * Copyright (c) 2016 Idiap Research Institute, http://www.idiap.ch/ * Contact: beat.support@idiap.ch - * + * * This file is part of the beat.web module of the BEAT platform. - * + * * Commercial License Usage * Licensees holding valid commercial BEAT licenses may use this file in * accordance with the terms contained in a written agreement between you * and Idiap. For further information contact tto@idiap.ch - * + * * Alternatively, this file may be used under the terms of the GNU Affero * Public License version 3 as published by the Free Software and appearing * in the file LICENSE.AGPL included in the packaging of this file. * The BEAT platform 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. - * + * * You should have received a copy of the GNU Affero Public License along * with the BEAT platform. If not, see http://www.gnu.org/licenses/. */ diff --git a/beat/web/ui/static/ui/js/multipleselector_for_report.js b/beat/web/ui/static/ui/js/multipleselector_for_report.js index 495bf2c86..564cc9626 100644 --- a/beat/web/ui/static/ui/js/multipleselector_for_report.js +++ b/beat/web/ui/static/ui/js/multipleselector_for_report.js @@ -1,21 +1,21 @@ /* * Copyright (c) 2016 Idiap Research Institute, http://www.idiap.ch/ * Contact: beat.support@idiap.ch - * + * * This file is part of the beat.web module of the BEAT platform. - * + * * Commercial License Usage * Licensees holding valid commercial BEAT licenses may use this file in * accordance with the terms contained in a written agreement between you * and Idiap. For further information contact tto@idiap.ch - * + * * Alternatively, this file may be used under the terms of the GNU Affero * Public License version 3 as published by the Free Software and appearing * in the file LICENSE.AGPL included in the packaging of this file. * The BEAT platform 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. - * + * * You should have received a copy of the GNU Affero Public License along * with the BEAT platform. If not, see http://www.gnu.org/licenses/. */ diff --git a/beat/web/ui/static/ui/js/plotterparameterdialog.js b/beat/web/ui/static/ui/js/plotterparameterdialog.js index 1d9aba3c0..f0a85c784 100644 --- a/beat/web/ui/static/ui/js/plotterparameterdialog.js +++ b/beat/web/ui/static/ui/js/plotterparameterdialog.js @@ -1,21 +1,21 @@ /* * Copyright (c) 2016 Idiap Research Institute, http://www.idiap.ch/ * Contact: beat.support@idiap.ch - * + * * This file is part of the beat.web module of the BEAT platform. - * + * * Commercial License Usage * Licensees holding valid commercial BEAT licenses may use this file in * accordance with the terms contained in a written agreement between you * and Idiap. For further information contact tto@idiap.ch - * + * * Alternatively, this file may be used under the terms of the GNU Affero * Public License version 3 as published by the Free Software and appearing * in the file LICENSE.AGPL included in the packaging of this file. * The BEAT platform 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. - * + * * You should have received a copy of the GNU Affero Public License along * with the BEAT platform. If not, see http://www.gnu.org/licenses/. */ diff --git a/beat/web/ui/static/ui/js/save_as_dialog.js b/beat/web/ui/static/ui/js/save_as_dialog.js index fbb9f6c79..d9b8c910c 100644 --- a/beat/web/ui/static/ui/js/save_as_dialog.js +++ b/beat/web/ui/static/ui/js/save_as_dialog.js @@ -1,21 +1,21 @@ /* * Copyright (c) 2016 Idiap Research Institute, http://www.idiap.ch/ * Contact: beat.support@idiap.ch - * + * * This file is part of the beat.web module of the BEAT platform. - * + * * Commercial License Usage * Licensees holding valid commercial BEAT licenses may use this file in * accordance with the terms contained in a written agreement between you * and Idiap. For further information contact tto@idiap.ch - * + * * Alternatively, this file may be used under the terms of the GNU Affero * Public License version 3 as published by the Free Software and appearing * in the file LICENSE.AGPL included in the packaging of this file. * The BEAT platform 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. - * + * * You should have received a copy of the GNU Affero Public License along * with the BEAT platform. If not, see http://www.gnu.org/licenses/. */ diff --git a/beat/web/ui/static/ui/js/smartselector.js b/beat/web/ui/static/ui/js/smartselector.js index bfadcb77e..f93601171 100644 --- a/beat/web/ui/static/ui/js/smartselector.js +++ b/beat/web/ui/static/ui/js/smartselector.js @@ -1,21 +1,21 @@ /* * Copyright (c) 2016 Idiap Research Institute, http://www.idiap.ch/ * Contact: beat.support@idiap.ch - * + * * This file is part of the beat.web module of the BEAT platform. - * + * * Commercial License Usage * Licensees holding valid commercial BEAT licenses may use this file in * accordance with the terms contained in a written agreement between you * and Idiap. For further information contact tto@idiap.ch - * + * * Alternatively, this file may be used under the terms of the GNU Affero * Public License version 3 as published by the Free Software and appearing * in the file LICENSE.AGPL included in the packaging of this file. * The BEAT platform 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. - * + * * You should have received a copy of the GNU Affero Public License along * with the BEAT platform. If not, see http://www.gnu.org/licenses/. */ @@ -311,7 +311,7 @@ beat.ui.SmartSelector.prototype.display = function(entries, left, top) if (left + width >= window_width) left = window_width - width - 4; } - + if (top === undefined) { top = (window_height - height) / 2; diff --git a/buildout.cfg b/buildout.cfg index d3794a0bc..a186c8641 100644 --- a/buildout.cfg +++ b/buildout.cfg @@ -144,7 +144,7 @@ packages = jquery#~1.11.3 underscore#~1.8.3 datatables#~1.10.10 angular-ui-sortable#~0.14 - angular-ui-codemirror + angular-ui-codemirror executable = ${buildout:bin-directory}/bower base-directory = beat/web downloads = static -- GitLab