diff --git a/beat/web/experiments/models.py b/beat/web/experiments/models.py index 9f9843b5ca02a69cdf6c22440e0f1409869aa59a..fc718d71a24e87340d9971446a1403beaceb4836 100755 --- a/beat/web/experiments/models.py +++ b/beat/web/experiments/models.py @@ -239,9 +239,9 @@ class Experiment(Shareable): #_____ Fields __________ author = models.ForeignKey(User, related_name='experiments', - on_delete=models.CASCADE) - toolchain = models.ForeignKey(Toolchain, - related_name='experiments', on_delete=models.CASCADE) + on_delete=models.CASCADE) + toolchain = models.ForeignKey(Toolchain, related_name='experiments', + on_delete=models.CASCADE) name = models.CharField(max_length=200) short_description = models.CharField(max_length=100, default='', blank=True, help_text=Messages['short_description']) status = models.CharField(max_length=1, choices=STATUS, default=PENDING) @@ -577,7 +577,7 @@ class Experiment(Shareable): b.analyzer=algorithm.analysis() b.environment=env b.queue=queue - b.required_slots=job_description['nb_slots'] + # TEMPORARILY DISABLED: b.required_slots=job_description['nb_slots'] b.channel=job_description['channel'] b.save() @@ -911,21 +911,21 @@ class Block(models.Model): ) experiment = models.ForeignKey(Experiment, related_name='blocks', - on_delete=models.CASCADE) + on_delete=models.CASCADE) name = models.CharField(max_length=200) command = models.TextField(null=True, blank=True) status = models.CharField(max_length=1, choices=STATUS, default=NOT_CACHED) analyzer = models.BooleanField(default=False) algorithm = models.ForeignKey(Algorithm, related_name='blocks', - on_delete=models.CASCADE) + on_delete=models.CASCADE) creation_date = models.DateTimeField(null=True, blank=True, - auto_now_add=True) + auto_now_add=True) start_date = models.DateTimeField(null=True, blank=True) end_date = models.DateTimeField(null=True, blank=True) environment = models.ForeignKey(Environment, related_name='blocks', - null=True, on_delete=models.SET_NULL) + null=True, on_delete=models.SET_NULL) queue = models.ForeignKey(Queue, related_name='blocks', null=True, - on_delete=models.SET_NULL) + on_delete=models.SET_NULL) required_slots = models.PositiveIntegerField(default=1) channel = models.CharField(max_length=200, default='', blank=True, diff --git a/beat/web/experiments/serializers.py b/beat/web/experiments/serializers.py old mode 100644 new mode 100755 index bab80c88bb4e8650d70ef0d01fee8e93d6fd8c07..4dadd9e09505efa39db0bb2bf5faa640ffea0fca --- a/beat/web/experiments/serializers.py +++ b/beat/web/experiments/serializers.py @@ -26,6 +26,7 @@ ############################################################################### from rest_framework import serializers +from django.contrib.humanize.templatetags.humanize import naturaltime from ..common.serializers import ShareableSerializer from ..common.fields import JSONSerializerField @@ -198,18 +199,22 @@ class ExperimentResultsSerializer(ShareableSerializer): attestation = serializers.IntegerField(source='attestation.number') blocks_status = serializers.SerializerMethodField() execution_info = serializers.SerializerMethodField() + execution_order = serializers.SerializerMethodField() + status = serializers.CharField(source="get_status_display") results = serializers.SerializerMethodField() errors = serializers.SerializerMethodField() html_description = serializers.SerializerMethodField() description = serializers.SerializerMethodField() declaration = JSONSerializerField() + display_start_date = serializers.SerializerMethodField() + display_end_date = serializers.SerializerMethodField() class Meta(ShareableSerializer.Meta): model = Experiment - default_fields = ['started', 'done', 'failed', 'blocks_status', + default_fields = ['started', 'done', 'failed', 'status', 'blocks_status', 'results', 'attestation', 'declaration', 'errors', 'sharing', 'accessibility', - 'execution_info'] + 'execution_info', 'execution_order'] def get_started(self, obj): return obj.status not in [Experiment.PENDING, Experiment.SCHEDULED, Experiment.CANCELING] @@ -229,6 +234,8 @@ class ExperimentResultsSerializer(ShareableSerializer): results[block.name] = 'pending' elif block.status == Block.FAILED: results[block.name] = 'failed' + elif block.status == Block.CANCELLED: + results[block.name] = 'cancelled' else: results[block.name] = 'processing' return results @@ -244,6 +251,12 @@ class ExperimentResultsSerializer(ShareableSerializer): } return results + def get_execution_order(self, obj): + results = [] + for block in obj.blocks.order_by('execution_order').iterator(): + results.append(block.name) + return results + def get_results(self, obj): results = {} for k in obj.blocks.filter(analyzer=True).all(): @@ -263,3 +276,21 @@ class ExperimentResultsSerializer(ShareableSerializer): def get_description(self, obj): return obj.description.decode('utf-8') + + def get_display_start_date(self, obj): + if obj.start_date is None: + return None + + return { + 'date': obj.start_date.strftime("%b %d, %Y, %-H:%M"), + 'natural': naturaltime(obj.start_date), + } + + def get_display_end_date(self, obj): + if obj.end_date is None: + return None + + return { + 'date': obj.end_date.strftime("%b %d, %Y, %-H:%M"), + 'natural': naturaltime(obj.end_date), + } diff --git a/beat/web/experiments/static/experiments/js/panels.js b/beat/web/experiments/static/experiments/js/panels.js index a52a0285ba9083c34cc77874858c29ff41c16a64..6d94dd6665c43feacc906cdb171d46509b55106c 100644 --- a/beat/web/experiments/static/experiments/js/panels.js +++ b/beat/web/experiments/static/experiments/js/panels.js @@ -3385,3 +3385,296 @@ beat.experiments.panels.BlockParameters.prototype._createStringControls = functi $(select).addClass('default'); }); } + + +/********************** CLASS: ExecutionDetails ************************************/ + +//---------------------------------------------------------------------- +// Constructor +// +// panel_id: ID of the DOM element to use +//---------------------------------------------------------------------- +beat.experiments.panels.ExecutionDetails = function(panel_id, url_prefix) { + + this.id = panel_id; + this.element = $('#' + panel_id)[0]; + this.configuration = null; + this.url_prefix = url_prefix; +} + + +//---------------------------------------------------------------------- +// Setup the content of the panel from an experiment configuration +//---------------------------------------------------------------------- +beat.experiments.panels.ExecutionDetails.prototype.setup = function( + configuration, templates, urls, experiment_results) +{ + this.configuration = configuration; + + var rendered = this._createGlobalsSection(templates, urls); + rendered += this._createDatasetsSection(templates, urls); + rendered += this._createBlockSections(templates, urls, experiment_results); + + $(this.element).html(rendered); +} + + +//---------------------------------------------------------------------- +// Update the content of the panel from the experiment results +//---------------------------------------------------------------------- +beat.experiments.panels.ExecutionDetails.prototype.update = function(experiment_results) +{ + for (var i = 0; i < experiment_results.execution_order.length; ++i) { + var block_name = experiment_results.execution_order[i]; + var status = experiment_results.blocks_status[block_name]; + + var div = $(this.element).find('#block-' + block_name); + if (div.data('beat-status') != status) { + div.data('beat-status', status); + + div.find('#block-' + block_name + '-status-icon')[0].className = this._getIcon(status); + + if ((status == 'failed') || (status == 'generated')) { + var execution_info = experiment_results.execution_info[block_name]; + + var span = div.find('#block-' + block_name + '-execution-time'); + span.text(' @ ' + execution_info.linear_execution_time.toFixed(2) + 's') + span.show(); + + var li = div.find('#block-' + block_name + '-linear-execution-time'); + li.text('Total execution time: ' + execution_info.linear_execution_time.toFixed(2) + 's') + li.show(); + + if (this.configuration.blockNbSlots(block_name) > 1) { + li = div.find('#block-' + block_name + '-speed-up-real'); + li.text('Slots: ' + this.configuration.blockNbSlots(block_name) + ' (speed-up achieved: ' + execution_info.speed_up_real.toFixed(1) + 'x') + li.show(); + + li = div.find('#block-' + block_name + '-speed-up-maximal'); + li.text('Maximum speed-up achievable: ' + execution_info.speed_up_maximal.toFixed(1) + 'x') + li.show(); + } else { + div.find('#block-' + block_name + '-slots').show(); + } + + li = div.find('#block-' + block_name + '-queuing-time'); + li.text('Queuing time: ' + execution_info.queuing_time.toFixed(2) + 's') + li.show(); + + if (experiment_results.errors) { + for (var j = 0; j < experiment_results.errors.length; ++j) { + if (experiment_results.errors[j].block == block_name) { + if (experiment_results.errors[j].details) { + li = div.find('#block-' + block_name + '-error-report'); + li.find('.console-output').text(experiment_results.errors[j].details); + li.show(); + } + + if (experiment_results.errors[j].stdout) { + li = div.find('#block-' + block_name + '-stdout'); + li.find('.console-output').text(experiment_results.errors[j].stdout); + li.show(); + } + + if (experiment_results.errors[j].stderr) { + li = div.find('#block-' + block_name + '-stderr'); + li.find('.console-output').text(experiment_results.errors[j].stderr); + li.show(); + } + + break; + } + } + } + } + } + } +} + + +//---------------------------------------------------------------------- +// Creates the 'Global Parameters' section +//---------------------------------------------------------------------- +beat.experiments.panels.ExecutionDetails.prototype._createGlobalsSection = function( + templates, urls) +{ + var template = $(templates.globals).html(); + + Mustache.parse(template); + + var environment = this.configuration.defaultEnvironment(); + + var tags = { + environment: { + name: environment.name, + version: environment.version, + url: urls.environment.replace('NAME', environment.name).replace('VERSION', environment.version), + }, + queue: this.configuration.defaultQueue(), + algorithms: [], + } + + var algorithm_names = this.configuration.algorithms(); + for (var i = 0; i < algorithm_names.length; ++i) { + var algorithm_name = algorithm_names[i]; + var algorithm_globals = this.configuration.algorithmGlobals(algorithm_name); + + var items = []; + + var parameters = Object.keys(algorithm_globals); + for (var j = 0; j < parameters.length; ++j) { + items.push({ + name: parameters[j], + value: algorithm_globals[parameters[j]], + }); + } + + if (items.length > 0) { + tags.algorithms.push({ + name: algorithm_name, + url: urls.algorithm.replace('AUTHOR/NAME/0', algorithm_name), + items: items, + }); + } + } + + return Mustache.render(template, tags); +} + + +//---------------------------------------------------------------------- +// Creates the 'Datasets' section +//---------------------------------------------------------------------- +beat.experiments.panels.ExecutionDetails.prototype._createDatasetsSection = function( + templates, urls) +{ + var template = $(templates.datasets).html(); + + Mustache.parse(template); + + var tags = { + datasets: [], + } + + var dataset_names = this.configuration.datasetNames(); + for (var i = 0; i < dataset_names.length; ++i) { + var dataset_name = dataset_names[i]; + var dataset = this.configuration.dataset(dataset_name); + + tags.datasets.push({ + block_name: dataset_name, + url: urls.database.replace('NAME/0', dataset.database), + name: dataset.database, + protocol: dataset.protocol, + set: dataset.set, + }); + } + + return Mustache.render(template, tags); +} + + +//---------------------------------------------------------------------- +// Creates the 'Block' sections +//---------------------------------------------------------------------- +beat.experiments.panels.ExecutionDetails.prototype._createBlockSections = function( + templates, urls, results) +{ + var template = $(templates.block).html(); + + Mustache.parse(template); + + var tags = { + blocks: [], + } + + for (var i = 0; i < results.execution_order.length; ++i) { + var block_name = results.execution_order[i]; + var block = this.configuration.component(block_name); + + var execution_info = results.execution_info[block_name]; + var status = results.blocks_status[block_name]; + + var environment = this.configuration.blockEnvironment(block_name); + + var entry = { + name: block_name, + analyzer: this.configuration.is_analyzer(block_name), + algorithm: block.algorithm, + url: urls.algorithm.replace('AUTHOR/NAME/0', block.algorithm), + + status: status, + failed: status == 'failed', + cached: status == 'generated', + cached_or_failed: (status == 'failed') || (status == 'generated'), + + nb_slots: (this.configuration.blockNbSlots(block_name) > 1 ? this.configuration.blockNbSlots(block_name) : 0), + environment: { + name: environment.name, + version: environment.version, + url: urls.environment.replace('NAME', environment.name).replace('VERSION', environment.version), + }, + queue: this.configuration.blockQueue(block_name), + + has_parameters: this.configuration.isParametrized(block_name), + parameters: [], + + queuing_time: execution_info.queuing_time.toFixed(2), + linear_execution_time: execution_info.linear_execution_time.toFixed(2), + speed_up_real: execution_info.speed_up_real.toFixed(1), + speed_up_maximal: execution_info.speed_up_maximal.toFixed(1), + + error_report: null, + stdout: null, + stderr: null, + + icon: null, + } + + if (block.parameters !== undefined) { + var parameters = Object.keys(block.parameters); + for (var j = 0; j < parameters.length; ++j) { + entry.parameters.push({ + name: parameters[j], + value: block.parameters[parameters[j]], + }); + } + } + + if (results.errors) { + for (var j = 0; j < results.errors.length; ++j) { + if (results.errors[j].block == block_name) { + entry.error_report = results.errors[j].details; + entry.stdout = results.errors[j].stdout; + entry.stderr = results.errors[j].stderr; + break; + } + } + } + + entry.icon = this._getIcon(entry.status); + + tags.blocks.push(entry); + } + + return Mustache.render(template, tags); +} + +//--------------------------------------- + +beat.experiments.panels.ExecutionDetails.prototype._getIcon = function(status) { + if (status == 'pending') + return "icon-scheduled fa fa-play"; + else if (status == 'generated') + return "icon-done fa fa-check"; + else if (status == 'failed') + return "icon-failed fa fa-bug"; + else if (status == 'processing') + return "icon-running fa fa-spin fa-refresh"; + else if (status == 'failed') + return "icon-failed fa fa-bug"; + else if (status == 'cancelled') + return "icon-failed fa fa-power-off"; + else + return "icon-pending fa fa-asterisk"; +} diff --git a/beat/web/experiments/static/experiments/js/utils.js b/beat/web/experiments/static/experiments/js/utils.js index eb56f56fb53b2774a3e7cb41e06fb261c702e60f..6ba453fbded713f20747633874ee716a00b3315b 100644 --- a/beat/web/experiments/static/experiments/js/utils.js +++ b/beat/web/experiments/static/experiments/js/utils.js @@ -2205,134 +2205,4 @@ beat.experiments.utils.modal_rename = function( ], }); }); -}; - -/** - * Updates the toolchain viewer with status information from the blocks - * - * Parameters: - * - * viewer (beat.toolchains.viewer.ToolchainViewer): An instance of the BEAT - * toolchain viewer that will be used to update the blocks graphically - * objects (jQuery.object): A jQuery object that contains the block - * information for the current experiment - * dt_block_name (String): The name of the data object inside each block - * where the current block name is stored. - * dt_block_status (String): The name of the data object inside each block - * where the current block state is stored. - */ -beat.experiments.utils.update_viewer = function( - 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); -}; - -/** - * Updates blocks of a running experiment on the experiment "view" page - * - * Parameters: - * - * url (String): The url to probe for new versions of the blocks to replace - * st (String): A jQuery selector for the div containing the data item - * 'beat-experiment-status', that will be probed for the current experiment - * status. The blocks are only updated if the current experiment status is - * one of 'Scheduled', 'Running' or 'Canceling'. - * dt (String): The name of the data object inside the `st` selector where - * the current experiment state is stored. - * unveil (String): A jQuery selector of objects to unveil in case we're - * updating and the experiment is finished - * objects (String): A jQuery selector that will make us search for the - * replacement blocks on the `url' above. - * dt_block_name (String): The name of the data object inside each block - * where the current block name is stored. - * dt_block_status (String): The name of the data object inside each block - * where the current block state is stored. - * viewer (beat.toolchains.viewer.ToolchainViewer): An instance of the BEAT - * toolchain viewer that will be used to update the blocks graphically - * interval (Int): A timeout in milliseconds to use for updating the block - * representations and the toolchain viewer. - */ -beat.experiments.utils.update_blocks = function( - 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); -}; +} diff --git a/beat/web/experiments/templates/experiments/view.html b/beat/web/experiments/templates/experiments/view.html index f464825bedf47f94c0a73357dfeedd1ea89ecba9..b65761560e1e40daeee73c2b872ca4786560468a 100644 --- a/beat/web/experiments/templates/experiments/view.html +++ b/beat/web/experiments/templates/experiments/view.html @@ -1,6 +1,6 @@ {% extends "base.html" %} {% comment %} - * Copyright (c) 2016 Idiap Research Institute, http://www.idiap.ch/ + * Copyright (c) 2017 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. @@ -51,8 +51,10 @@ <script src="{% fingerprint "toolchains/js/dialogs.js" %}" type="text/javascript" charset="utf-8"></script> <script src="{% fingerprint "toolchains/js/utils.js" %}" type="text/javascript" charset="utf-8"></script> <script src="{% fingerprint "experiments/js/dialogs.js" %}" type="text/javascript" charset="utf-8"></script> +<script src="{% fingerprint "experiments/js/panels.js" %}" type="text/javascript" charset="utf-8"></script> <script src="{% fingerprint "experiments/js/utils.js" %}" type="text/javascript" charset="utf-8"></script> <script src="{% fingerprint "bootstrap3-typeahead/bootstrap3-typeahead.min.js" %}" type="text/javascript" charset="utf-8"></script> +<script src="{% fingerprint "mustache.js/mustache.min.js" %}" type="text/javascript" charset="utf-8"></script> {% if owner %} {% code_editor_scripts "rst" %} {% endif %} @@ -61,10 +63,126 @@ {% block content %} -<div id="experiment-status" class="may-update" data-beat-status="{{ experiment.get_status_display }}"></div> +{# Mustache templates for the blocks on the 'Execution details' page #} +{% verbatim %} +<script id="template-globals" type="x-tmpl-mustache"> + <div id="globals" class="panel panel-default globals"> + <div class="panel-heading" role="tab" id="heading-globals"> + <h4 class="panel-title"> + <a class="collapsed" role="button" data-toggle="collapse" data-parent="#globals" + href="#collapse-globals" aria-expanded="false" aria-controls="collapse-globals">Global Parameters</a> + </h4> + </div> + <div id="collapse-globals" class="panel-collapse collapse" role="tabpanel" aria-labelledby="heading-globals"> + <div class="panel-body"> + <ul> + <li>Environment: <a href="{{ environment.url }}">{{ environment.name }}/{{ environment.version }}</a></li> + <li>Queue: {{ queue }}</li> + {{ #algorithms }} + <li>Algorithm: <a href="{{ url }}">{{ name }}</a>: + <ul> + {{ #items }} + <li>{{ name }}: <code>{{ value }}</code></li> + {{ /items }} + </ul> + </li> + {{ /algorithms }} + </ul> + </div> + </div> + </div> +</script> + +<script id="template-datasets" type="x-tmpl-mustache"> + <div id="datasets" class="panel panel-default dataset"> + <div class="panel-heading" role="tab" id="heading-dataset"> + <h4 class="panel-title"> + <a class="collapsed" role="button" data-toggle="collapse" data-parent="#datasets" + href="#collapse-dataset" aria-expanded="false" aria-controls="collapse-dataset">Datasets</a> + </h4> + </div> + <div id="collapse-dataset" class="panel-collapse collapse" role="tabpanel" aria-labelledby="heading-dataset"> + <div class="panel-body"> + <ul> + {{ #datasets }} + <li>{{ block_name }} <i class="fa fa-arrow-right"></i> {{ set }} from protocol "{{ protocol }}" @ <a href="{{ url }}">{{ name }}</a></li> + {{ /datasets }} + </ul> + </div> + </div> + </div> +</script> + +<script id="template-block" type="x-tmpl-mustache"> + {{ #blocks }} + <div id="block-{{ name }}" data-beat-status="{{ status }}" + class="panel panel-default {{ #analyzer }} analyzer{{ /analyzer }}"> + + <div class="panel-heading" role="tab" id="heading-{{ name }}"> + <h4 class="panel-title"> + <a {{ ^failed }}class="collapsed"{{ /failed }} + role="button" data-toggle="collapse" data-parent="#block-{{ name }}" + href="#collapse-block-{{ name }}" aria-controls="collapse-block-{{ name }}" + aria-expanded="{{ failed }}"> + <i id="block-{{ name }}-status-icon" data-toggle="tooltip" data-placement="bottom" title="{{ status }}" class="{{ icon }}"></i> + {{ name }} ({{ algorithm }})<span id="block-{{ name }}-execution-time">{{ #cached_or_failed }} @ {{ linear_execution_time }}s{{ /cached_or_failed }}</span> + </a> + </h4> + </div> + <div id="collapse-block-{{ name }}" class="panel-collapse collapse{{ #failed }} in{{ /failed }}" role="tabpanel" aria-labelledby="heading-block-{{ name }}"> + <div class="panel-body"> + <ul> + <li>Algorithm: <a href="{{ url }}">{{ algorithm }}</a></li> + + <li id="block-{{ name }}-linear-execution-time"{{ ^cached_or_failed }} style="display: none;"{{ /cached_or_failed }}>Total execution time: {{ linear_execution_time }}s</li> + + {{ #nb_slots }} + <li id="block-{{ name }}-speed-up-real"{{ ^cached_or_failed }} style="display: none;"{{ /cached_or_failed }}>Slots: {{ nb_slots }} (speed-up achieved: {{ speed_up_real }}x)</li> + <li id="block-{{ name }}-speed-up-maximal"{{ ^cached_or_failed }} style="display: none;"{{ /cached_or_failed }}>Maximum speed-up achievable: {{ block.speed_up_maximal }}x</li> + {{ /nb_slots }} + + {{ ^nb_slots }} + <li id="block-{{ name }}-slots"{{ ^cached_or_failed }} style="display: none;"{{ /cached_or_failed }}>Slots: 1</li> + {{ /nb_slots }} + + <li id="block-{{ name }}-queuing-time"{{ ^cached_or_failed }} style="display: none;"{{ /cached_or_failed }}>Queuing time: {{ queuing_time }}s</li> + + <li>Environment: <a href="{{ environment.url }}">{{ environment.name }}/{{ environment.version }}</a></li> + <li>Queue: {{ queue }}</li> + + {{ #has_parameters }} + <li>Explicit Parameters: + <ul> + {{ #parameters }} + <li>{{ name }}: <code>{{ value }}</code></li> + {{ /parameters }} + </ul> + </li> + {{ /has_parameters }} + + <li id="block-{{ name }}-error-report"{{ ^error_report }} style="display: none;"{{ /error_report }}>Captured Errors (on user code): + <pre class="console-output">{{ error_report }}</pre> + </li> + + <li id="block-{{ name }}-stdout"{{ ^stdout }} style="display: none;"{{ /stdout }}>Standard Output: + <pre class="console-output">{{ stdout }}</pre> + </li> + + <li id="block-{{ name }}-stderr"{{ ^stderr }} style="display: none;"{{ /stderr }}>Standard Error: + <pre class="console-output">{{ stderr }}</pre> + </li> + </ul> + </div> + </div> + </div> + {{ /blocks }} +</script> +{% endverbatim %} + -<div id="experiment-header" class="row may-update" data-beat-status="{{ experiment.get_status_display }}"> - <div class="col-sm-8 vertical-center" onmouseover="expand_breadcrumb(this, 8, 4);" onmouseout="reset_breadcrumb(this, 8, 4);"> +<div id="experiment-header" class="row" data-beat-status="{{ experiment.get_status_display }}"> + <div class="col-sm-8 vertical-center" onmouseover="expand_breadcrumb(this, 8, 4);" + onmouseout="reset_breadcrumb(this, 8, 4);"> {% experiment_breadcrumb experiment %} </div><div class="col-sm-4 vertical-center"> {% experiment_actions experiment False %} @@ -80,22 +198,9 @@ </div> {% endif %} -<div id="experiment-date" class="row may-update" data-beat-status="{{ experiment.get_status_display }}-{{ experiment.start_date|naturaltime }}"> +<div id="experiment-date" class="row" style="display: none;"> <div class="col-sm-12"> - {% if experiment.end_date %} - <p class="help-block"><i class="fa fa-clock-o"></i> Finished executing <strong>{{ experiment.end_date|naturaltime }}</strong> (on {{ experiment.end_date }})</p> - {% elif experiment.start_date %} - <p class="help-block"><i class="fa fa-clock-o"></i> Started executing <strong>{{ experiment.start_date|naturaltime }}</strong> (on {{ experiment.start_date }})</p> - {% endif %} - </div> -</div> - -<div class="row unveil" style="display:none;"> - <div class="col-sm-12"> - <div class="alert alert-warning alert-dismissible" role="alert"> - <button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button> - <i class="fa fa-warning"></i> The experiment finished. <a href="{{ experiment.get_absolute_url }}">Reload this page</a> when possible to access more details. - </div> + <p class="help-block"><i class="fa fa-clock-o"></i></p> </div> </div> @@ -107,6 +212,7 @@ </div> {% endif %} + {% with experiment.get_status_display as status %} {% visible_reports experiment as reports %} {% with reports|length|add:experiment.has_attestation as nreferrers %} @@ -132,159 +238,11 @@ {% experiment_results "results" experiment %} </div> {% endifequal %} + <div role="tabpanel" class="tab-pane{% ifnotequal status 'Done' %} active{% endifnotequal %}" id="exec"> - {% with experiment.core as xpcore %} - - <div class="col-sm-6">{# block information #} - - <div class="panel-group" id="block-accordion" role="tablist" aria-multiselectable="true"> - - {# global settings #} - <div id="globals" class="panel panel-default globals"> - <div class="panel-heading" role="tab" id="heading-globals"> - <h4 class="panel-title"> - <a class="collapsed" role="button" data-toggle="collapse" data-parent="#globals" href="#collapse-globals" aria-expanded="false" aria-controls="collapse-globals">Global Parameters</a> - </h4> - </div>{# panel heading #} - <div id="collapse-globals" class="panel-collapse collapse" role="tabpanel" aria-labelledby="heading-globals"> - <div class="panel-body"> - {% with xpcore.data.globals as xpglobals %} - <ul> - <li>Environment: <a href="{% url 'backend:view-environment' xpglobals.environment.name xpglobals.environment.version %}">{{ xpglobals.environment.name }}/{{ xpglobals.environment.version }}</a></li> - <li>Queue: {{ xpglobals.queue }}</li> - {% for k, v in xpcore.data.globals.items %} - {% if k != 'environment' and k != 'queue' %} - <li>Algorithm: {% with k|split_fullname as parts %}<a href="{% url 'algorithms:view' parts.0 parts.1 parts.2 %}">{{ k }}</a>{% endwith %}: - <ul> - {% for parname, parvalue in v.items %} - <li>{{ parname }}: <code>{{ parvalue }}</code></li> - {% endfor %} - </ul> - {% endif %} - {% endfor %} - </li> - </ul> - {% endwith %} - </div>{# panel body #} - </div>{# collapse #} - </div>{# panel #} - - {# database set descriptors #} - <div id="dataset" class="panel panel-default dataset"> - <div class="panel-heading" role="tab" id="heading-dataset"> - <h4 class="panel-title"> - <a class="collapsed" role="button" data-toggle="collapse" data-parent="#dataset" href="#collapse-dataset" aria-expanded="false" aria-controls="collapse-dataset">Datasets</a> - </h4> - </div>{# panel heading #} - <div id="collapse-dataset" class="panel-collapse collapse" role="tabpanel" aria-labelledby="heading-dataset"> - <div class="panel-body"> - <ul> - {% for k,v in xpcore.datasets.items %} - {% with v.database.name|split_fullname as parts %} - <li>{{ k }} <i class="fa fa-arrow-right"></i> {{ v.set }} from protocol "{{ v.protocol }}" @ <a href="{% url 'databases:view' parts.0 parts.1 %}">{{ v.database.name }}</a></li> - {% endwith %} - {% endfor %} - </ul> - </div>{# panel body #} - </div>{# collapse #} - </div>{# panel #} - - {% ordered_blocks experiment as blocks %} - {% for block in blocks %} - {% with xpcore.blocks|getkey:block.name as core_block %} - {% with block.get_status_display as block_status %} - {% with block.error_report as error_report %} - <div id="{{ block.name }}" data-beat-block-name="{{ block.name }}" data-beat-status="{{ block.get_status_display|lower }}" class="panel panel-default may-update{% if block.analyzer %} analyzer{% endif %}"> - - <div class="panel-heading" role="tab" id="heading-{{ block.name }}"> - <h4 class="panel-title"> - <a{% if error_report and block.done and block_status != 'Cancelled' %}{% else %} class="collapsed"{% endif %} role="button" data-toggle="collapse" data-parent="#{{ block.name }}" href="#collapse-{{ block.name }}" aria-expanded="{% if error_report and block.done and block_status != 'Cancelled' %}false{% else %}true{% endif %}" aria-controls="collapse-{{ block.name }}"> - <i data-toggle="tooltip" data-placement="bottom" title="{{ block_status }}" - {% if block_status == 'Not cached' %} - class="icon-scheduled fa fa-play" - {% elif block_status == 'Cached' %} - class="icon-done fa fa-check" - {% elif block_status == 'Failed' %} - class="icon-failed fa fa-bug" - {% elif block_status == 'Processing' %} - class="icon-running fa fa-spin fa-refresh" - {% elif block_status == 'Cancelled' %} - class="icon-failed fa fa-power-off" - {% else %} - class="icon-pending fa fa-asterisk" - {% endif %} - ></i> - {{ block.name }} ({{ block.algorithm.fullname }}){% if block_status in 'Cached Failed' %} @ {{ block.linear_execution_time|floatformat:-2 }}s{% endif %} - </a> - </h4> - </div>{# panel heading #} - <div id="collapse-{{ block.name }}" class="panel-collapse collapse{% if error_report and block.done and block_status != 'Cancelled' %} in{% endif %}" role="tabpanel" aria-labelledby="heading-{{ block.name }}"> - <div class="panel-body"> - <ul> - <li>Algorithm: <a href="{{ block.algorithm.get_absolute_url }}">{{ block.algorithm.fullname }}</a></li> - {% if block_status == 'Cached' or block_status == 'Failed' %} - <li>Total execution time: {{ block.linear_execution_time|floatformat:-2 }}s</li> - {% comment %} - <!-- not very reliable - <ul> - <li>Algorithm CPU time: {% if block.cpu_time > 0.01 %}{{ block.cpu_time|floatformat:-2 }}s{% else %}<i class="help">unavailable</i>{% endif %}</li> - <li>Maximum RSS memory: {% if block.max_memory > 1 %}{{ block.max_memory }} bytes{% else %}<i class="help">unavailable</i>{% endif %}</li> - <li>Disk reading time: {% if block.data_read_time > 0.01 %}{{ block.data_read_time|floatformat:-2 }}s{% else %}<i class="help">negligible</i>{% endif %}</li> - <li>Data read: {% if block.data_read_size > 0.01 %}{{ block.data_read_time|floatformat:-2 }} bytes{% else %}<i class="help">negligible</i>{% endif %}</li> - <li>Disk writing time: {% if block.data_written_time > 0.01 %}{{ block.data_written_time|floatformat:-2 }}s{% else %}<i class="help">negligible</i>{% endif %}</li> - <li>Data written: {% if block.data_written_size > 0.01 %}{{ block.data_written_time|floatformat:-2 }} bytes{% else %}<i class="help">negligible</i>{% endif %}</li> - </ul> - --> - {% endcomment %} - {% if core_block.nb_slots and core_block.nb_slots > 1 %} - <li>Slots: {{ core_block.nb_slots }} (speed-up achieved: {{ block.speed_up_real|floatformat:-1 }}x)</li> - <li>Maximum speed-up achievable: {{ block.speed_up_maximal|floatformat:-1 }}x</li> - {% else %} - <li>Slots: 1</li> - {% endif %} - <li>Queuing time: {{ block.queuing_time|floatformat:-2 }}s</li> - {% endif %} - <li>Queue: {% if core_block.queue %}{{ core_block.queue }}{% else %}{{ xpcore.data.globals.queue }}{% endif %}</li> - <li>Environment: <a href="{{ block.environment.get_absolute_url }}">{{ block.environment.fullname }}</a></li> - {% if core_block.parameters %} - <li>Explicit Parameters: - <ul> - {% for parname, parvalue in core_block.parameters.items %} - <li>{{ parname }}: <code>{{ parvalue }}</code></li> - {% endfor %} - </ul> - </li> - {% endif %} - {% if block.done and block_status != 'Cancelled' %} - {% if error_report and block_status != 'Cached' %} - <li>Captured Errors (on user code): - <pre class="console-output">{{ error_report }}</pre> - </li> - {% endif %} - {% if block.stdout %} - <li>Standard Output: - <pre class="console-output">{{ block.stdout }}</pre> - </li> - {% endif %} - {% if block.stderr %} - <li>Standard Error: - <pre class="console-output">{{ block.stderr }}</pre> - </li> - {% endif %} - {% endif %}{# block.done #} - </ul> - </div>{# panel body #} - </div>{# collapse #} - </div>{# panel #} - {% endwith %} - {% endwith %} - {% endwith %} - {% endfor %} - - </div>{# panel group #} - - {% endwith %} - </div>{# column #} + <div class="col-sm-6"> {# execution details display #} + <div class="panel-group" id="execution-details" role="tablist" aria-multiselectable="true"></div> + </div> <div class="col-sm-6"> {# toolchain display #} <div id="toolchain-viewer" class="toolchain-viewer"> @@ -296,14 +254,17 @@ </div> </div> </div> + <div role="tabpanel" class="tab-pane" id="doc"> {% doc_editor experiment 'api_experiments:object' %} </div> + {% if owner %} <div role="tabpanel" class="tab-pane" id="sharing"> {% experiment_sharing experiment %} </div> {% endif %} + <div role="tabpanel" class="tab-pane" id="referrers"> <p class="action-buttons help">Experiments which are referred by reports or by an associated attestation cannot be modified. You may revert the situation by deleting unpublished reports or the attestation in case they have not been published. Note you may still <i class="btn-fork fa fa-code-fork"></i> fork this experiment and re-run a modified or unmodified version of it.</p> {% if experiment.has_attestation %} @@ -343,58 +304,164 @@ <script type="text/javascript"> $(document).ready(function(){ - manage_tabs('ul#object-tabs'); + manage_tabs('ul#object-tabs'); + + + // Initialise the execution details panel + var execution_details_panel = new beat.experiments.panels.ExecutionDetails( + 'execution-details', '{{ URL_PREFIX }}' + ); + - {% comment %} - The remainder of this code handles the updates on the experiment and block - status + // Initialise the toolchain viewer + var toolchain_viewer = new beat.toolchains.viewer.ToolchainViewer('toolchain-viewer'); - Nota Bene: code repeated from toolchains/panels/viewer.html for the purpose - of controlling the filling as the experiment executes - {% endcomment %} + toolchain_viewer.element.style.cursor = 'pointer'; + $(toolchain_viewer.element).click(function(){ + window.location = '{{ experiment.get_absolute_url }}'; + }); - var viewer = new beat.toolchains.viewer.ToolchainViewer('toolchain-viewer'); - {# load toolchain data, set on viewer #} - beat.toolchains.utils.load('{{ URL_PREFIX }}', '{{ experiment.toolchain.fullname }}', - function(toolchain, data) { - if (!toolchain) { - alert(data); - return; + // Update functions + var toolchain = null; + var experiment_configuration = null; + var experiment_results = null; + + var fields = 'status,blocks_status,done,errors,execution_info,execution_order,' + + 'results,started,display_start_date,display_end_date'; + + function update_date(started, start_date, end_date) { + if (!started) + return; + + $('#experiment-date').show(); + + if (end_date !== null) { + $('#experiment-date .help-block').html('<i class="fa fa-clock-o"></i> Finished executing <strong>' + + end_date.natural + '</strong> (on ' + end_date.date + ')'); + } + else { + $('#experiment-date .help-block').html('<i class="fa fa-clock-o"></i> Started executing <strong>' + + start_date.natural + '</strong> (on ' + start_date.date + ')'); + } + } + + function update() { + $.get('{% url 'api_experiments:all' %}{{ experiment.fullname }}/?fields=' + fields, + function(experiment_results) { + execution_details_panel.update(experiment_results); + + if (!toolchain_viewer.running && experiment_results.started && !experiment_results.done) + toolchain_viewer.onExperimentStarted(); + + toolchain_viewer.updateBlocksStatus(experiment_results.blocks_status); + + // (If necessary) Update the icon in the breadcrump + var header = $('#experiment-header'); + if (header.data('beat-status') != experiment_results.status) { + header.data('beat-status', experiment_results.status); + + var icon = header.find('.breadcrumb .icon-link i'); + if (experiment_results.status == 'Done') + icon[0].className = "icon-done fa fa-check fa-lg"; + else if (experiment_results.status == 'Scheduled') + icon[0].className = "icon-scheduled fa fa-calendar-check-o fa-lg"; + else if (experiment_results.status == 'Pending') + icon[0].className = "icon-pending fa fa-play fa-lg"; + else if (experiment_results.status == 'Failed') + icon[0].className = "icon-failed fa fa-bug fa-lg"; + else if (experiment_results.status == 'Canceling') + icon[0].className = "icon-canceling fa fa-power-off fa-lg"; + else + icon[0].className = "icon-running fa fa-refresh fa-spin fa-lg"; + } + + // Update the displayed date + update_date(experiment_results.started, experiment_results.display_start_date, + experiment_results.display_end_date) + + // (If necessary) Schedule an update for later + if (!experiment_results.done) + setTimeout(update, 5000); + else + location.reload(); + } + ); + } + + + // Load the toolchain and the experiment declaration + function setup() { + if ((toolchain === null) || (experiment_configuration === null) || + (experiment_results === null)) + return; + + var configuration = new beat.toolchains.models.Configuration(experiment_configuration); + + // Update the content of the execution details panel + templates = { + globals: '#template-globals', + datasets: '#template-datasets', + block: '#template-block', } - viewer.zoom = 0.5; - viewer.setToolchain(toolchain); + urls = { + environment: "{% url 'backend:view-environment' 'NAME' 'VERSION' %}", + algorithm: "{% url 'algorithms:view' 'AUTHOR' 'NAME' 0 %}", + database: "{% url 'databases:view' 'NAME' 0 %}", + } + + execution_details_panel.setup(configuration, templates, urls, experiment_results); + + // Update the content of the toolchain viewer + toolchain_viewer.zoom = 0.5; + toolchain_viewer.setToolchain(toolchain); + toolchain_viewer.setConfiguration(configuration); + + if (experiment_results.started && !experiment_results.done) + toolchain_viewer.onExperimentStarted(); + + toolchain_viewer.updateBlocksStatus(experiment_results.blocks_status); var panel = $('#toolchain-viewer'); if (!panel.is(':visible')) { - //assume we're the child of tabpane and try to get width from - //our grand-parent. - viewer.setSize(panel.parent().parent().width(), panel.height()); + // Assume we're the child of tabpane and try to get width from + // our grand-parent. + toolchain_viewer.setSize(panel.parent().parent().width(), panel.height()); } else { - viewer.setSize(panel.width(), panel.height()); + toolchain_viewer.setSize(panel.width(), panel.height()); } - $.get('{% url 'api_experiments:all' %}{{ experiment.fullname }}/?fields=declaration', function(data) { viewer.setConfiguration(new beat.toolchains.models.Configuration(data.declaration)); }); - }); - - viewer.element.style.cursor = 'pointer'; - $(viewer.element).click(function(){ - window.location = '{{ experiment.get_absolute_url }}'; - }); - - {# the function to update the blocks, probes every 5 seconds #} - beat.experiments.utils.update_blocks( - '{{ experiment.get_absolute_url }}', - '#experiment-status', //div to probe for the current status - 'beat-status', //data field containing status information on relevant bits - '.unveil', //objects that should be unveiled once done - '.may-update', //objects that should be updated - 'beat-block-name', //data field containing the block name - viewer, - 5000 //update interval in milliseconds - ); + // Update the displayed date + update_date(experiment_results.started, experiment_results.display_start_date, + experiment_results.display_end_date) + + // (If necessary) Schedule an update for later + if (!experiment_results.done) + setTimeout(update, 5000); + } + + beat.toolchains.utils.load('{{ URL_PREFIX }}', '{{ experiment.toolchain.fullname }}', + function(loaded_toolchain, data) { + if (!loaded_toolchain) { + alert(data); + return; + } + + toolchain = loaded_toolchain; + + setup(); + } + ); + + $.get('{% url 'api_experiments:all' %}{{ experiment.fullname }}/?fields=declaration,' + fields, + function(data) { + experiment_configuration = data.declaration; + experiment_results = data; + setup(); + } + ); }) </script> {% endwith %} diff --git a/beat/web/toolchains/static/toolchains/js/common.js b/beat/web/toolchains/static/toolchains/js/common.js index 15817d48e172b4c68506cbf952cf2c82dc5e0d72..dd5c4576c316171c8656a8b31e3d75bec9320a88 100644 --- a/beat/web/toolchains/static/toolchains/js/common.js +++ b/beat/web/toolchains/static/toolchains/js/common.js @@ -56,6 +56,12 @@ beat.toolchains.common.SELECTION_INPUT = 1; beat.toolchains.common.SELECTION_OUTPUT = 2; beat.toolchains.common.SELECTION_FULL = 3; +// Scrollbars +beat.toolchains.common.SCROLLBAR_HOLDER_WIDTH = 15; +beat.toolchains.common.SCROLLBAR_WIDTH = 8; +beat.toolchains.common.SCROLLBAR_OFFSET = 4; +beat.toolchains.common.SCROLLBAR_MARGIN = 200; + /*********************************** UTILITY FUNCTIONS **********************************/ diff --git a/beat/web/toolchains/static/toolchains/js/editor.js b/beat/web/toolchains/static/toolchains/js/editor.js index 88056781b4b5f86c0a8ba3be39e23a08fca8aeb6..446ec018d849a76595a4aab057e369845387d2b5 100644 --- a/beat/web/toolchains/static/toolchains/js/editor.js +++ b/beat/web/toolchains/static/toolchains/js/editor.js @@ -75,12 +75,6 @@ beat.toolchains.editor.HOVERED_CONNECTION = 10; beat.toolchains.editor.HOVERED_CONNECTION_WITH_CHANNELS = 11; beat.toolchains.editor.HOVERED_INPUT_HANDLE_CONNECTION = 12; -// Scrollbars -beat.toolchains.editor.SCROLLBAR_HOLDER_WIDTH = 15; -beat.toolchains.editor.SCROLLBAR_WIDTH = 8; -beat.toolchains.editor.SCROLLBAR_OFFSET = 4; -beat.toolchains.editor.SCROLLBAR_MARGIN = 200; - /******************************** CLASS: ToolchainEditor ********************************/ @@ -2097,16 +2091,16 @@ beat.toolchains.editor.ToolchainView.prototype._onMouseDown = function(event) var infos = this._getComponentAt(this.mouse_position.x, this.mouse_position.y); // Horizontal scrollbar - if (this.mouse_position.y >= this.canvas.height - beat.toolchains.editor.SCROLLBAR_HOLDER_WIDTH) + if (this.mouse_position.y >= this.canvas.height - beat.toolchains.common.SCROLLBAR_HOLDER_WIDTH) { var view = this; function _incremental_horizontal_scroll() { - if (view.mouse_position.y < view.canvas.height - beat.toolchains.editor.SCROLLBAR_HOLDER_WIDTH) + if (view.mouse_position.y < view.canvas.height - beat.toolchains.common.SCROLLBAR_HOLDER_WIDTH) return; - if (view.mouse_position.x >= view.canvas.width - beat.toolchains.editor.SCROLLBAR_HOLDER_WIDTH) + if (view.mouse_position.x >= view.canvas.width - beat.toolchains.common.SCROLLBAR_HOLDER_WIDTH) return; if (view.mouse_position.x < view.horizontal_scrollbar.position) @@ -2118,7 +2112,7 @@ beat.toolchains.editor.ToolchainView.prototype._onMouseDown = function(event) } // Corner - if (this.mouse_position.x >= this.canvas.width - beat.toolchains.editor.SCROLLBAR_HOLDER_WIDTH) + if (this.mouse_position.x >= this.canvas.width - beat.toolchains.common.SCROLLBAR_HOLDER_WIDTH) { // Nothing to do } @@ -2161,16 +2155,16 @@ beat.toolchains.editor.ToolchainView.prototype._onMouseDown = function(event) } // Vertical scrollbar - else if (this.mouse_position.x >= this.canvas.width - beat.toolchains.editor.SCROLLBAR_HOLDER_WIDTH) + else if (this.mouse_position.x >= this.canvas.width - beat.toolchains.common.SCROLLBAR_HOLDER_WIDTH) { var view = this; function _incremental_vertical_scroll() { - if (view.mouse_position.x < view.canvas.width - beat.toolchains.editor.SCROLLBAR_HOLDER_WIDTH) + if (view.mouse_position.x < view.canvas.width - beat.toolchains.common.SCROLLBAR_HOLDER_WIDTH) return; - if (view.mouse_position.y >= view.canvas.height - beat.toolchains.editor.SCROLLBAR_HOLDER_WIDTH) + if (view.mouse_position.y >= view.canvas.height - beat.toolchains.common.SCROLLBAR_HOLDER_WIDTH) return; if (view.mouse_position.y < view.vertical_scrollbar.position) @@ -4326,7 +4320,7 @@ beat.toolchains.editor.ToolchainView.prototype._onMouseWheel = function(event) var mouse_position = this._getMousePosition(event); // Mouse over the horizontal scrollbar: translate horizontally - if (mouse_position.y >= this.canvas.height - beat.toolchains.editor.SCROLLBAR_HOLDER_WIDTH) + if (mouse_position.y >= this.canvas.height - beat.toolchains.common.SCROLLBAR_HOLDER_WIDTH) { // Scroll up if (event.originalEvent.wheelDelta > 0 || event.originalEvent.detail < 0) @@ -5448,8 +5442,8 @@ beat.toolchains.editor.ToolchainView.prototype._drawScrollbars = function() // Compute all the needed positions / sizes - var horizontal_top = this.canvas.height - beat.toolchains.editor.SCROLLBAR_HOLDER_WIDTH; - var vertical_left = this.canvas.width - beat.toolchains.editor.SCROLLBAR_HOLDER_WIDTH; + var horizontal_top = this.canvas.height - beat.toolchains.common.SCROLLBAR_HOLDER_WIDTH; + var vertical_left = this.canvas.width - beat.toolchains.common.SCROLLBAR_HOLDER_WIDTH; if (this.bounding_box.left !== null) { @@ -5505,27 +5499,27 @@ beat.toolchains.editor.ToolchainView.prototype._drawScrollbars = function() var extended_width = Math.max(bounding_box.right, this.visible_area.left + this.visible_area.width) - Math.min(bounding_box.left, this.visible_area.left); var extended_height = Math.max(bounding_box.bottom, this.visible_area.top + this.visible_area.height) - Math.min(bounding_box.top, this.visible_area.top); - var covered_width = extended_width / this.zoom + beat.toolchains.editor.SCROLLBAR_MARGIN * 2; - var covered_height = extended_height / this.zoom + beat.toolchains.editor.SCROLLBAR_MARGIN * 2; + var covered_width = extended_width / this.zoom + beat.toolchains.common.SCROLLBAR_MARGIN * 2; + var covered_height = extended_height / this.zoom + beat.toolchains.common.SCROLLBAR_MARGIN * 2; var horizontal_x = 0.5 + (visible_area_center_x - bounding_box_center_x) / extended_width * 0.5; var vertical_y = 0.5 + (visible_area_center_y - bounding_box_center_y) / extended_height * 0.5; - this.horizontal_scrollbar.size = ((this.visible_area.width - beat.toolchains.editor.SCROLLBAR_HOLDER_WIDTH) / covered_width) * this.canvas.width / this.zoom; + this.horizontal_scrollbar.size = ((this.visible_area.width - beat.toolchains.common.SCROLLBAR_HOLDER_WIDTH) / covered_width) * this.canvas.width / this.zoom; this.horizontal_scrollbar.position = (this.canvas.width * horizontal_x) - this.horizontal_scrollbar.size / 2; - this.vertical_scrollbar.size = ((this.visible_area.height - beat.toolchains.editor.SCROLLBAR_HOLDER_WIDTH) / covered_height) * this.canvas.height / this.zoom; + this.vertical_scrollbar.size = ((this.visible_area.height - beat.toolchains.common.SCROLLBAR_HOLDER_WIDTH) / covered_height) * this.canvas.height / this.zoom; this.vertical_scrollbar.position = (this.canvas.height * vertical_y) - this.vertical_scrollbar.size / 2; // Draw the holders this.context.fillStyle = '#F9F9F9'; - this.context.fillRect(0.5, horizontal_top + 0.5, this.canvas.width, beat.toolchains.editor.SCROLLBAR_HOLDER_WIDTH); - this.context.fillRect(vertical_left + 0.5, 0.5, beat.toolchains.editor.SCROLLBAR_HOLDER_WIDTH, this.canvas.height); + this.context.fillRect(0.5, horizontal_top + 0.5, this.canvas.width, beat.toolchains.common.SCROLLBAR_HOLDER_WIDTH); + this.context.fillRect(vertical_left + 0.5, 0.5, beat.toolchains.common.SCROLLBAR_HOLDER_WIDTH, this.canvas.height); this.context.strokeStyle = '#E8E8E8'; - this.context.strokeRect(0.5, horizontal_top + 0.5, this.canvas.width - beat.toolchains.editor.SCROLLBAR_HOLDER_WIDTH, 1); - this.context.strokeRect(vertical_left + 0.5, 0.5, 1, this.canvas.height - beat.toolchains.editor.SCROLLBAR_HOLDER_WIDTH); + this.context.strokeRect(0.5, horizontal_top + 0.5, this.canvas.width - beat.toolchains.common.SCROLLBAR_HOLDER_WIDTH, 1); + this.context.strokeRect(vertical_left + 0.5, 0.5, 1, this.canvas.height - beat.toolchains.common.SCROLLBAR_HOLDER_WIDTH); // Draw the horizontal scrollbar @@ -5534,16 +5528,16 @@ beat.toolchains.editor.ToolchainView.prototype._drawScrollbars = function() else this.context.fillStyle = '#C2C2C2'; - _roundRect(this.context, this.horizontal_scrollbar.position + 0.5, horizontal_top + beat.toolchains.editor.SCROLLBAR_OFFSET + 0.5, - this.horizontal_scrollbar.size, beat.toolchains.editor.SCROLLBAR_WIDTH, beat.toolchains.editor.SCROLLBAR_WIDTH / 2, true, false); + _roundRect(this.context, this.horizontal_scrollbar.position + 0.5, horizontal_top + beat.toolchains.common.SCROLLBAR_OFFSET + 0.5, + this.horizontal_scrollbar.size, beat.toolchains.common.SCROLLBAR_WIDTH, beat.toolchains.common.SCROLLBAR_WIDTH / 2, true, false); if ((this.current_action == beat.toolchains.editor.ACTION_SCROLLBAR) && !this.action_data.horizontal) this.context.fillStyle = '#A2A2A2'; else this.context.fillStyle = '#C2C2C2'; - _roundRect(this.context, vertical_left + beat.toolchains.editor.SCROLLBAR_OFFSET + 0.5, this.vertical_scrollbar.position + 0.5, - beat.toolchains.editor.SCROLLBAR_WIDTH, this.vertical_scrollbar.size, beat.toolchains.editor.SCROLLBAR_WIDTH / 2, true, false); + _roundRect(this.context, vertical_left + beat.toolchains.common.SCROLLBAR_OFFSET + 0.5, this.vertical_scrollbar.position + 0.5, + beat.toolchains.common.SCROLLBAR_WIDTH, this.vertical_scrollbar.size, beat.toolchains.common.SCROLLBAR_WIDTH / 2, true, false); this.context.restore(); diff --git a/beat/web/toolchains/static/toolchains/js/models.js b/beat/web/toolchains/static/toolchains/js/models.js index aa03273d1dab8164869b35d3af16709a92a9923f..e4ba350797fd735430e05b9920856194c2a504d2 100644 --- a/beat/web/toolchains/static/toolchains/js/models.js +++ b/beat/web/toolchains/static/toolchains/js/models.js @@ -1491,6 +1491,15 @@ beat.toolchains.models.Configuration.prototype.setAlgorithmGlobal = function(alg } +//---------------------------------------------------------------------------------------- +// Retrieves the name of all the datasets +//---------------------------------------------------------------------------------------- +beat.toolchains.models.Configuration.prototype.datasetNames = function() +{ + return Object.keys(this.declaration.datasets); +} + + //---------------------------------------------------------------------------------------- // Retrieves the name of all the blocks //---------------------------------------------------------------------------------------- @@ -2159,6 +2168,15 @@ beat.toolchains.models.Configuration.prototype.removeDefaultQueueListener = func } +//---------------------------------------------------------------------------------------- +// Indicates if a component is an analyzer +//---------------------------------------------------------------------------------------- +beat.toolchains.models.Configuration.prototype.is_analyzer = function(name) +{ + return this.declaration.analyzers[name] !== undefined; +} + + //----------------------------------------------- diff --git a/beat/web/toolchains/static/toolchains/js/viewer.js b/beat/web/toolchains/static/toolchains/js/viewer.js index ac886c0f97b9744d58e34be489ca5e1fab81bb82..9db2516fa9a7ac6199ca8167fda8825f481c4916 100644 --- a/beat/web/toolchains/static/toolchains/js/viewer.js +++ b/beat/web/toolchains/static/toolchains/js/viewer.js @@ -687,7 +687,7 @@ beat.toolchains.viewer.ToolchainViewer.prototype._onMouseWheel = function(event) var mouse_position = this._getMousePosition(event); // Mouse over the horizontal scrollbar: translate horizontally - if (mouse_position.y >= this.canvas.height - beat.toolchains.editor.SCROLLBAR_HOLDER_WIDTH) + if (mouse_position.y >= this.canvas.height - beat.toolchains.common.SCROLLBAR_HOLDER_WIDTH) { // Scroll up if (event.originalEvent.wheelDelta > 0 || event.originalEvent.detail < 0) diff --git a/buildout.cfg b/buildout.cfg index a186c864111e8d4abf302756625a22394a2d8bd2..682232fd6e433eae220b970af6a003c9be66b43c 100644 --- a/buildout.cfg +++ b/buildout.cfg @@ -37,6 +37,7 @@ eggs = alabaster djangorestframework django-rest-swagger django-jsonfield + django-jsonfield-compat docopt docutils funcsigs @@ -134,6 +135,7 @@ packages = jquery#~1.11.3 bootstrap3-typeahead#~4.0.0 bootstrap-datepicker#~1.5.1 mousetrap#~1.5.3 + mustache#2.3.0 raphael#~2.1.4 spectrum#~1.7.1 https://github.com/joshaven/string_score.git#~0.1.22