From 83b23dbdf4ead956017e611d5d6dfcbf4ca80c06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20Fernando=20S=C3=A1nchez?= Date: Mon, 18 Jun 2018 16:18:58 +0200 Subject: [PATCH] UI improvements * Add option to add multiple plugins * Improve UI hints for collapsed parameters * Refactored plugins without requirements * Hide evaluation tab for the moment. You can see it by adding "?evaluation" to the URL. --- example-plugins/configurable_plugin.py | 1 - senpy/blueprints.py | 7 +- senpy/plugins/__init__.py | 2 +- senpy/plugins/misc/split.senpy | 19 -- .../misc/{split.py => split_plugin.py} | 16 +- .../sentiment/sentiment140/sentiment140.senpy | 22 -- ...sentiment140.py => sentiment140_plugin.py} | 21 +- senpy/static/css/main.css | 33 +++ senpy/static/js/main.js | 204 ++++++++++++------ senpy/templates/index.html | 49 +++-- tests/test_blueprints.py | 2 +- 11 files changed, 253 insertions(+), 123 deletions(-) delete mode 100644 senpy/plugins/misc/split.senpy rename senpy/plugins/misc/{split.py => split_plugin.py} (83%) delete mode 100644 senpy/plugins/sentiment/sentiment140/sentiment140.senpy rename senpy/plugins/sentiment/sentiment140/{sentiment140.py => sentiment140_plugin.py} (80%) diff --git a/example-plugins/configurable_plugin.py b/example-plugins/configurable_plugin.py index 73af6ee..f857000 100644 --- a/example-plugins/configurable_plugin.py +++ b/example-plugins/configurable_plugin.py @@ -43,7 +43,6 @@ class Dictionary(plugins.SentimentPlugin): class EmojiOnly(Dictionary): '''Sentiment annotation with a basic lexicon of emojis''' - description = 'A plugin' dictionaries = [basic.emojis] test_cases = [{ diff --git a/senpy/blueprints.py b/senpy/blueprints.py index d5dec56..26fd330 100644 --- a/senpy/blueprints.py +++ b/senpy/blueprints.py @@ -47,7 +47,12 @@ def get_params(req): @demo_blueprint.route('/') def index(): - return render_template("index.html", version=__version__) + ev = str(get_params(request).get('evaluation', False)) + evaluation_enabled = ev.lower() not in ['false', 'no', 'none'] + + return render_template("index.html", + evaluation=evaluation_enabled, + version=__version__) @api_blueprint.route('/contexts/.jsonld') diff --git a/senpy/plugins/__init__.py b/senpy/plugins/__init__.py index 1510fd4..314f2fb 100644 --- a/senpy/plugins/__init__.py +++ b/senpy/plugins/__init__.py @@ -509,7 +509,7 @@ def install_deps(*plugins): exitcode = process.wait() installed = True if exitcode != 0: - raise models.Error("Dependencies not properly installed") + raise models.Error("Dependencies not properly installed: {}".format(pip_args)) nltk_resources |= set(info.get('nltk_resources', [])) installed |= nltk.download(list(nltk_resources)) diff --git a/senpy/plugins/misc/split.senpy b/senpy/plugins/misc/split.senpy deleted file mode 100644 index 399d683..0000000 --- a/senpy/plugins/misc/split.senpy +++ /dev/null @@ -1,19 +0,0 @@ ---- -name: split -module: senpy.plugins.misc.split -description: A sample plugin that chunks input text -author: "@militarpancho" -version: '0.2' -url: "https://github.com/gsi-upm/senpy" -requirements: - - nltk -extra_params: - delimiter: - aliases: - - type - - t - required: false - default: sentence - options: - - sentence - - paragraph diff --git a/senpy/plugins/misc/split.py b/senpy/plugins/misc/split_plugin.py similarity index 83% rename from senpy/plugins/misc/split.py rename to senpy/plugins/misc/split_plugin.py index c7cea73..4c11f3a 100644 --- a/senpy/plugins/misc/split.py +++ b/senpy/plugins/misc/split_plugin.py @@ -5,13 +5,27 @@ from nltk.tokenize.simple import LineTokenizer import nltk -class SplitPlugin(AnalysisPlugin): +class Split(AnalysisPlugin): '''description: A sample plugin that chunks input text''' + author = ["@militarpancho", '@balkian'] + version = '0.2' + url = "https://github.com/gsi-upm/senpy" + + extra_params = { + 'delimiter': { + 'aliases': ['type', 't'], + 'required': False, + 'default': 'sentence', + 'options': ['sentence', 'paragraph'] + }, + } + def activate(self): nltk.download('punkt') def analyse_entry(self, entry, params): + yield entry chunker_type = params["delimiter"] original_text = entry['nif:isString'] if chunker_type == "sentence": diff --git a/senpy/plugins/sentiment/sentiment140/sentiment140.senpy b/senpy/plugins/sentiment/sentiment140/sentiment140.senpy deleted file mode 100644 index 2b38283..0000000 --- a/senpy/plugins/sentiment/sentiment140/sentiment140.senpy +++ /dev/null @@ -1,22 +0,0 @@ ---- -name: sentiment140 -module: sentiment140 -description: "Connects to the sentiment140 free API: http://sentiment140.com" -author: "@balkian" -version: '0.2' -url: "https://github.com/gsi-upm/senpy-plugins-community" -extra_params: - language: - "@id": lang_sentiment140 - aliases: - - language - - l - required: false - options: - - es - - en - - auto - default: auto -requirements: {} -maxPolarityValue: 1 -minPolarityValue: 0 \ No newline at end of file diff --git a/senpy/plugins/sentiment/sentiment140/sentiment140.py b/senpy/plugins/sentiment/sentiment140/sentiment140_plugin.py similarity index 80% rename from senpy/plugins/sentiment/sentiment140/sentiment140.py rename to senpy/plugins/sentiment/sentiment140/sentiment140_plugin.py index e97e73c..5b3ae71 100644 --- a/senpy/plugins/sentiment/sentiment140/sentiment140.py +++ b/senpy/plugins/sentiment/sentiment140/sentiment140_plugin.py @@ -5,8 +5,25 @@ from senpy.plugins import SentimentPlugin from senpy.models import Sentiment -class Sentiment140Plugin(SentimentPlugin): +class Sentiment140(SentimentPlugin): '''Connects to the sentiment140 free API: http://sentiment140.com''' + + author = "@balkian" + version = '0.2' + url = "https://github.com/gsi-upm/senpy-plugins-community" + extra_params = { + 'language': { + "@id": 'lang_sentiment140', + 'aliases': ['language', 'l'], + 'required': False, + 'default': 'auto', + 'options': ['es', 'en', 'auto'] + } + } + + maxPolarityValue = 1 + minPolarityValue = 0 + def analyse_entry(self, entry, params): lang = params["language"] res = requests.post("http://www.sentiment140.com/api/bulkClassifyJson", @@ -44,7 +61,7 @@ class Sentiment140Plugin(SentimentPlugin): from senpy.testing import patch_requests expected = {"data": [{"polarity": 4}]} with patch_requests(expected) as (request, response): - super(Sentiment140Plugin, self).test(*args, **kwargs) + super(Sentiment140, self).test(*args, **kwargs) assert request.called assert response.json.called diff --git a/senpy/static/css/main.css b/senpy/static/css/main.css index 7ea452d..ea6cbf2 100644 --- a/senpy/static/css/main.css +++ b/senpy/static/css/main.css @@ -167,3 +167,36 @@ textarea{ color: inherit; text-decoration: inherit; } + +.collapsed .collapseicon { + display: none !important; +} + +.collapsed .expandicon { + display: inline-block !important; +} + +.expandicon { + display: none !important; +} + +.collapseicon { + display: inline-block !important; +} + +.loader { + border: 6px solid #f3f3f3; /* Light grey */ + border-top: 6px solid blue; + border-bottom: 6px solid blue; + + border-radius: 50%; + width: 3em; + height: 3em; + animation: spin 2s linear infinite; + +} + +@keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} diff --git a/senpy/static/js/main.js b/senpy/static/js/main.js index 2499149..f7cab31 100644 --- a/senpy/static/js/main.js +++ b/senpy/static/js/main.js @@ -4,6 +4,7 @@ var plugins_params = default_params = {}; var plugins = []; var defaultPlugin = {}; var gplugins = {}; +var pipeline = []; function replaceURLWithHTMLLinks(text) { console.log('Text: ' + text); @@ -30,7 +31,10 @@ function hashchanged(){ function get_plugins(response){ - plugins = response.plugins; + for(ix in response.plugins){ + plug = response.plugins[ix]; + plugins[plug.name] = plug; + } } function get_datasets(response){ @@ -83,10 +87,32 @@ function draw_plugins_selection(){ html += "" // Two elements with plugin class // One from the evaluate tab and another one from the analyse tab - document.getElementsByClassName('plugin')[0].innerHTML = html; - document.getElementsByClassName('plugin')[1].innerHTML = html; + plugin_lists = document.getElementsByClassName('plugin') + for (element in plugin_lists){ + plugin_lists[element].innerHTML = html; + } + draw_plugin_pipeline(); +} + +function draw_plugin_pipeline(){ + var pipeHTML = ""; + console.log("Drawing pipeline: ", pipeline); + for (ix in pipeline){ + plug = pipeline[ix]; + pipeHTML += ' ' + plug + ' '; + } + console.log(pipeHTML); + $("#pipeline").html(pipeHTML); } + +function remove_plugin_pipeline(name){ + console.log("Removing plugin: ", name); + var index = pipeline.indexOf(name); + pipeline.splice(index, 1); + draw_plugin_pipeline(); + +} function draw_plugins_list(){ var availablePlugins = document.getElementById('availablePlugins'); @@ -105,6 +131,13 @@ function draw_plugins_list(){ } } +function add_plugin_pipeline(){ + var selected = get_selected_plugin(); + pipeline.push(selected); + console.log("Adding ", selected); + draw_plugin_pipeline(); +} + function draw_datasets(){ html = ""; repeated_html = ""+url+"" - document.getElementById("results-div").style.display = 'block'; - try { - response = JSON.parse(response); - var options = { - mode: 'view' - }; - var editor = new JSONEditor(container, options, response); - editor.expandAll(); - // $('#results-div a[href="#viewer"]').tab('show'); - $('#results-div a[href="#viewer"]').click(); - // location.hash = 'raw'; - } - catch(err){ - console.log("Error decoding JSON (got turtle?)"); - $('#results-div a[href="#raw"]').click(); - // location.hash = 'raw'; - } + $.ajax({type: "GET", url: url}).always(function(response){ + document.getElementById("results-div").style.display = 'block'; + if(typeof response=="object") { + var options = { + mode: 'view' + }; + var editor = new JSONEditor(container, options, response); + editor.expandAll(); + $('#results-div a[href="#viewer"]').click(); + response = JSON.stringify(response, null, 4); + } else { + console.log("Got turtle?"); + $('#results-div a[href="#raw"]').click(); + } + + rawcontainer.innerHTML = replaceURLWithHTMLLinks(response); + document.getElementById("input_request").innerHTML = ""+url+"" + + $(".loading").removeClass("loader"); + $("#preview").show(); + }); } function get_datasets_from_checkbox(){ @@ -347,40 +412,53 @@ function evaluate_JSON(){ url += "?algo="+plugin+"&dataset="+datasets - var response = $.ajax({type: "GET", url: url , async: false, dataType: 'json'}).responseText; - rawcontainer.innerHTML = replaceURLWithHTMLLinks(response); - - document.getElementById("input_request_eval").innerHTML = ""+url+"" - document.getElementById("evaluate-div").style.display = 'block'; - - try { - response = JSON.parse(response); - var options = { - mode: 'view' - }; - - //Control the single response results - if (!(Array.isArray(response.evaluations))){ - response.evaluations = [response.evaluations] - } + $('#doevaluate').attr("disabled", true); + $.ajax({type: "GET", url: url, dataType: 'json'}).done(function(resp) { + $('#doevaluate').attr("disabled", false); + response = resp.responseText; - new_tbody = create_body_metrics(response.evaluations) - table.replaceChild(new_tbody, table.lastElementChild) + rawcontainer.innerHTML = replaceURLWithHTMLLinks(response); - var editor = new JSONEditor(container, options, response); - editor.expandAll(); - // $('#results-div a[href="#viewer"]').tab('show'); - $('#evaluate-div a[href="#evaluate-table"]').click(); - // location.hash = 'raw'; - - - } - catch(err){ - console.log("Error decoding JSON (got turtle?)"); - $('#evaluate-div a[href="#evaluate-raw"]').click(); - // location.hash = 'raw'; - } + document.getElementById("input_request_eval").innerHTML = ""+url+"" + document.getElementById("evaluate-div").style.display = 'block'; - + try { + response = JSON.parse(response); + var options = { + mode: 'view' + }; + + //Control the single response results + if (!(Array.isArray(response.evaluations))){ + response.evaluations = [response.evaluations] + } -} \ No newline at end of file + new_tbody = create_body_metrics(response.evaluations) + table.replaceChild(new_tbody, table.lastElementChild) + + var editor = new JSONEditor(container, options, response); + editor.expandAll(); + // $('#results-div a[href="#viewer"]').tab('show'); + $('#evaluate-div a[href="#evaluate-table"]').click(); + // location.hash = 'raw'; + + + } + catch(err){ + console.log("Error decoding JSON (got turtle?)"); + $('#evaluate-div a[href="#evaluate-raw"]').click(); + // location.hash = 'raw'; + } + }) +} + +function draw_plugin_description(){ + var plugin = plugins[get_selected_plugin()]; + $("#plugdescription").text(plugin.description); + console.log(plugin); +} + +function plugin_selected(){ + draw_extra_parameters(); + draw_plugin_description(); +} diff --git a/senpy/templates/index.html b/senpy/templates/index.html index 5d7e4f4..ee6a9a2 100755 --- a/senpy/templates/index.html +++ b/senpy/templates/index.html @@ -5,6 +5,9 @@ Playground {{version}} + @@ -32,7 +35,9 @@ @@ -61,6 +66,14 @@

+

Senpy is a research project. If you use it in your research, please cite: +

+Senpy: A Pragmatic Linked Sentiment Analysis Framework.
+Sánchez-Rada, J. F., Iglesias, C. A., Corcuera, I., & Araque, Ó.
+In Data Science and Advanced Analytics (DSAA),
+2016 IEEE International Conference on (pp. 735-742). IEEE.
+                  
+

@@ -70,8 +83,6 @@
    - - @@ -96,17 +104,28 @@ whilst this text makes me happy and surprised at the same time. I cannot believe it! -
    - - -
    + {% if evaluation %} +
    @@ -169,7 +193,7 @@ I cannot believe it!
    - Evaluate Plugin! + Evaluate Plugin!
    @@ -216,6 +240,7 @@ I cannot believe it! + {% endif %} diff --git a/tests/test_blueprints.py b/tests/test_blueprints.py index 1698bc8..ec8b28f 100644 --- a/tests/test_blueprints.py +++ b/tests/test_blueprints.py @@ -36,7 +36,7 @@ class BlueprintsTest(TestCase): def test_playground(self): resp = self.client.get("/") - assert "main.js" in resp.data.decode() + assert "main.js" in resp.get_data(as_text=True) def test_home(self): """