From 96d98cf46e105c688219892ac934e1a01035dec7 Mon Sep 17 00:00:00 2001 From: Alexander Schieweck Date: Wed, 23 Mar 2016 21:31:44 +0100 Subject: [PATCH 01/44] pushed version to 1.1-SNAPSHOT --- api/pom.xml | 2 +- main/pom.xml | 2 +- pom.xml | 2 +- processors/pom.xml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/api/pom.xml b/api/pom.xml index 7a6adbcc6..572a597b0 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -7,7 +7,7 @@ de.learnlib.alex alex-parent - 1.0-SNAPSHOT + 1.1-SNAPSHOT ../pom.xml diff --git a/main/pom.xml b/main/pom.xml index a780efa73..b9c40ad7f 100644 --- a/main/pom.xml +++ b/main/pom.xml @@ -7,7 +7,7 @@ de.learnlib.alex alex-parent - 1.0-SNAPSHOT + 1.1-SNAPSHOT ../pom.xml diff --git a/pom.xml b/pom.xml index 213d48cd5..766d1942a 100644 --- a/pom.xml +++ b/pom.xml @@ -22,7 +22,7 @@ de.learnlib.alex alex-parent - 1.0-SNAPSHOT + 1.1-SNAPSHOT pom Automata Learning Experience (ALEX) diff --git a/processors/pom.xml b/processors/pom.xml index 1f166a71c..33ca52f8a 100644 --- a/processors/pom.xml +++ b/processors/pom.xml @@ -7,7 +7,7 @@ de.learnlib.alex alex-parent - 1.0-SNAPSHOT + 1.1-SNAPSHOT ../pom.xml From 7299ef22712f25d3905cc3f33619dd5c8a661a7b Mon Sep 17 00:00:00 2001 From: Alexander Bainczyk Date: Mon, 28 Mar 2016 20:23:51 +0200 Subject: [PATCH 02/44] updated frontend dependencies it is advised that you manually delete the node_modules directory once and call npm install in it --- main/src/main/webapp/gruntfile.js | 3 --- main/src/main/webapp/package.json | 40 +++++++++++++--------------- main/src/main/webapp/src/js/index.js | 6 +++-- 3 files changed, 23 insertions(+), 26 deletions(-) diff --git a/main/src/main/webapp/gruntfile.js b/main/src/main/webapp/gruntfile.js index 5d9681b26..b9fc3cdda 100644 --- a/main/src/main/webapp/gruntfile.js +++ b/main/src/main/webapp/gruntfile.js @@ -5,9 +5,6 @@ module.exports = function (grunt) { 'node_modules/ace-builds/src/theme-eclipse.js', 'node_modules/ace-builds/src/mode-json.js', 'node_modules/angular/angular.js', - 'node_modules/angular-animate/angular-animate.js', - 'node_modules/angular-sanitize/angular-sanitize.js', - 'node_modules/angular-messages/angular-messages.js', 'node_modules/angular-ui-ace/src/ui-ace.js', 'node_modules/n3-charts/build/LineChart.js', 'node_modules/selection-model/dist/selection-model.js' diff --git a/main/src/main/webapp/package.json b/main/src/main/webapp/package.json index 0df4607ae..6cc4529f5 100644 --- a/main/src/main/webapp/package.json +++ b/main/src/main/webapp/package.json @@ -23,48 +23,46 @@ "main": "index.html", "dependencies": { "ace-builds": "^1.2.2", - "angular": "^1.5.0", - "angular-animate": "^1.5.0", + "angular": "^1.5.3", + "angular-animate": "^1.5.3", "angular-dragula": "^1.1.9", "angular-jwt": "0.0.9", - "angular-messages": "^1.5.0", - "angular-sanitize": "^1.5.0", + "angular-messages": "^1.5.3", "angular-toastr": "^1.7.0", "angular-ui-ace": "^0.2.3", - "angular-ui-bootstrap": "^1.1.2", + "angular-ui-bootstrap": "^1.2.5", "angular-ui-router": "^0.2.17", "bootstrap-sass": "^3.3.6", "d3": "^3.5.9", "dagre-d3": "^0.4.17", "font-awesome": "^4.4.0", - "install": "^0.3.0", "lodash": "^3.10.1", "n3-charts": "^2.0.3", - "ng-file-upload": "^10.0.2", - "npm": "^3.4.0", + "ng-file-upload": "^12.0.4", "selection-model": "^0.10.0" }, "devDependencies": { - "angular-mocks": "^1.5.0", - "autoprefixer": "^6.2.2", + "angular-mocks": "^1.5.3", + "autoprefixer": "^6.3.5", "babel-preset-es2015": "^6.5.0", "babelify": "^7.2.0", - "browserify-istanbul": "^0.2.1", + "browserify-istanbul": "^2.0.0", "grunt": "^0.4.5", - "grunt-browserify": "^4.0.1", - "grunt-contrib-concat": "^0.5.1", - "grunt-contrib-cssmin": "^0.14.0", + "grunt-browserify": "^5.0.0", + "grunt-contrib-concat": "^1.0.0", + "grunt-contrib-cssmin": "^1.0.1", "grunt-contrib-jshint": "^1.0.0", - "grunt-contrib-uglify": "^0.10.0", - "grunt-contrib-watch": "^0.6.1", + "grunt-contrib-uglify": "^1.0.1", + "grunt-contrib-watch": "^1.0.0", "grunt-html2js": "^0.3.5", - "grunt-karma": "^0.12.1", - "grunt-ng-annotate": "^1.0.1", - "grunt-postcss": "^0.7.1", + "grunt-karma": "^0.12.2", + "grunt-ng-annotate": "^2.0.1", + "grunt-postcss": "^0.8.0", "grunt-sass": "^1.1.0", + "istanbul": "^0.4.2", "karma": "^0.13.15", - "karma-browserify": "^5.0.1", - "karma-chrome-launcher": "^0.2.2", + "karma-browserify": "^5.0.3", + "karma-chrome-launcher": "^0.2.3", "karma-coverage": "^0.5.3", "karma-jasmine": "^0.3.7" } diff --git a/main/src/main/webapp/src/js/index.js b/main/src/main/webapp/src/js/index.js index c53b36b3f..c2d379f2e 100644 --- a/main/src/main/webapp/src/js/index.js +++ b/main/src/main/webapp/src/js/index.js @@ -21,6 +21,8 @@ import d3 from 'd3/d3'; import uiBootstrap from 'angular-ui-bootstrap'; import uiRouter from 'angular-ui-router'; import toastr from 'angular-toastr'; +import ngAnimate from 'angular-animate'; +import ngMessages from 'angular-messages'; import {configuration} from './config'; import {routes} from './routes'; @@ -36,8 +38,8 @@ angular .module('ALEX', [ // modules from external libraries - 'ngAnimate', - 'ngMessages', + ngAnimate, + ngMessages, uiBootstrap, uiRouter, 'ui.ace', From 7a7a2ad7318df68ac05781c790f54cc7ce359947 Mon Sep 17 00:00:00 2001 From: Alexander Schieweck Date: Wed, 30 Mar 2016 11:45:26 +0200 Subject: [PATCH 03/44] updated .gitignore: removed webbapp/bower_components, added webapp/app. --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 73dabe242..8122f6649 100644 --- a/.gitignore +++ b/.gitignore @@ -8,7 +8,7 @@ target hs_err_pid* # JavaScript -main/src/main/webapp/bower_components +main/src/main/webapp/app main/src/main/webapp/node_modules main/src/main/webapp/dist main/src/main/webapp/tests/coverage From 64b4dec851e7045c3223dd714084997b932087cd Mon Sep 17 00:00:00 2001 From: Alexander Schieweck Date: Wed, 30 Mar 2016 11:47:50 +0200 Subject: [PATCH 04/44] fixed the integration test symbols: changed a hard coded url to the 'PROJECT_PATH' variable. --- main/src/test/resources/integrationtest/Symbols.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main/src/test/resources/integrationtest/Symbols.json b/main/src/test/resources/integrationtest/Symbols.json index b7488da99..336cb7737 100644 --- a/main/src/test/resources/integrationtest/Symbols.json +++ b/main/src/test/resources/integrationtest/Symbols.json @@ -89,7 +89,7 @@ "type": "rest_call", "method": "PUT", "url": "/projects/{{$project}}", - "data" : "{\"name\": \"The real ALEX\", \"baseUrl\": \"http://localhost:8080/rest\"}", + "data" : "{\"name\": \"The real ALEX\", \"baseUrl\": \"%%PROJECT_PATH%%\"}", "headers": { "Authorization": "Bearer {{$token}}" } @@ -202,4 +202,4 @@ { "type": "wait", "duration": 150000} ] } -] \ No newline at end of file +] From 1ebeae3b6ec1068fdacb883bb452b9f0677b1958 Mon Sep 17 00:00:00 2001 From: Alexander Schieweck Date: Wed, 30 Mar 2016 12:24:06 +0200 Subject: [PATCH 05/44] added propper logging to the FillAction. --- .../alex/actions/WebSymbolActions/FillAction.java | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/main/src/main/java/de/learnlib/alex/actions/WebSymbolActions/FillAction.java b/main/src/main/java/de/learnlib/alex/actions/WebSymbolActions/FillAction.java index 8615a64b6..4bf23d0d2 100644 --- a/main/src/main/java/de/learnlib/alex/actions/WebSymbolActions/FillAction.java +++ b/main/src/main/java/de/learnlib/alex/actions/WebSymbolActions/FillAction.java @@ -21,6 +21,8 @@ import de.learnlib.alex.core.entities.ExecuteResult; import de.learnlib.alex.core.learner.connectors.WebSiteConnector; import de.learnlib.alex.utils.CSSUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.hibernate.validator.constraints.NotBlank; import org.openqa.selenium.NoSuchElementException; import org.openqa.selenium.WebElement; @@ -40,6 +42,9 @@ public class FillAction extends WebSymbolAction { /** to be serializable. */ private static final long serialVersionUID = 8595076806577663223L; + /** Use the learner logger. */ + private static final Logger LOGGER = LogManager.getLogger("learner"); + /** * The node to look for. * @requiredField @@ -117,12 +122,16 @@ public void setValue(String value) { @Override public ExecuteResult execute(WebSiteConnector connector) { + String nodeWithVariables = getNodeWithVariableValues(); + String valueWithVariables = getValueWithVariableValues(); try { - WebElement element = connector.getElement(CSSUtils.escapeSelector(getNodeWithVariableValues())); + WebElement element = connector.getElement(CSSUtils.escapeSelector(nodeWithVariables)); element.clear(); - element.sendKeys(getValueWithVariableValues()); + element.sendKeys(valueWithVariables); return getSuccessOutput(); } catch (NoSuchElementException e) { + LOGGER.info("Could not find the element '" + nodeWithVariables + "' to fill it with '" + + valueWithVariables + "'.", e); return getFailedOutput(); } } From 799f9d13399b05c28a19602f4f8f88c87f9f6da4 Mon Sep 17 00:00:00 2001 From: Alexander Schieweck Date: Wed, 30 Mar 2016 12:27:03 +0200 Subject: [PATCH 06/44] updated .gitignore: removed webapp/app which apparently stil existed in my setup. --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index 8122f6649..0dca62c95 100644 --- a/.gitignore +++ b/.gitignore @@ -8,7 +8,6 @@ target hs_err_pid* # JavaScript -main/src/main/webapp/app main/src/main/webapp/node_modules main/src/main/webapp/dist main/src/main/webapp/tests/coverage From 96fa392684f0ef0c84ff774b5b22e28daf009969 Mon Sep 17 00:00:00 2001 From: Alexander Schieweck Date: Wed, 30 Mar 2016 12:35:23 +0200 Subject: [PATCH 07/44] added webapp/dist and uploads to the clean phase of Maven. --- main/pom.xml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/main/pom.xml b/main/pom.xml index b9c40ad7f..966e7c84a 100644 --- a/main/pom.xml +++ b/main/pom.xml @@ -253,6 +253,21 @@ + + maven-clean-plugin + 3.0.0 + + + + src/main/webapp/dist + + + uploads + + + + + org.apache.maven.plugins maven-war-plugin From 6311a9223d29c6232515d8294191a2d46eae3392 Mon Sep 17 00:00:00 2001 From: Alexander Schieweck Date: Wed, 30 Mar 2016 13:05:23 +0200 Subject: [PATCH 08/44] updated dependencies. --- main/pom.xml | 13 +++++++++---- pom.xml | 8 ++++---- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/main/pom.xml b/main/pom.xml index 966e7c84a..7abc810e6 100644 --- a/main/pom.xml +++ b/main/pom.xml @@ -20,8 +20,8 @@ - 1.3.2.RELEASE - 2.4.1 + 1.3.3.RELEASE + 2.5 de.learnlib.alex.App @@ -60,14 +60,14 @@ org.springframework spring-context - 4.2.4.RELEASE + 4.2.5.RELEASE org.hibernate hibernate-core - 5.0.3.Final + 5.1.0.Final org.hibernate @@ -170,6 +170,11 @@ selenium-java ${selenium.version} + + org.seleniumhq.selenium + htmlunit-driver + 2.20 + xml-apis xml-apis diff --git a/pom.xml b/pom.xml index 766d1942a..1a6debe15 100644 --- a/pom.xml +++ b/pom.xml @@ -113,13 +113,13 @@ 2.2 - 2.22.1 - 5.2.2.Final + 2.22.2 + 5.2.4.Final 0.12.0 0.6.0 - 2.48.2 + 2.53.0 9.3.5.v20151012 - 0.4.4 + 0.5.0 1.2.4 From 3640fc1de66f73b6fe99b8444830f9d63a028e8f Mon Sep 17 00:00:00 2001 From: Alexander Schieweck Date: Fri, 1 Apr 2016 15:08:57 +0200 Subject: [PATCH 09/44] moved the path of the uploads directory into the Spring property 'alex.uploadspath' and added command line argument '--uploadspath=...' for it. Fixed that the upload directory could only be created if all its root folders existed. --- .../learnlib/alex/core/dao/FileDAOImpl.java | 20 +++++++++++++++---- .../src/main/resources/application.properties | 1 + 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/main/src/main/java/de/learnlib/alex/core/dao/FileDAOImpl.java b/main/src/main/java/de/learnlib/alex/core/dao/FileDAOImpl.java index 9e2615c3a..5b686fbdb 100644 --- a/main/src/main/java/de/learnlib/alex/core/dao/FileDAOImpl.java +++ b/main/src/main/java/de/learnlib/alex/core/dao/FileDAOImpl.java @@ -19,13 +19,16 @@ import de.learnlib.alex.core.entities.UploadableFile; import de.learnlib.alex.exceptions.NotFoundException; import org.glassfish.jersey.media.multipart.FormDataContentDisposition; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Repository; +import javax.annotation.PostConstruct; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.nio.file.Path; import java.nio.file.Paths; import java.util.LinkedList; import java.util.List; @@ -39,18 +42,27 @@ public class FileDAOImpl implements FileDAO { /** The size of the output write buffer in bytes. */ public static final int WRITE_BUFFER_SIZE = 1024; + /** + * The path of the upload directory as String. + * This will be injected by Spring and is configured in the applications.properties file. + */ + @Value("${alex.uploadspath}") + private String uploadPathAsString; + /** Path to the root of the upload directory. */ private java.nio.file.Path uploadedDirectoryBaseLocation; /** - * Default consturtor that jsut initialises the internal data structures. + * Create the uploads directory, if necessary. + * Called by Spring after the DAO object is created and all injections are present. */ - public FileDAOImpl() { - this.uploadedDirectoryBaseLocation = Paths.get(System.getProperty("user.dir"), "uploads"); + @PostConstruct + private void init() { + this.uploadedDirectoryBaseLocation = Paths.get(uploadPathAsString); File uploadBaseDirectory = uploadedDirectoryBaseLocation.toFile(); if (!uploadBaseDirectory.exists()) { - uploadBaseDirectory.mkdir(); + uploadBaseDirectory.mkdirs(); } } diff --git a/main/src/main/resources/application.properties b/main/src/main/resources/application.properties index 3801ba15f..244ab15b4 100644 --- a/main/src/main/resources/application.properties +++ b/main/src/main/resources/application.properties @@ -1 +1,2 @@ server.port=${port:8000} +alex.uploadspath=${uploadspath:./target/uploads} From 27fc17d24b9b70f974d7dc9659bef56bf721f0a2 Mon Sep 17 00:00:00 2001 From: Alexander Bainczyk Date: Tue, 12 Apr 2016 15:19:21 +0200 Subject: [PATCH 10/44] updated frontend build management to grunt v1.0.0 You can uninstall grunt and the grunt-cli from your global node_modules. Instead of calling `grunt [task]` in the webapp folder execute * `npm run build` to build the frontend * `npm run dev` to build the frontend and call file watchers * `npm test` to execute the unit tests --- README.md | 1 - main/pom.xml | 19 +++++++++++-------- main/src/main/webapp/package.json | 8 +++++++- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 46c527cb2..1ad3d55b5 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,6 @@ For the Installation from the source files make sure your system matches the fol * Java JDK 8 * Maven 3 * Node.js v4.2.* and the NPM -* Grunt and Grund-Cli `npm install -g grunt grunt-cli` To install and run ALEX, execute the following commands in a directory of your choice: diff --git a/main/pom.xml b/main/pom.xml index 7abc810e6..13e458593 100644 --- a/main/pom.xml +++ b/main/pom.xml @@ -458,17 +458,16 @@ 1.4.0 - grunt + npm-build generate-sources exec - grunt + npm - build-html - build-css - build-js + run + build src/main/webapp @@ -494,7 +493,7 @@ 1.4.0 - npm + npm-install initialize exec @@ -508,13 +507,17 @@ - grunt + npm-build generate-sources exec - grunt + npm + + run + build + src/main/webapp diff --git a/main/src/main/webapp/package.json b/main/src/main/webapp/package.json index 6cc4529f5..a1751531c 100644 --- a/main/src/main/webapp/package.json +++ b/main/src/main/webapp/package.json @@ -10,6 +10,11 @@ "type": "Apache-2.0", "url": "http://www.apache.org/licenses/LICENSE-2.0" }, + "scripts": { + "build": "grunt", + "dev": "grunt && grunt -- watch", + "test": "grunt -- test" + }, "contributors": [ { "name": "Alexander Bainczyk", @@ -47,7 +52,7 @@ "babel-preset-es2015": "^6.5.0", "babelify": "^7.2.0", "browserify-istanbul": "^2.0.0", - "grunt": "^0.4.5", + "grunt": "^1.0.1", "grunt-browserify": "^5.0.0", "grunt-contrib-concat": "^1.0.0", "grunt-contrib-cssmin": "^1.0.1", @@ -60,6 +65,7 @@ "grunt-postcss": "^0.8.0", "grunt-sass": "^1.1.0", "istanbul": "^0.4.2", + "jasmine-core": "^2.4.1", "karma": "^0.13.15", "karma-browserify": "^5.0.3", "karma-chrome-launcher": "^0.2.3", From da4b9f998934d2cc0a880f5bb0620dbd80224549 Mon Sep 17 00:00:00 2001 From: Alexander Bainczyk Date: Sat, 23 Apr 2016 10:31:38 +0200 Subject: [PATCH 11/44] updated angular to v1.5.5 renamed Symbol to AlphabetSymbol since 'Symbol' is a reserved keyword in es6 --- main/src/main/webapp/package.json | 6 +-- .../js/components/forms/projectCreateForm.js | 8 ++-- .../src/js/components/forms/userLoginForm.js | 26 ++++++----- .../js/components/forms/userRegisterForm.js | 25 ++++++----- .../js/components/views/learnerSetupView.js | 8 ++-- .../js/components/views/symbolsActionsView.js | 6 +-- .../js/components/views/symbolsHistoryView.js | 10 ++--- .../js/components/views/symbolsImportView.js | 12 ++--- .../js/components/views/symbolsTrashView.js | 8 ++-- .../src/js/components/views/symbolsView.js | 20 ++++----- .../widgets/counterexamplesWidget.js | 2 +- .../modals/actionCreateModalHandle.js | 2 +- .../modals/symbolCreateModalHandle.js | 8 ++-- .../modals/symbolEditModalHandle.js | 10 ++--- .../modals/symbolGroupCreateModalHandle.js | 4 +- .../modals/symbolMoveModalHandle.js | 8 ++-- .../entities/{Symbol.js => AlphabetSymbol.js} | 45 +++++++++---------- .../src/js/entities/LearnConfiguration.js | 4 +- .../main/webapp/src/js/entities/Project.js | 44 +++++++++--------- .../webapp/src/js/entities/SymbolGroup.js | 34 ++++---------- main/src/main/webapp/src/js/entities/User.js | 22 ++------- .../ExecuteSymbolGeneralAction.js | 4 +- .../restActions/CheckStatusRestAction.js | 5 +-- .../src/js/resources/ProjectResource.js | 2 +- .../src/js/resources/SymbolGroupResource.js | 2 +- .../webapp/src/js/resources/SymbolResource.js | 32 ++++++------- .../webapp/src/js/resources/UserResource.js | 24 ++++++---- .../forms/projectCreateForm.tests.js | 6 +-- .../components/forms/userLoginForm.tests.js | 10 ++--- .../forms/userRegisterForm.tests.js | 13 +++--- .../views/symbolsHistoryView.tests.js | 6 +-- .../views/symbolsImportView.tests.js | 6 +-- .../views/symbolsTrashView.tests.js | 4 +- .../modals/symbolCreateModal.tests.js | 4 +- .../modals/symbolEditModal.tests.js | 4 +- .../modals/symbolGroupCreateModal.tests.js | 8 ++-- .../unit/entities/LearnConfiguration.tests.js | 6 +-- .../tests/unit/entities/Project.tests.js | 22 +-------- .../tests/unit/entities/Symbol.tests.js | 23 ++-------- .../tests/unit/entities/SymbolGroup.tests.js | 20 ++------- .../webapp/tests/unit/entities/User.tests.js | 16 ------- .../unit/resources/ProjectResource.tests.js | 4 +- .../resources/SymbolGroupResource.tests.js | 4 +- .../unit/resources/SymbolResource.tests.js | 30 ++++++------- .../unit/resources/UserResource.tests.js | 17 ++++--- 45 files changed, 251 insertions(+), 333 deletions(-) rename main/src/main/webapp/src/js/entities/{Symbol.js => AlphabetSymbol.js} (76%) diff --git a/main/src/main/webapp/package.json b/main/src/main/webapp/package.json index a1751531c..777e2da87 100644 --- a/main/src/main/webapp/package.json +++ b/main/src/main/webapp/package.json @@ -28,11 +28,11 @@ "main": "index.html", "dependencies": { "ace-builds": "^1.2.2", - "angular": "^1.5.3", - "angular-animate": "^1.5.3", + "angular": "^1.5.5", + "angular-animate": "^1.5.5", "angular-dragula": "^1.1.9", "angular-jwt": "0.0.9", - "angular-messages": "^1.5.3", + "angular-messages": "^1.5.5", "angular-toastr": "^1.7.0", "angular-ui-ace": "^0.2.3", "angular-ui-bootstrap": "^1.2.5", diff --git a/main/src/main/webapp/src/js/components/forms/projectCreateForm.js b/main/src/main/webapp/src/js/components/forms/projectCreateForm.js index f3ceddb69..2f9c73fc3 100644 --- a/main/src/main/webapp/src/js/components/forms/projectCreateForm.js +++ b/main/src/main/webapp/src/js/components/forms/projectCreateForm.js @@ -15,7 +15,7 @@ */ import {events} from '../../constants'; -import {ProjectFormModel} from '../../entities/Project'; +import {Project} from '../../entities/Project'; /** The class of the project create form component */ // @ngInject @@ -34,9 +34,9 @@ class ProjectCreateForm { /** * The empty project model that is used for the form - * @type {ProjectFormModel} + * @type {Project} */ - this.project = new ProjectFormModel(); + this.project = new Project(); } /** Creates a new project */ @@ -45,7 +45,7 @@ class ProjectCreateForm { .then(createdProject => { this.ToastService.success(`Project "${createdProject.name}" created`); this.EventBus.emit(events.PROJECT_CREATED, {project: createdProject}); - this.project = new ProjectFormModel(); + this.project = new Project(); // set the form to its original state this.form.$setPristine(); diff --git a/main/src/main/webapp/src/js/components/forms/userLoginForm.js b/main/src/main/webapp/src/js/components/forms/userLoginForm.js index 15fb29e57..4262b83ce 100644 --- a/main/src/main/webapp/src/js/components/forms/userLoginForm.js +++ b/main/src/main/webapp/src/js/components/forms/userLoginForm.js @@ -15,7 +15,7 @@ */ import {events} from '../../constants'; -import {UserFormModel} from '../../entities/User'; +import {User} from '../../entities/User'; /** The component controller for the user login form */ // @ngInject @@ -39,25 +39,31 @@ class UserLoginForm { this.EventBus = EventBus; /** - * The user that wants to login - * @type {UserFormModel} + * The email of the user + * @type {string} */ - this.user = new UserFormModel(); + this.email = null; + + /** + * The password of the user + * @type {string} + */ + this.password = null; } login() { - if (this.user.email && this.user.password) { - this.UserResource.login(this.user) + if (this.email && this.password) { + this.UserResource.login(this.email, this.password) .then(response => { this.ToastService.info('You have logged in!'); // decode the token and create a user from it const token = response.data.token; const tokenPayload = this.jwtHelper.decodeToken(token); - const user = { + const user = new User({ id: tokenPayload.userId, role: tokenPayload.userRole - }; + }); this.SessionService.saveUser(user, token); this.EventBus.emit(events.USER_LOGGED_IN, {user: user}); @@ -77,7 +83,7 @@ const userLoginForm = {
- +
@@ -90,7 +96,7 @@ const userLoginForm = {
- +
diff --git a/main/src/main/webapp/src/js/components/forms/userRegisterForm.js b/main/src/main/webapp/src/js/components/forms/userRegisterForm.js index 4a9f8b03c..2a48df4b1 100644 --- a/main/src/main/webapp/src/js/components/forms/userRegisterForm.js +++ b/main/src/main/webapp/src/js/components/forms/userRegisterForm.js @@ -14,8 +14,6 @@ * limitations under the License. */ -import {UserFormModel} from '../../entities/User'; - /** The controller for the user register form component */ // @ngInject class UserRegisterFrom { @@ -31,18 +29,25 @@ class UserRegisterFrom { /** - * The user to create - * @type {UserFormModel} + * The email of the user + * @type {string} + */ + this.email = null; + + /** + * The password of the user + * @type {string} */ - this.user = new UserFormModel(); + this.password = null; } register() { - if (this.user.email && this.user.password) { - this.UserResource.create(this.user) + if (this.email && this.password) { + this.UserResource.create(this.email, this.password) .then(() => { this.ToastService.success('Registration successful'); - this.user = new UserFormModel(); + this.email = null; + this.password = null; }) .catch(response => { this.ToastService.danger(`Registration failed. ${response.data.message}`); @@ -60,7 +65,7 @@ const userRegisterForm = {
- +
@@ -73,7 +78,7 @@ const userRegisterForm = {
- +
diff --git a/main/src/main/webapp/src/js/components/views/learnerSetupView.js b/main/src/main/webapp/src/js/components/views/learnerSetupView.js index ba055bb59..c195b123d 100644 --- a/main/src/main/webapp/src/js/components/views/learnerSetupView.js +++ b/main/src/main/webapp/src/js/components/views/learnerSetupView.js @@ -50,13 +50,13 @@ class LearnerSetupView { /** * A list of all symbols of all groups that is used in order to select them - * @type {Symbol[]} + * @type {AlphabetSymbol[]} */ this.allSymbols = []; /** * A list of selected Symbols - * @type {Symbol[]} + * @type {AlphabetSymbol[]} */ this.selectedSymbols = []; @@ -68,7 +68,7 @@ class LearnerSetupView { /** * The symbol that should be used as a reset symbol - * @type {Symbol|null} + * @type {AlphabetSymbol|null} */ this.resetSymbol = null; @@ -119,7 +119,7 @@ class LearnerSetupView { /** * Sets the reset symbol - * @param {Symbol} symbol - The symbol that will be used to reset the sul + * @param {AlphabetSymbol} symbol - The symbol that will be used to reset the sul */ setResetSymbol(symbol) { this.resetSymbol = symbol; diff --git a/main/src/main/webapp/src/js/components/views/symbolsActionsView.js b/main/src/main/webapp/src/js/components/views/symbolsActionsView.js index 58fd926ac..9b6b9f42a 100644 --- a/main/src/main/webapp/src/js/components/views/symbolsActionsView.js +++ b/main/src/main/webapp/src/js/components/views/symbolsActionsView.js @@ -16,7 +16,7 @@ import _ from 'lodash'; import {events} from '../../constants'; -import {Symbol} from '../../entities/Symbol'; +import {AlphabetSymbol} from '../../entities/AlphabetSymbol'; /** * The controller that handles the page for managing all actions of a symbol. The symbol whose actions should be @@ -56,7 +56,7 @@ class SymbolsActionsView { /** * The symbol whose actions are managed - * @type {Symbol|null} + * @type {AlphabetSymbol|null} */ this.symbol = null; @@ -170,7 +170,7 @@ class SymbolsActionsView { saveChanges() { // make a copy of the symbol - const symbolToUpdate = new Symbol(this.symbol); + const symbolToUpdate = new AlphabetSymbol(this.symbol); // update the symbol this.SymbolResource.update(symbolToUpdate) diff --git a/main/src/main/webapp/src/js/components/views/symbolsHistoryView.js b/main/src/main/webapp/src/js/components/views/symbolsHistoryView.js index 7b1f86e09..4298d72ba 100644 --- a/main/src/main/webapp/src/js/components/views/symbolsHistoryView.js +++ b/main/src/main/webapp/src/js/components/views/symbolsHistoryView.js @@ -14,7 +14,7 @@ * limitations under the License. */ -import {Symbol} from '../../entities/Symbol'; +import {AlphabetSymbol} from '../../entities/AlphabetSymbol'; /** * The controller for the page where the revision history if a symbol is listed and old revisions can be restored @@ -39,13 +39,13 @@ class SymbolsHistoryView { /** * All revisions of a symbol - * @type {Symbol[]} + * @type {AlphabetSymbol[]} */ this.revisions = []; /** * The most current version of a symbol - * @type {Symbol} + * @type {AlphabetSymbol} */ this.latestRevision = null; @@ -62,10 +62,10 @@ class SymbolsHistoryView { /** * Restores a previous revision of a symbol by updating the latest with the properties of the revision - * @param {Symbol} revision - The revision of the symbol that should be restored + * @param {AlphabetSymbol} revision - The revision of the symbol that should be restored */ restoreRevision(revision) { - const symbol = new Symbol(this.latestRevision); + const symbol = new AlphabetSymbol(this.latestRevision); // copy all important properties from the revision to the latest symbol.name = revision.name; diff --git a/main/src/main/webapp/src/js/components/views/symbolsImportView.js b/main/src/main/webapp/src/js/components/views/symbolsImportView.js index 4572e5c00..f704a105a 100644 --- a/main/src/main/webapp/src/js/components/views/symbolsImportView.js +++ b/main/src/main/webapp/src/js/components/views/symbolsImportView.js @@ -16,7 +16,7 @@ import _ from 'lodash'; import {events} from '../../constants'; -import {Symbol} from '../../entities/Symbol'; +import {AlphabetSymbol} from '../../entities/AlphabetSymbol'; /** * The controller that handles the import of symbols from a *.json file. @@ -44,13 +44,13 @@ class SymbolsImportView { /** * The symbols that will be uploaded - * @type {Symbol[]} + * @type {AlphabetSymbol[]} */ this.symbols = []; /** * The list of selected symbols - * @type {Symbol[]} + * @type {AlphabetSymbol[]} */ this.selectedSymbols = []; @@ -80,7 +80,7 @@ class SymbolsImportView { try { this.symbols = angular.fromJson(data).map(s => { s.id = _.uniqueId(); - return new Symbol(s); + return new AlphabetSymbol(s); }); } catch (e) { this.ToastService.danger('

Loading json file failed

' + e); @@ -96,7 +96,7 @@ class SymbolsImportView { this.SymbolResource.getAll(this.project.id) .then(existingSymbols => { const maxId = _.max(existingSymbols, 'id').id; - const symbols = this.selectedSymbols.map(s => new Symbol(s)); + const symbols = this.selectedSymbols.map(s => new AlphabetSymbol(s)); symbols.forEach(symbol => { delete symbol.id; @@ -129,7 +129,7 @@ class SymbolsImportView { * Changes the name and/or the abbreviation a symbol before uploading it to prevent naming conflicts in the * database. * - * @param {Symbol} updatedSymbol - The updated symbol + * @param {AlphabetSymbol} updatedSymbol - The updated symbol */ updateSymbol(updatedSymbol) { diff --git a/main/src/main/webapp/src/js/components/views/symbolsTrashView.js b/main/src/main/webapp/src/js/components/views/symbolsTrashView.js index b3f4da2b0..dd140b9f9 100644 --- a/main/src/main/webapp/src/js/components/views/symbolsTrashView.js +++ b/main/src/main/webapp/src/js/components/views/symbolsTrashView.js @@ -15,7 +15,7 @@ */ import _ from 'lodash'; -import {Symbol} from '../../entities/Symbol'; +import {AlphabetSymbol} from '../../entities/AlphabetSymbol'; /** * Lists all deleted symbols, what means the symbols where the property 'visible' == 'hidden'. Handles the recover @@ -42,13 +42,13 @@ class SymbolsTrashView { /** * The list of deleted symbols - * @type {Symbol[]} + * @type {AlphabetSymbol[]} */ this.symbols = []; /** * The list of selected symbols - * @type {Symbol[]} + * @type {AlphabetSymbol[]} */ this.selectedSymbols = []; @@ -60,7 +60,7 @@ class SymbolsTrashView { /** * Recovers a deleted symbol by calling the API and removes the recovered symbol from the symbol list on success - * @param {Symbol} symbol - The symbol that should be recovered from the trash + * @param {AlphabetSymbol} symbol - The symbol that should be recovered from the trash */ recoverSymbol(symbol) { this.SymbolResource.recover(symbol) diff --git a/main/src/main/webapp/src/js/components/views/symbolsView.js b/main/src/main/webapp/src/js/components/views/symbolsView.js index c20a4e21e..4adefe9c8 100644 --- a/main/src/main/webapp/src/js/components/views/symbolsView.js +++ b/main/src/main/webapp/src/js/components/views/symbolsView.js @@ -16,7 +16,7 @@ import _ from 'lodash'; import {events} from '../../constants'; -import {Symbol} from '../../entities/Symbol'; +import {AlphabetSymbol} from '../../entities/AlphabetSymbol'; /** * The controller that handles CRUD operations on symbols and symbol groups. @@ -51,7 +51,7 @@ class SymbolsView { /** * The model for selected symbols - * @type {Symbol[]} + * @type {AlphabetSymbol[]} */ this.selectedSymbols = []; @@ -110,7 +110,7 @@ class SymbolsView { /** * Extracts all symbols from all symbol groups and merges them into a single array * - * @returns {Symbol[]} + * @returns {AlphabetSymbol[]} */ getAllSymbols() { return _.flatten(this.groups.map(g => g.symbols)); @@ -119,7 +119,7 @@ class SymbolsView { /** * Adds a single new symbol to the scope by finding its corresponding group and adding it there * - * @param {Symbol} symbol - The symbol that should be added + * @param {AlphabetSymbol} symbol - The symbol that should be added */ addSymbol(symbol) { this.findGroupFromSymbol(symbol).symbols.push(symbol); @@ -129,7 +129,7 @@ class SymbolsView { * Removes a list of symbols from the scope by finding the group of each symbol and removing it from * it * - * @param {Symbol[]} symbols - The symbols that should be removed + * @param {AlphabetSymbol[]} symbols - The symbols that should be removed */ removeSymbols(symbols) { symbols.forEach(symbol => { @@ -141,7 +141,7 @@ class SymbolsView { /** * Updates an existing symbol * - * @param {Symbol} updatedSymbol - The updated symbol object + * @param {AlphabetSymbol} updatedSymbol - The updated symbol object */ updateSymbol(updatedSymbol) { this.updateSymbols([updatedSymbol]); @@ -150,7 +150,7 @@ class SymbolsView { /** * Updates multiple existing symbols * - * @param {Symbol[]} updatedSymbols - The updated symbol objects + * @param {AlphabetSymbol[]} updatedSymbols - The updated symbol objects */ updateSymbols(updatedSymbols) { updatedSymbols.forEach(symbol => { @@ -168,7 +168,7 @@ class SymbolsView { /** * Moves a list of existing symbols into another group. * - * @param {Symbol[]} symbols - The symbols that should be moved + * @param {AlphabetSymbol[]} symbols - The symbols that should be moved * @param {SymbolGroup} group - The group the symbols should be moved into */ moveSymbolsToGroup(symbols, group) { @@ -186,7 +186,7 @@ class SymbolsView { /** * Deletes a single symbol from the server and from the scope if the deletion was successful * - * @param {Symbol} symbol - The symbol to be deleted + * @param {AlphabetSymbol} symbol - The symbol to be deleted */ deleteSymbol(symbol) { this.SymbolResource.remove(symbol) @@ -244,7 +244,7 @@ class SymbolsView { // create a copy of the symbol list and sort them by id // so that ids can be referenced correctly by executeSymbol actions const symbols = this.selectedSymbols - .map(s => new Symbol(s)) + .map(s => new AlphabetSymbol(s)) .sort((s1, s2) => s1.id - s2.id); // adjust referenced symbol ids from executeSymbol actions diff --git a/main/src/main/webapp/src/js/components/widgets/counterexamplesWidget.js b/main/src/main/webapp/src/js/components/widgets/counterexamplesWidget.js index 559166232..7eb0d2005 100644 --- a/main/src/main/webapp/src/js/components/widgets/counterexamplesWidget.js +++ b/main/src/main/webapp/src/js/components/widgets/counterexamplesWidget.js @@ -45,7 +45,7 @@ class CounterexamplesWidget { /** * The symbols - * @type {Symbol[]} + * @type {AlphabetSymbol[]} */ this.symbols = []; diff --git a/main/src/main/webapp/src/js/directives/modals/actionCreateModalHandle.js b/main/src/main/webapp/src/js/directives/modals/actionCreateModalHandle.js index 49afbc052..fd5d37e75 100644 --- a/main/src/main/webapp/src/js/directives/modals/actionCreateModalHandle.js +++ b/main/src/main/webapp/src/js/directives/modals/actionCreateModalHandle.js @@ -43,7 +43,7 @@ class ActionCreateModalController { /** * All symbols of the project - * @type {Symbol[]} + * @type {AlphabetSymbol[]} */ this.symbols = []; diff --git a/main/src/main/webapp/src/js/directives/modals/symbolCreateModalHandle.js b/main/src/main/webapp/src/js/directives/modals/symbolCreateModalHandle.js index 224a9ec59..55bda662e 100644 --- a/main/src/main/webapp/src/js/directives/modals/symbolCreateModalHandle.js +++ b/main/src/main/webapp/src/js/directives/modals/symbolCreateModalHandle.js @@ -15,7 +15,7 @@ */ import {events} from '../../constants'; -import {SymbolFormModel} from '../../entities/Symbol'; +import {AlphabetSymbol} from '../../entities/AlphabetSymbol'; /** The controller for the modal window to create a new symbol */ // @ngInject @@ -44,9 +44,9 @@ class SymbolCreateModalController { /** * The model of the symbol that will be created - * @type {SymbolFormModel} + * @type {AlphabetSymbol} */ - this.symbol = new SymbolFormModel(); + this.symbol = new AlphabetSymbol(); /** * The list of available symbol groups where the new symbol could be created in @@ -88,7 +88,7 @@ class SymbolCreateModalController { .then(symbol => { this.ToastService.success(`Created symbol "${symbol.name}"`); this.EventBus.emit(events.SYMBOL_CREATED, {symbol: symbol}); - this.symbol = new SymbolFormModel(); + this.symbol = new AlphabetSymbol(); // set the form to its original state this.form.$setPristine(); diff --git a/main/src/main/webapp/src/js/directives/modals/symbolEditModalHandle.js b/main/src/main/webapp/src/js/directives/modals/symbolEditModalHandle.js index 0f4ac9b42..1c1ceb612 100644 --- a/main/src/main/webapp/src/js/directives/modals/symbolEditModalHandle.js +++ b/main/src/main/webapp/src/js/directives/modals/symbolEditModalHandle.js @@ -14,7 +14,7 @@ * limitations under the License. */ -import {Symbol} from '../../entities/Symbol'; +import {AlphabetSymbol} from '../../entities/AlphabetSymbol'; import {events} from '../../constants'; /** @@ -40,15 +40,15 @@ class SymbolEditModalController { /** * The symbol to edit - * @type {Symbol} + * @type {AlphabetSymbol} */ this.symbol = modalData.symbol; /** * A copy of the old symbol - * @type {Symbol} + * @type {AlphabetSymbol} */ - this.symbolCopy = new Symbol(modalData.symbol); + this.symbolCopy = new AlphabetSymbol(modalData.symbol); /** * The error message that is displayed when update fails @@ -118,7 +118,7 @@ function symbolEditModalHandle($uibModal) { resolve: { modalData: function () { return { - symbol: new Symbol(scope.symbol), + symbol: new AlphabetSymbol(scope.symbol), updateOnServer: scope.updateOnServer }; } diff --git a/main/src/main/webapp/src/js/directives/modals/symbolGroupCreateModalHandle.js b/main/src/main/webapp/src/js/directives/modals/symbolGroupCreateModalHandle.js index 84bcdf36e..f14519be9 100644 --- a/main/src/main/webapp/src/js/directives/modals/symbolGroupCreateModalHandle.js +++ b/main/src/main/webapp/src/js/directives/modals/symbolGroupCreateModalHandle.js @@ -15,7 +15,7 @@ */ import {events} from '../../constants'; -import {SymbolGroupFormModel} from '../../entities/SymbolGroup'; +import {SymbolGroup} from '../../entities/SymbolGroup'; /** The controller for the modal dialog that handles the creation of a new symbol group. */ // @ngInject @@ -45,7 +45,7 @@ class SymbolGroupCreateModalController { * The new symbol group * @type {SymbolGroup} */ - this.group = new SymbolGroupFormModel(); + this.group = new SymbolGroup(); /** * An error message that can be displayed in the modal template diff --git a/main/src/main/webapp/src/js/directives/modals/symbolMoveModalHandle.js b/main/src/main/webapp/src/js/directives/modals/symbolMoveModalHandle.js index 42e757238..5d0784b88 100644 --- a/main/src/main/webapp/src/js/directives/modals/symbolMoveModalHandle.js +++ b/main/src/main/webapp/src/js/directives/modals/symbolMoveModalHandle.js @@ -14,7 +14,7 @@ * limitations under the License. */ -import {Symbol} from '../../entities/Symbol'; +import {AlphabetSymbol} from '../../entities/AlphabetSymbol'; import {events} from '../../constants'; /** @@ -46,7 +46,7 @@ class SymbolMoveModalController { /** * The list of symbols that should be moved - * @type {Symbol[]} + * @type {AlphabetSymbol[]} */ this.symbols = modalData.symbols; @@ -75,7 +75,7 @@ class SymbolMoveModalController { moveSymbols() { if (this.selectedGroup !== null) { - const symbolsToMove = this.symbols.map(s => new Symbol(s)); + const symbolsToMove = this.symbols.map(s => new AlphabetSymbol(s)); symbolsToMove.forEach(s => {s.group = this.selectedGroup.id;}); this.SymbolResource.moveMany(symbolsToMove, this.selectedGroup) @@ -135,7 +135,7 @@ function symbolMoveModalHandle($uibModal) { controllerAs: 'vm', resolve: { modalData: function () { - return {symbols: scope.symbols.map(s => new Symbol(s))}; + return {symbols: scope.symbols.map(s => new AlphabetSymbol(s))}; } } }); diff --git a/main/src/main/webapp/src/js/entities/Symbol.js b/main/src/main/webapp/src/js/entities/AlphabetSymbol.js similarity index 76% rename from main/src/main/webapp/src/js/entities/Symbol.js rename to main/src/main/webapp/src/js/entities/AlphabetSymbol.js index ac89f0637..d7be87e0c 100644 --- a/main/src/main/webapp/src/js/entities/Symbol.js +++ b/main/src/main/webapp/src/js/entities/AlphabetSymbol.js @@ -18,33 +18,32 @@ import ActionService from '../services/ActionService'; const actionService = new ActionService(); -/** The model for the symbol create form */ -class SymbolFormModel { +/** The symbol model */ +export class AlphabetSymbol { /** * Constructor - * @param {string} name - The unique name of the symbol - * @param {string} abbreviation - The unique abbreviation of the symbol - * @param {number} group - The id of the group the symbol should be created in + * @param {object} obj - The object to create the symbol from */ - constructor(name = '', abbreviation = '', group = 0) { - this.name = name; - this.abbreviation = abbreviation; - this.group = group; - this.actions = []; - } -} + constructor(obj = {}) { + /** + * The unique name of the symbol + * @type {string} + */ + this.name = obj.name || null; -/** The symbol model */ -class Symbol extends SymbolFormModel { + /** + * The unique abbreviation of the symbol + * @type {string} + */ + this.abbreviation = obj.abbreviation || null; - /** - * Constructor - * @param {object} obj - The object to create the symbol from - */ - constructor(obj) { - super(obj.name, obj.abbreviation); + /** + * The id of the group the symbol should be created in + * @type {number} + */ + this.group = obj.group || 0; /** * The id of the symbol @@ -86,7 +85,7 @@ class Symbol extends SymbolFormModel { * The actions of the symbol * @type {Action[]} */ - this.actions = obj.actions.map(action => actionService.create(action)); + this.actions = obj.actions ? obj.actions.map(action => actionService.create(action)) : []; } /** @@ -119,6 +118,4 @@ class Symbol extends SymbolFormModel { actions: this.actions }; } -} - -export {SymbolFormModel, Symbol}; \ No newline at end of file +} \ No newline at end of file diff --git a/main/src/main/webapp/src/js/entities/LearnConfiguration.js b/main/src/main/webapp/src/js/entities/LearnConfiguration.js index f38e6fa79..0e8db00ff 100644 --- a/main/src/main/webapp/src/js/entities/LearnConfiguration.js +++ b/main/src/main/webapp/src/js/entities/LearnConfiguration.js @@ -71,7 +71,7 @@ class LearnConfiguration { /** * Adds a symbol to the configuration - * @param {Symbol} symbol - The symbol to add to the config + * @param {AlphabetSymbol} symbol - The symbol to add to the config */ addSymbol(symbol) { this.symbols.push(symbol.getIdRevisionPair()); @@ -79,7 +79,7 @@ class LearnConfiguration { /** * Sets the reset symbols for the configuration - * @param {Symbol} symbol - The reset symbol to use + * @param {AlphabetSymbol} symbol - The reset symbol to use */ setResetSymbol(symbol) { this.resetSymbol = symbol.getIdRevisionPair(); diff --git a/main/src/main/webapp/src/js/entities/Project.js b/main/src/main/webapp/src/js/entities/Project.js index 50ef3635f..7cb439893 100644 --- a/main/src/main/webapp/src/js/entities/Project.js +++ b/main/src/main/webapp/src/js/entities/Project.js @@ -14,32 +14,32 @@ * limitations under the License. */ -/** The form model for a Project */ -class ProjectFormModel { - - /** - * Constructor - * @param {string} name - The name of the project - * @param {string} baseUrl - The base URL of the project - * @param {string|null} description - The description of the project - * @constructor - */ - constructor(name = '', baseUrl = '', description = null) { - this.name = name; - this.baseUrl = baseUrl; - this.description = description; - } -} - /** The api result model for a Project */ -class Project extends ProjectFormModel { +export class Project { /** * Constructor * @param {object} obj - The object to create a project from */ - constructor(obj) { - super(obj.name, obj.baseUrl, obj.description); + constructor(obj = {}) { + + /** + * The name of the project + * @type {string} + */ + this.name = obj.name || null; + + /** + * The base URL of the project + * @type {string} + */ + this.baseUrl = obj.baseUrl || null; + + /** + * The description of the project + * @type {string} + */ + this.description = obj.description || null; /** * The id of the project @@ -53,6 +53,4 @@ class Project extends ProjectFormModel { */ this.user = obj.user; } -} - -export {ProjectFormModel, Project}; \ No newline at end of file +} \ No newline at end of file diff --git a/main/src/main/webapp/src/js/entities/SymbolGroup.js b/main/src/main/webapp/src/js/entities/SymbolGroup.js index 1ec7a0221..82c659e5c 100644 --- a/main/src/main/webapp/src/js/entities/SymbolGroup.js +++ b/main/src/main/webapp/src/js/entities/SymbolGroup.js @@ -14,38 +14,24 @@ * limitations under the License. */ -import {Symbol} from './Symbol'; +import {AlphabetSymbol} from './AlphabetSymbol'; /** - * The symbol group model for forms + * The model for symbol group */ -class SymbolGroupFormModel { +export class SymbolGroup { /** * Constructor - * @param {string} name - The name of the group + * @param {*} obj - The object to create the symbol group from */ - constructor(name = '') { + constructor(obj = {}) { /** * The name of the group * @type {string} */ - this.name = name; - } -} - -/** - * The model for symbol group - */ -class SymbolGroup extends SymbolGroupFormModel { - - /** - * Constructor - * @param {object} obj - The object to create the symbol group from - */ - constructor(obj) { - super(obj.name); + this.name = obj.name || null; /** * The id of the group @@ -67,10 +53,8 @@ class SymbolGroup extends SymbolGroupFormModel { /** * The visible symbols of the group - * @type {Symbol[]} + * @type {AlphabetSymbol[]} */ - this.symbols = obj.symbols ? obj.symbols.filter(s => !s.hidden).map(s => new Symbol(s)) : []; + this.symbols = obj.symbols ? obj.symbols.filter(s => !s.hidden).map(s => new AlphabetSymbol(s)) : []; } -} - -export {SymbolGroupFormModel, SymbolGroup}; \ No newline at end of file +} \ No newline at end of file diff --git a/main/src/main/webapp/src/js/entities/User.js b/main/src/main/webapp/src/js/entities/User.js index c2f34ceb9..766c5aaf3 100644 --- a/main/src/main/webapp/src/js/entities/User.js +++ b/main/src/main/webapp/src/js/entities/User.js @@ -14,28 +14,14 @@ * limitations under the License. */ -/** The model for user forms */ -class UserFormModel { - - /** - * Constructor - * @param {string} email - The email of the user - * @param {string} password - The unencrypted password of the user - */ - constructor(email = '', password = '') { - this.email = email; - this.password = password; - } -} - /** The model for user api results */ -class User { +export class User { /** * Constructor * @param {object} obj - The object to create a user from */ - constructor(obj) { + constructor(obj = {}) { /** * The id of the user @@ -55,6 +41,4 @@ class User { */ this.email = obj.email; } -} - -export {UserFormModel, User}; \ No newline at end of file +} \ No newline at end of file diff --git a/main/src/main/webapp/src/js/entities/actions/generalActions/ExecuteSymbolGeneralAction.js b/main/src/main/webapp/src/js/entities/actions/generalActions/ExecuteSymbolGeneralAction.js index 9fb73416e..36752367e 100644 --- a/main/src/main/webapp/src/js/entities/actions/generalActions/ExecuteSymbolGeneralAction.js +++ b/main/src/main/webapp/src/js/entities/actions/generalActions/ExecuteSymbolGeneralAction.js @@ -66,7 +66,7 @@ class ExecuteSymbolGeneralAction extends Action { /** * Update the revision of the symbol to execute - * @param {Symbol[]} symbols + * @param {AlphabetSymbol[]} symbols */ updateRevision(symbols) { for (let i = 0; i < symbols.length; i++) { @@ -80,7 +80,7 @@ class ExecuteSymbolGeneralAction extends Action { /** * Sets the symbol to execute * @param {string} name - The name of the symbol to execute - * @param {Symbol[]} symbols + * @param {AlphabetSymbol[]} symbols */ setSymbol(name, symbols) { this.getModel().maxRevision = null; diff --git a/main/src/main/webapp/src/js/entities/actions/restActions/CheckStatusRestAction.js b/main/src/main/webapp/src/js/entities/actions/restActions/CheckStatusRestAction.js index f4cfa3da6..2e8f1b790 100644 --- a/main/src/main/webapp/src/js/entities/actions/restActions/CheckStatusRestAction.js +++ b/main/src/main/webapp/src/js/entities/actions/restActions/CheckStatusRestAction.js @@ -19,9 +19,6 @@ import {actionType} from '../../../constants'; /** Checks for the status code (e.g. 404) in an HTTP response */ class CheckStatusRestAction extends Action { - static get type() { - return 'rest_checkStatus'; - } /** * Constructor @@ -29,7 +26,7 @@ class CheckStatusRestAction extends Action { * @constructor */ constructor(obj) { - super(CheckStatusRestAction.type, obj); + super(actionType.REST_CHECK_STATUS, obj); /** * The status code diff --git a/main/src/main/webapp/src/js/resources/ProjectResource.js b/main/src/main/webapp/src/js/resources/ProjectResource.js index c29823a5b..00c1d543d 100644 --- a/main/src/main/webapp/src/js/resources/ProjectResource.js +++ b/main/src/main/webapp/src/js/resources/ProjectResource.js @@ -41,7 +41,7 @@ class ProjectResource { /** * Creates a new project - * @param {ProjectFormModel} project - The project to create + * @param {Project} project - The project to create * @returns {*} */ create(project) { diff --git a/main/src/main/webapp/src/js/resources/SymbolGroupResource.js b/main/src/main/webapp/src/js/resources/SymbolGroupResource.js index 19bfc3dd1..da845161f 100644 --- a/main/src/main/webapp/src/js/resources/SymbolGroupResource.js +++ b/main/src/main/webapp/src/js/resources/SymbolGroupResource.js @@ -48,7 +48,7 @@ class SymbolGroupResource { * Creates a new symbol group * * @param {number} projectId - The id of the project of the symbol group - * @param {SymbolGroupFormModel} group - The object of the symbol group that should be created + * @param {SymbolGroup} group - The object of the symbol group that should be created * @returns {*} */ create(projectId, group) { diff --git a/main/src/main/webapp/src/js/resources/SymbolResource.js b/main/src/main/webapp/src/js/resources/SymbolResource.js index cbde3cd93..52158ecfb 100644 --- a/main/src/main/webapp/src/js/resources/SymbolResource.js +++ b/main/src/main/webapp/src/js/resources/SymbolResource.js @@ -14,7 +14,7 @@ * limitations under the License. */ -import {Symbol} from '../entities/Symbol'; +import {AlphabetSymbol} from '../entities/AlphabetSymbol'; /** * The resource that handles http requests to the API to do CRUD operations on symbols @@ -38,7 +38,7 @@ class SymbolResource { */ get(projectId, symbolId) { return this.$http.get(`rest/projects/${projectId}/symbols/${symbolId}`) - .then(response => new Symbol(response.data)); + .then(response => new AlphabetSymbol(response.data)); } /** @@ -51,7 +51,7 @@ class SymbolResource { getAll(projectId, includeHiddenSymbols = false) { const params = includeHiddenSymbols ? '?visibility=hidden' : ''; return this.$http.get(`rest/projects/${projectId}/symbols${params}`) - .then(response => response.data.map(s => new Symbol(s))); + .then(response => response.data.map(s => new AlphabetSymbol(s))); } /** @@ -65,7 +65,7 @@ class SymbolResource { getManyByIdRevisionPairs(projectId, idRevisionPairs) { const pairs = idRevisionPairs.map(pair => pair.id + ':' + pair.revision).join(','); return this.$http.get(`rest/projects/${projectId}/symbols/batch/${pairs}`) - .then(response => response.data.map(s => new Symbol(s))); + .then(response => response.data.map(s => new AlphabetSymbol(s))); } /** @@ -78,37 +78,37 @@ class SymbolResource { */ getRevisions(projectId, symbolId) { return this.$http.get(`rest/projects/${projectId}/symbols/${symbolId}/complete`) - .then(response => response.data.map(s => new Symbol(s))); + .then(response => response.data.map(s => new AlphabetSymbol(s))); } /** * Creates a new symbol * * @param {number} projectId - The id of the project the symbol should belong to - * @param {SymbolFormModel} symbol - The symbol that should be created + * @param {AlphabetSymbol} symbol - The symbol that should be created */ create(projectId, symbol) { return this.$http.post(`rest/projects/${projectId}/symbols`, symbol) - .then(response => new Symbol(response.data)); + .then(response => new AlphabetSymbol(response.data)); } /** * Creates many new symbols * * @param {number} projectId - The id of the project - * @param {Symbol[]} symbols - The symbols to create + * @param {AlphabetSymbol[]} symbols - The symbols to create * @returns {*} */ createMany(projectId, symbols) { return this.$http.post(`rest/projects/${projectId}/symbols/batch`, symbols) - .then(response => response.data.map(s => new Symbol(s))); + .then(response => response.data.map(s => new AlphabetSymbol(s))); } /** * Makes a PUT request to rest/projects/{projectId}/symbols[/batch]/{symbolId[s]}/moveTo/{groupId} in order to * move [a] symbol[s] to another group without creating a new revision * - * @param {Symbol|Symbol[]} symbols - The symbol[s] to be moved to another group + * @param {AlphabetSymbol|AlphabetSymbol[]} symbols - The symbol[s] to be moved to another group * @param {SymbolGroup} group - The id of the symbol group * @returns {HttpPromise} */ @@ -121,18 +121,18 @@ class SymbolResource { /** * Updates a single symbol and increments its revision number * - * @param {Symbol} symbol - The symbol to be updated + * @param {AlphabetSymbol} symbol - The symbol to be updated * @returns {*} */ update(symbol) { return this.$http.put(`rest/projects/${symbol.project}/symbols/${symbol.id}`, symbol) - .then(response => new Symbol(response.data)); + .then(response => new AlphabetSymbol(response.data)); } /** * Deletes a single symbol * - * @param {Symbol} symbol - The the symbol that should be deleted + * @param {AlphabetSymbol} symbol - The the symbol that should be deleted * @returns {*} */ remove(symbol) { @@ -142,7 +142,7 @@ class SymbolResource { /** * Removes many symbols * - * @param {Symbol[]} symbols + * @param {AlphabetSymbol[]} symbols * @returns {*} */ removeMany(symbols) { @@ -154,7 +154,7 @@ class SymbolResource { /** * Recovers a single symbol by setting its property 'visible' to true * - * @param {Symbol} symbol - The symbol to recover + * @param {AlphabetSymbol} symbol - The symbol to recover * @returns {*} */ recover(symbol) { @@ -164,7 +164,7 @@ class SymbolResource { /** * Recovers many symbols by setting their property 'visible' to true * - * @param {Symbol[]} symbols - The symbols to recover + * @param {AlphabetSymbol[]} symbols - The symbols to recover * @returns {*} */ recoverMany(symbols) { diff --git a/main/src/main/webapp/src/js/resources/UserResource.js b/main/src/main/webapp/src/js/resources/UserResource.js index 78e1fe709..38d2c2f1a 100644 --- a/main/src/main/webapp/src/js/resources/UserResource.js +++ b/main/src/main/webapp/src/js/resources/UserResource.js @@ -81,23 +81,31 @@ class UserResource { } /** - * Creates a new user + * Creates a new user. * - * @param {UserFormModel} user - The user to create + * @param {string} email + * @param {string} password * @returns {*} - A promise */ - create(user) { - return this.$http.post('rest/users', user); + create(email, password) { + return this.$http.post('rest/users', { + email: email, + password: password + }); } /** * Logs in a user * - * @param {User} user - The user to login - * @returns {*} - A promise that contains the jwt + * @param {string} email + * @param {string} password + * @returns {*} - A promise */ - login(user) { - return this.$http.post('rest/users/login', user); + login(email, password) { + return this.$http.post('rest/users/login', { + email: email, + password: password + }); } /** diff --git a/main/src/main/webapp/tests/unit/components/forms/projectCreateForm.tests.js b/main/src/main/webapp/tests/unit/components/forms/projectCreateForm.tests.js index 53fa0367f..43779f328 100644 --- a/main/src/main/webapp/tests/unit/components/forms/projectCreateForm.tests.js +++ b/main/src/main/webapp/tests/unit/components/forms/projectCreateForm.tests.js @@ -1,4 +1,4 @@ -import {Project, ProjectFormModel} from '../../../../src/js/entities/Project'; +import {Project} from '../../../../src/js/entities/Project'; import {events} from '../../../../src/js/constants'; describe('projectCreateForm', () => { @@ -30,7 +30,7 @@ describe('projectCreateForm', () => { })); it('should start with an empty project model', () => { - expect(controller.project).toEqual(new ProjectFormModel()); + expect(controller.project).toEqual(new Project()); }); it('should create a project, display a message, emit an event and reset the form', () => { @@ -51,7 +51,7 @@ describe('projectCreateForm', () => { expect(ProjectResource.create).toHaveBeenCalled(); expect(ToastService.success).toHaveBeenCalled(); expect(EventBus.emit).toHaveBeenCalledWith(events.PROJECT_CREATED, {project: project}); - expect(controller.project).toEqual(new ProjectFormModel()); + expect(controller.project).toEqual(new Project()); }); it('should display a fail message if the project could not be created', () => { diff --git a/main/src/main/webapp/tests/unit/components/forms/userLoginForm.tests.js b/main/src/main/webapp/tests/unit/components/forms/userLoginForm.tests.js index e00252886..dfc261c9d 100644 --- a/main/src/main/webapp/tests/unit/components/forms/userLoginForm.tests.js +++ b/main/src/main/webapp/tests/unit/components/forms/userLoginForm.tests.js @@ -1,5 +1,3 @@ -import {events} from '../../../../src/js/constants'; - describe('userLoginForm', () => { let $rootScope, $compile, $q, UserResource, ToastService, $state, EventBus, SessionService; let renderedElement, controller; @@ -45,11 +43,12 @@ describe('userLoginForm', () => { spyOn(UserResource, 'login').and.returnValue(deferred.promise); deferred.reject({data: null}); - controller.user = USER; + controller.email = USER.email; + controller.password = USER.password; controller.login(); $rootScope.$digest(); - expect(UserResource.login).toHaveBeenCalledWith(USER); + expect(UserResource.login).toHaveBeenCalledWith(USER.email, USER.password); expect(ToastService.danger).toHaveBeenCalled(); }); @@ -62,7 +61,8 @@ describe('userLoginForm', () => { spyOn(EventBus, 'emit').and.callThrough(); spyOn(SessionService, 'saveUser').and.callThrough(); - controller.user = USER; + controller.email = USER.email; + controller.password = USER.password; controller.login(); $rootScope.$digest(); diff --git a/main/src/main/webapp/tests/unit/components/forms/userRegisterForm.tests.js b/main/src/main/webapp/tests/unit/components/forms/userRegisterForm.tests.js index cfe9e56c0..cf2ada390 100644 --- a/main/src/main/webapp/tests/unit/components/forms/userRegisterForm.tests.js +++ b/main/src/main/webapp/tests/unit/components/forms/userRegisterForm.tests.js @@ -1,5 +1,3 @@ -import {UserFormModel} from '../../../../src/js/entities/User'; - describe('userRegisterForm', () => { let $rootScope, $compile, $q, UserResource, ToastService; let renderedElement, controller; @@ -38,11 +36,12 @@ describe('userRegisterForm', () => { spyOn(UserResource, 'create').and.returnValue(deferred.promise); deferred.reject({data: {message: null}}); - controller.user = USER; + controller.email = USER.email; + controller.password = USER.password; controller.register(); $rootScope.$digest(); - expect(UserResource.create).toHaveBeenCalledWith(USER); + expect(UserResource.create).toHaveBeenCalledWith(USER.email, USER.password); expect(ToastService.danger).toHaveBeenCalled(); }); @@ -53,11 +52,13 @@ describe('userRegisterForm', () => { spyOn(ToastService, 'success').and.callThrough(); - controller.user = USER; + controller.email = USER.email; + controller.password = USER.password; controller.register(); $rootScope.$digest(); expect(ToastService.success).toHaveBeenCalled(); - expect(controller.user).toEqual(new UserFormModel()); + expect(controller.email).toBeNull() + expect(controller.password).toBeNull() }); }); \ No newline at end of file diff --git a/main/src/main/webapp/tests/unit/components/views/symbolsHistoryView.tests.js b/main/src/main/webapp/tests/unit/components/views/symbolsHistoryView.tests.js index 8aa6e53cd..4b248c8ab 100644 --- a/main/src/main/webapp/tests/unit/components/views/symbolsHistoryView.tests.js +++ b/main/src/main/webapp/tests/unit/components/views/symbolsHistoryView.tests.js @@ -1,4 +1,4 @@ -import {Symbol} from '../../../../src/js/entities/Symbol'; +import {AlphabetSymbol} from '../../../../src/js/entities/AlphabetSymbol'; describe('symbolsHistoryView', () => { const SYMBOL_ID = 1; @@ -18,7 +18,7 @@ describe('symbolsHistoryView', () => { ToastService = $injector.get('ToastService'); ErrorService = $injector.get('ErrorService'); - symbols = ENTITIES.symbols.map(s => new Symbol(s)); + symbols = ENTITIES.symbols.map(s => new AlphabetSymbol(s)); project = ENTITIES.projects[0]; user = ENTITIES.users[0]; @@ -48,7 +48,7 @@ describe('symbolsHistoryView', () => { spyOn(SymbolResource, 'getRevisions').and.returnValue(deferred.promise); deferred.resolve(symbols); - const copy = symbols.map(s => new Symbol(s)); + const copy = symbols.map(s => new AlphabetSymbol(s)); createComponent(); $rootScope.$digest(); diff --git a/main/src/main/webapp/tests/unit/components/views/symbolsImportView.tests.js b/main/src/main/webapp/tests/unit/components/views/symbolsImportView.tests.js index 1be204380..bf21fc304 100644 --- a/main/src/main/webapp/tests/unit/components/views/symbolsImportView.tests.js +++ b/main/src/main/webapp/tests/unit/components/views/symbolsImportView.tests.js @@ -1,4 +1,4 @@ -import {Symbol} from '../../../../src/js/entities/Symbol'; +import {AlphabetSymbol} from '../../../../src/js/entities/AlphabetSymbol'; import {events} from '../../../../src/js/constants'; describe('symbolsImportView', () => { @@ -56,11 +56,11 @@ describe('symbolsImportView', () => { it('should load all symbols from a file', () => { createComponent(); - const symbols = ENTITIES.symbols.map(s => new Symbol(s)); + const symbols = ENTITIES.symbols.map(s => new AlphabetSymbol(s)); const json = angular.toJson(symbols); controller.fileLoaded(json); controller.symbols.forEach((s,i) => { - expect(s instanceof Symbol).toBe(true); + expect(s instanceof AlphabetSymbol).toBe(true); expect(s.id).toBeDefined(); expect(s.name).toEqual(symbols[i].name); expect(s.abbreviation).toEqual(symbols[i].abbreviation); diff --git a/main/src/main/webapp/tests/unit/components/views/symbolsTrashView.tests.js b/main/src/main/webapp/tests/unit/components/views/symbolsTrashView.tests.js index 3a6298d0f..2a9078931 100644 --- a/main/src/main/webapp/tests/unit/components/views/symbolsTrashView.tests.js +++ b/main/src/main/webapp/tests/unit/components/views/symbolsTrashView.tests.js @@ -1,4 +1,4 @@ -import {Symbol} from '../../../../src/js/entities/Symbol'; +import {AlphabetSymbol} from '../../../../src/js/entities/AlphabetSymbol'; describe('symbolsTrashView', () => { @@ -16,7 +16,7 @@ describe('symbolsTrashView', () => { ToastService = $injector.get('ToastService'); - symbols = ENTITIES.symbols.map(s => new Symbol(s)); + symbols = ENTITIES.symbols.map(s => new AlphabetSymbol(s)); project = ENTITIES.projects[0]; user = ENTITIES.users[0]; diff --git a/main/src/main/webapp/tests/unit/directives/modals/symbolCreateModal.tests.js b/main/src/main/webapp/tests/unit/directives/modals/symbolCreateModal.tests.js index 30c7d25c0..a072078fd 100644 --- a/main/src/main/webapp/tests/unit/directives/modals/symbolCreateModal.tests.js +++ b/main/src/main/webapp/tests/unit/directives/modals/symbolCreateModal.tests.js @@ -1,4 +1,4 @@ -import {SymbolFormModel, Symbol} from '../../../../src/js/entities/Symbol'; +import {AlphabetSymbol} from '../../../../src/js/entities/AlphabetSymbol'; import {events} from '../../../../src/js/constants'; import {SymbolCreateModalController} from '../../../../src/js/directives/modals/symbolCreateModalHandle'; @@ -83,7 +83,7 @@ describe('symbolCreateModal', () => { it('should create a symbol and not close the modal', () => { createController(); const deferred = $q.defer(); - const symbol = new Symbol(ENTITIES.symbols[0]); + const symbol = new AlphabetSymbol(ENTITIES.symbols[0]); spyOn(SymbolResource, 'create').and.returnValue(deferred.promise); spyOn(EventBus, 'emit').and.callThrough(); deferred.resolve(symbol); diff --git a/main/src/main/webapp/tests/unit/directives/modals/symbolEditModal.tests.js b/main/src/main/webapp/tests/unit/directives/modals/symbolEditModal.tests.js index 26005a3f3..080756f77 100644 --- a/main/src/main/webapp/tests/unit/directives/modals/symbolEditModal.tests.js +++ b/main/src/main/webapp/tests/unit/directives/modals/symbolEditModal.tests.js @@ -1,5 +1,5 @@ import {SymbolEditModalController} from '../../../../src/js/directives/modals/symbolEditModalHandle'; -import {Symbol} from '../../../../src/js/entities/Symbol'; +import {AlphabetSymbol} from '../../../../src/js/entities/AlphabetSymbol'; describe('symbolEditModal', () => { let $controller, $compile, $q, $rootScope, SymbolResource, ToastService, EventBus, $uibModal; @@ -25,7 +25,7 @@ describe('symbolEditModal', () => { }; data = { - symbol: new Symbol(ENTITIES.symbols[0]), + symbol: new AlphabetSymbol(ENTITIES.symbols[0]), updateOnServer: true }; })); diff --git a/main/src/main/webapp/tests/unit/directives/modals/symbolGroupCreateModal.tests.js b/main/src/main/webapp/tests/unit/directives/modals/symbolGroupCreateModal.tests.js index a044352cf..7a9a441a8 100644 --- a/main/src/main/webapp/tests/unit/directives/modals/symbolGroupCreateModal.tests.js +++ b/main/src/main/webapp/tests/unit/directives/modals/symbolGroupCreateModal.tests.js @@ -1,5 +1,5 @@ import {Project} from '../../../../src/js/entities/Project'; -import {SymbolGroupFormModel} from '../../../../src/js/entities/SymbolGroup'; +import {SymbolGroup} from '../../../../src/js/entities/SymbolGroup'; import {events} from '../../../../src/js/constants'; import {SymbolGroupCreateModalController} from '../../../../src/js/directives/modals/symbolGroupCreateModalHandle'; @@ -64,7 +64,7 @@ describe('SymbolGroupCreateModalController', () => { it('should initialize the controller correctly', () => { createController(); expect(controller.project).toEqual(project); - expect(controller.group).toEqual(new SymbolGroupFormModel()); + expect(controller.group).toEqual(new SymbolGroup()); expect(controller.errorMsg).toBeNull(); }); @@ -80,7 +80,7 @@ describe('SymbolGroupCreateModalController', () => { spyOn(EventBus, 'emit'); spyOn(ToastService, 'success'); - const group = new SymbolGroupFormModel(ENTITIES.groups[0]); + const group = new SymbolGroup(ENTITIES.groups[0]); deferred.resolve(ENTITIES.groups[0]); controller.group = group; @@ -99,7 +99,7 @@ describe('SymbolGroupCreateModalController', () => { spyOn(SymbolGroupResource, 'create').and.returnValue(deferred.promise); const message = 'failed'; - const group = new SymbolGroupFormModel(ENTITIES.groups[0]); + const group = new SymbolGroup(ENTITIES.groups[0]); deferred.reject({data: {message: message}}); controller.group = group; diff --git a/main/src/main/webapp/tests/unit/entities/LearnConfiguration.tests.js b/main/src/main/webapp/tests/unit/entities/LearnConfiguration.tests.js index 641ae42cc..3a46053f4 100644 --- a/main/src/main/webapp/tests/unit/entities/LearnConfiguration.tests.js +++ b/main/src/main/webapp/tests/unit/entities/LearnConfiguration.tests.js @@ -1,4 +1,4 @@ -import {Symbol} from '../../../src/js/entities/Symbol'; +import {AlphabetSymbol} from '../../../src/js/entities/AlphabetSymbol'; import {LearnConfiguration} from '../../../src/js/entities/LearnConfiguration'; describe('LearnConfiguration', () => { @@ -30,7 +30,7 @@ describe('LearnConfiguration', () => { it('should add a symbol to the symbols list as id revision pair', () => { const config = new LearnConfiguration(); - const symbol = new Symbol(ENTITIES.symbols[0]); + const symbol = new AlphabetSymbol(ENTITIES.symbols[0]); const pair = symbol.getIdRevisionPair(); const pre = config.symbols.length; @@ -41,7 +41,7 @@ describe('LearnConfiguration', () => { it('should set a symbols as id revision pair as reset symbol', () => { const config = new LearnConfiguration(); - const symbol = new Symbol(ENTITIES.symbols[0]); + const symbol = new AlphabetSymbol(ENTITIES.symbols[0]); const pair = symbol.getIdRevisionPair(); config.setResetSymbol(symbol); diff --git a/main/src/main/webapp/tests/unit/entities/Project.tests.js b/main/src/main/webapp/tests/unit/entities/Project.tests.js index 14b948867..4b73aac7a 100644 --- a/main/src/main/webapp/tests/unit/entities/Project.tests.js +++ b/main/src/main/webapp/tests/unit/entities/Project.tests.js @@ -1,24 +1,4 @@ -import {Project, ProjectFormModel} from '../../../src/js/entities/Project'; - -describe('ProjectFormModel', () => { - beforeEach(angular.mock.module('ALEX')); - - it('should correctly create a new ProjectFormModel', () => { - let project = new ProjectFormModel(); - expect(Object.keys(project).length).toEqual(3); - expect(project.description).toBeNull(); - expect(project.name).toEqual(''); - expect(project.baseUrl).toEqual(''); - - const p = ENTITIES.projects[0]; - project = new ProjectFormModel(p.name, p.baseUrl, p.description); - expect(Object.keys(project).length).toEqual(3); - - for (let prop in project) { - expect(project[prop]).toEqual(p[prop]); - } - }); -}); +import {Project} from '../../../src/js/entities/Project'; describe('Project', () => { beforeEach(angular.mock.module('ALEX')); diff --git a/main/src/main/webapp/tests/unit/entities/Symbol.tests.js b/main/src/main/webapp/tests/unit/entities/Symbol.tests.js index 0a4b0f728..04e9c2e62 100644 --- a/main/src/main/webapp/tests/unit/entities/Symbol.tests.js +++ b/main/src/main/webapp/tests/unit/entities/Symbol.tests.js @@ -1,31 +1,14 @@ -import {Symbol, SymbolFormModel} from '../../../src/js/entities/Symbol'; +import {AlphabetSymbol} from '../../../src/js/entities/AlphabetSymbol'; import Action from '../../../src/js/entities/actions/Action'; -describe('SymbolFormModel', () => { - beforeEach(angular.mock.module('ALEX')); - - it('should correctly create a new SymbolFormModel', () => { - const expected = { - name: '', - abbreviation: '', - group: 0, - actions: [] - }; - const model = new SymbolFormModel(); - - expect(angular.toJson(model)).toEqual(angular.toJson(expected)); - }) -}); - - -describe('Symbol', () => { +describe('AlphabetSymbol', () => { let ActionService; let symbol; beforeEach(angular.mock.module('ALEX')); beforeEach(angular.mock.inject(($injector) => { ActionService = $injector.get('ActionService'); - symbol = new Symbol(ENTITIES.symbols[0]); + symbol = new AlphabetSymbol(ENTITIES.symbols[0]); })); it('should count all enabled actions', () => { diff --git a/main/src/main/webapp/tests/unit/entities/SymbolGroup.tests.js b/main/src/main/webapp/tests/unit/entities/SymbolGroup.tests.js index c164e15ff..79bfcdd8d 100644 --- a/main/src/main/webapp/tests/unit/entities/SymbolGroup.tests.js +++ b/main/src/main/webapp/tests/unit/entities/SymbolGroup.tests.js @@ -1,19 +1,5 @@ -import {SymbolGroup, SymbolGroupFormModel} from '../../../src/js/entities/SymbolGroup'; -import {Symbol} from '../../../src/js/entities/Symbol'; - -describe('SymbolGroupFormModel', () => { - beforeEach(angular.mock.module('ALEX')); - - it('should correctly create a new SymbolGroupFormModel', () => { - let group = new SymbolGroupFormModel(); - expect(Object.keys(group).length).toEqual(1); - expect(group.name).toEqual(''); - - group = new SymbolGroupFormModel('newGroup'); - expect(Object.keys(group).length).toEqual(1); - expect(group.name).toEqual('newGroup'); - }); -}); +import {SymbolGroup} from '../../../src/js/entities/SymbolGroup'; +import {AlphabetSymbol} from '../../../src/js/entities/AlphabetSymbol'; describe('SymbolGroup', () => { beforeEach(angular.mock.module('ALEX')); @@ -24,6 +10,6 @@ describe('SymbolGroup', () => { expect(Object.keys(group).length).toEqual(5); - group.symbols.forEach(s => expect(s instanceof Symbol).toBe(true)); + group.symbols.forEach(s => expect(s instanceof AlphabetSymbol).toBe(true)); }) }); \ No newline at end of file diff --git a/main/src/main/webapp/tests/unit/entities/User.tests.js b/main/src/main/webapp/tests/unit/entities/User.tests.js index 7dbaa456a..c1d86cda5 100644 --- a/main/src/main/webapp/tests/unit/entities/User.tests.js +++ b/main/src/main/webapp/tests/unit/entities/User.tests.js @@ -1,21 +1,5 @@ import {User, UserFormModel} from '../../../src/js/entities/User'; -describe('UserFormModel', () => { - beforeEach(angular.mock.module('ALEX')); - - it('should correctly create a new UserFormModel', () => { - let user = new UserFormModel(); - expect(Object.keys(user).length).toEqual(2); - expect(user.email).toEqual(''); - expect(user.password).toEqual(''); - - user = new UserFormModel('email', 'password'); - expect(Object.keys(user).length).toEqual(2); - expect(user.email).toEqual('email'); - expect(user.password).toEqual('password'); - }); -}); - describe('User', () => { beforeEach(angular.mock.module('ALEX')); diff --git a/main/src/main/webapp/tests/unit/resources/ProjectResource.tests.js b/main/src/main/webapp/tests/unit/resources/ProjectResource.tests.js index a52de02ef..29390b796 100644 --- a/main/src/main/webapp/tests/unit/resources/ProjectResource.tests.js +++ b/main/src/main/webapp/tests/unit/resources/ProjectResource.tests.js @@ -1,4 +1,4 @@ -import {Project, ProjectFormModel} from '../../../src/js/entities/Project'; +import {Project} from '../../../src/js/entities/Project'; describe('ProjectResource', () => { let $http; @@ -32,7 +32,7 @@ describe('ProjectResource', () => { it('should create project a project from a form model an return an instance of the created project', () => { spyOn(ProjectResource.$http, 'post').and.callThrough(); - const projectToCreate = new ProjectFormModel(); + const projectToCreate = new Project(); projectToCreate.name = 'project1'; projectToCreate.baseUrl = 'http://localhost'; projectToCreate.description = null; diff --git a/main/src/main/webapp/tests/unit/resources/SymbolGroupResource.tests.js b/main/src/main/webapp/tests/unit/resources/SymbolGroupResource.tests.js index d3afabb41..89091be96 100644 --- a/main/src/main/webapp/tests/unit/resources/SymbolGroupResource.tests.js +++ b/main/src/main/webapp/tests/unit/resources/SymbolGroupResource.tests.js @@ -1,4 +1,4 @@ -import {SymbolGroup, SymbolGroupFormModel} from '../../../src/js/entities/SymbolGroup'; +import {SymbolGroup} from '../../../src/js/entities/SymbolGroup'; describe('SymbolGroupResource', () => { let $http; @@ -49,7 +49,7 @@ describe('SymbolGroupResource', () => { }); it('should create a group from a form model and return an instance of the created group', () => { - const model = new SymbolGroupFormModel(); + const model = new SymbolGroup(); spyOn(SymbolGroupResource.$http, 'post').and.callThrough(); $httpBackend.whenPOST(uri).respond(201, ENTITIES.groups[0]); diff --git a/main/src/main/webapp/tests/unit/resources/SymbolResource.tests.js b/main/src/main/webapp/tests/unit/resources/SymbolResource.tests.js index dca9754c6..2f1cff92f 100644 --- a/main/src/main/webapp/tests/unit/resources/SymbolResource.tests.js +++ b/main/src/main/webapp/tests/unit/resources/SymbolResource.tests.js @@ -1,4 +1,4 @@ -import {Symbol, SymbolFormModel} from '../../../src/js/entities/Symbol'; +import {AlphabetSymbol} from '../../../src/js/entities/AlphabetSymbol'; describe('SymbolResource', () => { let $http, $httpBackend; @@ -29,7 +29,7 @@ describe('SymbolResource', () => { expect($http.get).toHaveBeenCalledWith(uri); promise.then((s) => { - expect(s instanceof Symbol).toBe(true) + expect(s instanceof AlphabetSymbol).toBe(true) }) }); @@ -44,7 +44,7 @@ describe('SymbolResource', () => { expect($http.get).toHaveBeenCalledWith(uri); promise.then((symbols) => { - symbols.forEach(s => expect(s instanceof Symbol).toBe(true)); + symbols.forEach(s => expect(s instanceof AlphabetSymbol).toBe(true)); }) }); @@ -52,14 +52,14 @@ describe('SymbolResource', () => { spyOn($http, 'post').and.callThrough(); const uri = `rest/projects/${project.id}/symbols`; - const symbol = new SymbolFormModel(); + const symbol = new AlphabetSymbol(); $httpBackend.whenPOST(uri).respond(201, ENTITIES.symbols[0]); const promise = SymbolResource.create(project.id, symbol); $httpBackend.flush(); expect($http.post).toHaveBeenCalledWith(uri, symbol); promise.then((symbol) => { - expect(symbol instanceof Symbol).toBe(true); + expect(symbol instanceof AlphabetSymbol).toBe(true); }) }); @@ -67,14 +67,14 @@ describe('SymbolResource', () => { spyOn($http, 'post').and.callThrough(); const uri = `rest/projects/${project.id}/symbols/batch`; - const symbols = [new SymbolFormModel(), new SymbolFormModel(), new SymbolFormModel()]; + const symbols = [new AlphabetSymbol(), new AlphabetSymbol(), new AlphabetSymbol()]; $httpBackend.whenPOST(uri).respond(201, ENTITIES.symbols); const promise = SymbolResource.createMany(project.id, symbols); $httpBackend.flush(); expect($http.post).toHaveBeenCalledWith(uri, symbols); promise.then((symbols) => { - symbols.forEach(s => expect(s instanceof Symbol).toBe(true)); + symbols.forEach(s => expect(s instanceof AlphabetSymbol).toBe(true)); }) }); @@ -94,7 +94,7 @@ describe('SymbolResource', () => { }); it('should get a list of symbols by given id revision pairs', () => { - const pairs = ENTITIES.symbols.map(s => new Symbol(s).getIdRevisionPair()); + const pairs = ENTITIES.symbols.map(s => new AlphabetSymbol(s).getIdRevisionPair()); const concatenatedPairs = pairs.map(p => p.id + ':' + p.revision).join(','); const projectId = ENTITIES.symbols[0].project; const uri = `rest/projects/${projectId}/symbols/batch/${concatenatedPairs}`; @@ -107,7 +107,7 @@ describe('SymbolResource', () => { expect($http.get).toHaveBeenCalledWith(uri); promise.then(symbols => { symbols.forEach(s => { - expect(s instanceof Symbol).toBe(true) + expect(s instanceof AlphabetSymbol).toBe(true) }); }) }); @@ -123,7 +123,7 @@ describe('SymbolResource', () => { expect($http.get).toHaveBeenCalledWith(uri); promise.then((symbols) => { - symbols.forEach(s => expect(s instanceof Symbol).toBe(true)); + symbols.forEach(s => expect(s instanceof AlphabetSymbol).toBe(true)); }); }); @@ -138,14 +138,14 @@ describe('SymbolResource', () => { expect($http.put).toHaveBeenCalledWith(uri, symbol); promise.then((s) => { - expect(s instanceof Symbol).toBe(true) + expect(s instanceof AlphabetSymbol).toBe(true) }) }); it('should remove a single symbol', () => { spyOn($http, 'post').and.callThrough(); - const symbol = new Symbol(ENTITIES.symbols[0]); + const symbol = new AlphabetSymbol(ENTITIES.symbols[0]); const uri = `rest/projects/${project.id}/symbols/${symbol.id}/hide`; $httpBackend.whenPOST(uri).respond(200, {}); @@ -159,7 +159,7 @@ describe('SymbolResource', () => { it('should remove many symbols', () => { spyOn($http, 'post').and.callThrough(); - const symbols = ENTITIES.symbols.map(s => new Symbol(s)); + const symbols = ENTITIES.symbols.map(s => new AlphabetSymbol(s)); const ids = symbols.map(s => s.id).join(','); const uri = `rest/projects/${project.id}/symbols/batch/${ids}/hide`; @@ -174,7 +174,7 @@ describe('SymbolResource', () => { it('should recover a single symbols', () => { spyOn($http, 'post').and.callThrough(); - const symbol = new Symbol(ENTITIES.symbols[0]); + const symbol = new AlphabetSymbol(ENTITIES.symbols[0]); const uri = `rest/projects/${project.id}/symbols/${symbol.id}/show`; $httpBackend.whenPOST(uri).respond(200, {}); @@ -188,7 +188,7 @@ describe('SymbolResource', () => { it('should recover many symbols', () => { spyOn($http, 'post').and.callThrough(); - const symbols = ENTITIES.symbols.map(s => new Symbol(s)); + const symbols = ENTITIES.symbols.map(s => new AlphabetSymbol(s)); const ids = symbols.map(s => s.id).join(','); const uri = `rest/projects/${project.id}/symbols/batch/${ids}/show`; diff --git a/main/src/main/webapp/tests/unit/resources/UserResource.tests.js b/main/src/main/webapp/tests/unit/resources/UserResource.tests.js index 71bb7bdab..8ffcb47ff 100644 --- a/main/src/main/webapp/tests/unit/resources/UserResource.tests.js +++ b/main/src/main/webapp/tests/unit/resources/UserResource.tests.js @@ -1,4 +1,4 @@ -import {User, UserFormModel} from '../../../src/js/entities/User'; +import {User} from "../../../src/js/entities/User"; describe('UserResource', () => { let UserResource, $http, $q, $httpBackend; @@ -64,14 +64,16 @@ describe('UserResource', () => { it('should create a new user', () => { const uri = `rest/users`; - const user = new UserFormModel('mail', 'pw'); spyOn($http, 'post').and.callThrough(); $httpBackend.whenPOST(uri).respond(201, ENTITIES.users[0]); - const promise = UserResource.create(user); + const promise = UserResource.create('mail', 'pw'); $httpBackend.flush(); - expect(UserResource.$http.post).toHaveBeenCalledWith(uri, user); + expect(UserResource.$http.post).toHaveBeenCalledWith(uri, { + email: 'mail', + password: 'pw' + }); promise.then((u) => { expect(u instanceof User); }) @@ -82,10 +84,13 @@ describe('UserResource', () => { spyOn($http, 'post').and.callThrough(); $httpBackend.whenPOST(uri).respond(200, user); - const promise = UserResource.login(user); + const promise = UserResource.login('mail', 'pw'); $httpBackend.flush(); - expect(UserResource.$http.post).toHaveBeenCalledWith(uri, user); + expect(UserResource.$http.post).toHaveBeenCalledWith(uri, { + email: 'mail', + password: 'pw' + }); expect(promise.then).toBeDefined(); }); From e25027b7a8613199648245ca3460a66c25ad8e16 Mon Sep 17 00:00:00 2001 From: Alexander Bainczyk Date: Sun, 24 Apr 2016 12:50:15 +0200 Subject: [PATCH 12/44] updated html-unit-driver and changed its browser version to Browserversion.INTERNET_EXPLORER_11 which now seems to work with javascript fixed two bugs in the frontend: 1. Symbol groups could not be edited because the button wasn't displayed 2. Failed learn processes were not marked as failed --- main/pom.xml | 4 ++-- .../learnlib/alex/core/learner/connectors/WebBrowser.java | 2 +- main/src/main/webapp/index.html | 2 +- .../main/webapp/src/html/components/learn-result-panel.html | 4 +--- .../main/webapp/src/js/components/symbolGroupListItem.js | 2 +- main/src/main/webapp/src/js/entities/LearnResult.js | 6 ++++++ 6 files changed, 12 insertions(+), 8 deletions(-) diff --git a/main/pom.xml b/main/pom.xml index 13e458593..0497d074c 100644 --- a/main/pom.xml +++ b/main/pom.xml @@ -172,8 +172,8 @@ org.seleniumhq.selenium - htmlunit-driver - 2.20 + selenium-htmlunit-driver + 2.52.0 xml-apis diff --git a/main/src/main/java/de/learnlib/alex/core/learner/connectors/WebBrowser.java b/main/src/main/java/de/learnlib/alex/core/learner/connectors/WebBrowser.java index 77123fb4c..a494332c1 100644 --- a/main/src/main/java/de/learnlib/alex/core/learner/connectors/WebBrowser.java +++ b/main/src/main/java/de/learnlib/alex/core/learner/connectors/WebBrowser.java @@ -75,7 +75,7 @@ public WebDriver getWebDriver() { try { if (this == HTMLUNITDRIVER) { HtmlUnitDriver driver = (HtmlUnitDriver) webDriverClass.getConstructor(BrowserVersion.class) - .newInstance(BrowserVersion.FIREFOX_38); + .newInstance(BrowserVersion.INTERNET_EXPLORER_11); enableJavaScript(driver); return driver; } else { diff --git a/main/src/main/webapp/index.html b/main/src/main/webapp/index.html index 34589fdc0..919374d98 100644 --- a/main/src/main/webapp/index.html +++ b/main/src/main/webapp/index.html @@ -15,7 +15,7 @@ - + \ No newline at end of file diff --git a/main/src/main/webapp/src/html/components/learn-result-panel.html b/main/src/main/webapp/src/html/components/learn-result-panel.html index a7bb3acc6..0a015e39a 100644 --- a/main/src/main/webapp/src/html/components/learn-result-panel.html +++ b/main/src/main/webapp/src/html/components/learn-result-panel.html @@ -83,12 +83,10 @@ Hypothesis
-
- -
+
diff --git a/main/src/main/webapp/src/js/components/symbolGroupListItem.js b/main/src/main/webapp/src/js/components/symbolGroupListItem.js index d8fdfea5f..97aef8508 100644 --- a/main/src/main/webapp/src/js/components/symbolGroupListItem.js +++ b/main/src/main/webapp/src/js/components/symbolGroupListItem.js @@ -52,7 +52,7 @@ const symbolGroupListItem = { Symbols
-
+
diff --git a/main/src/main/webapp/src/js/entities/LearnResult.js b/main/src/main/webapp/src/js/entities/LearnResult.js index 9804124c8..99efcaff6 100644 --- a/main/src/main/webapp/src/js/entities/LearnResult.js +++ b/main/src/main/webapp/src/js/entities/LearnResult.js @@ -88,6 +88,12 @@ class LearnResult { */ this.user = obj.user; + /** + * If the learner encounted an error + * @type {boolean} + */ + this.error = obj.error; + // convert ns to ms this.statistics.duration = Math.ceil(this.statistics.duration / 1000000); From 829ba2d5c2cd02d2509120671e0e4ba62065167a Mon Sep 17 00:00:00 2001 From: Alexander Bainczyk Date: Sun, 24 Apr 2016 13:40:51 +0200 Subject: [PATCH 13/44] update the title tag when the view changes added some ids to some forms --- .../html/components/project-create-form.html | 2 +- .../src/js/components/forms/userLoginForm.js | 2 +- .../js/components/forms/userRegisterForm.js | 2 +- .../webapp/src/js/entities/LearnResult.js | 6 +++ main/src/main/webapp/src/js/routes.js | 44 ++++++++++--------- 5 files changed, 33 insertions(+), 23 deletions(-) diff --git a/main/src/main/webapp/src/html/components/project-create-form.html b/main/src/main/webapp/src/html/components/project-create-form.html index 93bc7cc3e..2d39c2d5e 100644 --- a/main/src/main/webapp/src/html/components/project-create-form.html +++ b/main/src/main/webapp/src/html/components/project-create-form.html @@ -1,4 +1,4 @@ - +
diff --git a/main/src/main/webapp/src/js/components/forms/userLoginForm.js b/main/src/main/webapp/src/js/components/forms/userLoginForm.js index 4262b83ce..00e4057b5 100644 --- a/main/src/main/webapp/src/js/components/forms/userLoginForm.js +++ b/main/src/main/webapp/src/js/components/forms/userLoginForm.js @@ -80,7 +80,7 @@ const userLoginForm = { controller: UserLoginForm, controllerAs: 'vm', template: ` - +
diff --git a/main/src/main/webapp/src/js/components/forms/userRegisterForm.js b/main/src/main/webapp/src/js/components/forms/userRegisterForm.js index 2a48df4b1..26f1866de 100644 --- a/main/src/main/webapp/src/js/components/forms/userRegisterForm.js +++ b/main/src/main/webapp/src/js/components/forms/userRegisterForm.js @@ -62,7 +62,7 @@ const userRegisterForm = { controller: UserRegisterFrom, controllerAs: 'vm', template: ` - +
diff --git a/main/src/main/webapp/src/js/entities/LearnResult.js b/main/src/main/webapp/src/js/entities/LearnResult.js index 99efcaff6..05a3492db 100644 --- a/main/src/main/webapp/src/js/entities/LearnResult.js +++ b/main/src/main/webapp/src/js/entities/LearnResult.js @@ -94,6 +94,12 @@ class LearnResult { */ this.error = obj.error; + /** + * The description of the error that occurred + * @type {string} + */ + this.errorText = obj.errorText; + // convert ns to ms this.statistics.duration = Math.ceil(this.statistics.duration / 1000000); diff --git a/main/src/main/webapp/src/js/routes.js b/main/src/main/webapp/src/js/routes.js index 5f8ea203c..a8e1ebb8d 100644 --- a/main/src/main/webapp/src/js/routes.js +++ b/main/src/main/webapp/src/js/routes.js @@ -26,101 +26,103 @@ function config($stateProvider, $urlRouterProvider) { $stateProvider .state('home', { url: '/home', - template: '' + template: '', + data: {title: 'Automata Learning EXperience'} }) .state('usersSettings', { url: '/users/settings', template: '', - data: {requiresProject: false, roles: ['REGISTERED', 'ADMIN']} + data: {requiresProject: false, roles: ['REGISTERED', 'ADMIN'], title: 'Settings'} }) .state('projects', { url: '/projects', template: '', - data: {roles: ['REGISTERED', 'ADMIN']} + data: {roles: ['REGISTERED', 'ADMIN'], title : 'Projects'} }) .state('projectsDashboard', { url: '/projects/dashboard', template: '', - data: {requiresProject: true, roles: ['REGISTERED', 'ADMIN']} + data: {requiresProject: true, roles: ['REGISTERED', 'ADMIN'], title: 'Dashboard'} }) .state('counters', { url: '/counters', template: '', - data: {requiresProject: true, roles: ['REGISTERED', 'ADMIN']} + data: {requiresProject: true, roles: ['REGISTERED', 'ADMIN'], title: 'Counters'} }) .state('symbols', { url: '/symbols', template: '', - data: {requiresProject: true, roles: ['REGISTERED', 'ADMIN']} + data: {requiresProject: true, roles: ['REGISTERED', 'ADMIN'], title: 'Symbols'} }) .state('symbolsTrash', { url: '/symbols/trash', template: '', - data: {requiresProject: true, roles: ['REGISTERED', 'ADMIN']} + data: {requiresProject: true, roles: ['REGISTERED', 'ADMIN'], title: 'Symbols > Trash'} }) .state('symbolsHistory', { url: '/symbols/{symbolId:int}/history', template: '', - data: {requiresProject: true, roles: ['REGISTERED', 'ADMIN']} + data: {requiresProject: true, roles: ['REGISTERED', 'ADMIN'], title: 'Symbols > History'} }) .state('symbolsActions', { url: '/symbols/{symbolId:int}/actions', template: '', - data: {requiresProject: true, roles: ['REGISTERED', 'ADMIN']} + data: {requiresProject: true, roles: ['REGISTERED', 'ADMIN'], title: 'Symbols > Actions'} }) .state('symbolsImport', { url: '/symbols/import', template: '', - data: {requiresProject: true, roles: ['REGISTERED', 'ADMIN']} + data: {requiresProject: true, roles: ['REGISTERED', 'ADMIN'], title: 'Symbols > Import'} }) .state('learnerSetup', { url: '/learner/setup', template: '', - data: {requiresProject: true, roles: ['REGISTERED', 'ADMIN']} + data: {requiresProject: true, roles: ['REGISTERED', 'ADMIN'], title: 'Learner > Setup'} }) .state('learnerStart', { url: '/learner/start', template: '', - data: {requiresProject: true, roles: ['REGISTERED', 'ADMIN']} + data: {requiresProject: true, roles: ['REGISTERED', 'ADMIN'], title: 'Learning'} }) .state('results', { url: '/results', template: '', - data: {requiresProject: true, roles: ['REGISTERED', 'ADMIN']} + data: {requiresProject: true, roles: ['REGISTERED', 'ADMIN'], title: 'Results'} }) .state('resultsCompare', { url: '/results/:testNos/compare', template: '', - data: {requiresProject: true, roles: ['REGISTERED', 'ADMIN']} + data: {requiresProject: true, roles: ['REGISTERED', 'ADMIN'], title: 'Results > Compare'} }) .state('statistics', { url: '/statistics', template: '', - data: {requiresProject: true, roles: ['REGISTERED', 'ADMIN']} + data: {requiresProject: true, roles: ['REGISTERED', 'ADMIN'], title: 'Statistics'} }) .state('statisticsCompare', { url: '/statistics/{testNos:string}/compare/{mode:string}', template: '', - data: {requiresProject: true, roles: ['REGISTERED', 'ADMIN']} + data: {requiresProject: true, roles: ['REGISTERED', 'ADMIN'], title: 'Statistics > Compare'} }) .state('adminUsers', { url: '/admin/users', template: '', - data: {requiresProject: false, roles: ['ADMIN']} + data: {requiresProject: false, roles: ['ADMIN'], title: 'Admin > Users'} }) .state('about', { url: '/about', template: '', - data: {} + data: {title: 'About'} }) .state('error', { url: '/error', - template: '' + template: '', + data: {title: 'Error'} }) .state('files', { url: '/files', template: '', - data: {requiresProject: true, roles: ['REGISTERED', 'ADMIN']} + data: {requiresProject: true, roles: ['REGISTERED', 'ADMIN'], title: 'Files'} }); } @@ -138,6 +140,8 @@ function run($rootScope, $state, SessionService, ToastService) { const user = SessionService.getUser(); const project = SessionService.getProject(); + document.querySelector('title').innerHTML = 'ALEX | ' + toState.data.title; + if ((toState.data.roles && (user === null || toState.data.roles.indexOf(user.role) === -1)) || (toState.data.requiresProject && project === null)) { From 22faa2eb56617e99fbd2b7f9d433268776f8c734 Mon Sep 17 00:00:00 2001 From: Alexander Bainczyk Date: Mon, 25 Apr 2016 23:39:36 +0200 Subject: [PATCH 14/44] added feature: export and import projects with groups, symbols and actions --- .../alex/core/dao/ProjectDAOImpl.java | 151 +++++++++++------- .../learnlib/alex/core/dao/SymbolDAOImpl.java | 2 +- .../alex/core/dao/SymbolGroupDAOImpl.java | 8 +- .../alex/core/entities/SymbolGroup.java | 12 +- .../html/components/project-create-form.html | 139 +++++++++------- .../js/components/forms/projectCreateForm.js | 34 +++- .../webapp/src/js/components/projectList.js | 50 +++++- .../src/js/resources/ProjectResource.js | 10 ++ 8 files changed, 273 insertions(+), 133 deletions(-) diff --git a/main/src/main/java/de/learnlib/alex/core/dao/ProjectDAOImpl.java b/main/src/main/java/de/learnlib/alex/core/dao/ProjectDAOImpl.java index f35c4b290..21e62cb9f 100644 --- a/main/src/main/java/de/learnlib/alex/core/dao/ProjectDAOImpl.java +++ b/main/src/main/java/de/learnlib/alex/core/dao/ProjectDAOImpl.java @@ -16,10 +16,7 @@ package de.learnlib.alex.core.dao; -import de.learnlib.alex.core.entities.Project; -import de.learnlib.alex.core.entities.Symbol; -import de.learnlib.alex.core.entities.SymbolGroup; -import de.learnlib.alex.core.entities.User; +import de.learnlib.alex.core.entities.*; import de.learnlib.alex.exceptions.NotFoundException; import de.learnlib.alex.utils.HibernateUtil; import de.learnlib.alex.utils.ValidationExceptionHelper; @@ -33,11 +30,7 @@ import javax.inject.Inject; import javax.validation.ValidationException; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; +import java.util.*; /** * Implementation of a ProjectDAO using Hibernate. @@ -45,17 +38,20 @@ @Repository public class ProjectDAOImpl implements ProjectDAO { - /** Use the logger for the server part. */ + /** + * Use the logger for the server part. + */ private static final Logger LOGGER = LogManager.getLogger("server"); - /** The SymbolDAO to use. */ + /** + * The SymbolDAO to use. + */ private SymbolDAOImpl symbolDAO; /** * The constructor. * - * @param symbolDAO - * The SymbolDAOImpl to use. + * @param symbolDAO The SymbolDAOImpl to use. */ @Inject public ProjectDAOImpl(SymbolDAOImpl symbolDAO) { @@ -64,43 +60,18 @@ public ProjectDAOImpl(SymbolDAOImpl symbolDAO) { @Override public void create(Project project) throws ValidationException { + // start session Session session = HibernateUtil.getSession(); HibernateUtil.beginTransaction(); try { - - // TODO: fix this branch with multi user - if (project.getGroups().size() > 0) { // create new project from json with existing groups - Integer i = 0; - for (SymbolGroup group: project.getGroups()) { - Long groupId = project.getNextGroupId(); - - if (i.equals(0)) { // just assume that the first group is the default one - project.setDefaultGroup(group); - } else { - group.setId(groupId); - project.setNextGroupId(groupId + 1); - } - group.setProject(project); - - if (group.getSymbols().size() > 0) { - for (Symbol symbol: group.getSymbols()) { - Long symbolId = project.getNextSymbolId(); - symbol.setProject(project); - symbol.setGroup(group); - symbol.setRevision(0L); - symbol.setId(symbolId); - project.setNextSymbolId(symbolId + 1); - } - } - i++; - } - } else { + if (project.getGroups().size() == 0) { // if a new project is created the 'normal' way SymbolGroup defaultGroup = new SymbolGroup(); defaultGroup.setName("Default Group"); defaultGroup.setProject(project); defaultGroup.setUser(project.getUser()); + project.addGroup(defaultGroup); project.setDefaultGroup(defaultGroup); @@ -115,12 +86,83 @@ public void create(Project project) throws ValidationException { } project.setNextSymbolId(nextSymbolId + 1); } - } - session.save(project); - HibernateUtil.commitTransaction(); + session.save(project); + HibernateUtil.commitTransaction(); + } else { // the project is imported + + // map symbolName -> actions as a temporary store because actions cannot be + // persisted when a project is created + Map> actionMap = new HashMap<>(); + + // link all the entities for the default group + project.getDefaultGroup().setProject(project); + project.getDefaultGroup().setUser(project.getUser()); + project.getDefaultGroup().getSymbols().forEach(symbol -> { + actionMap.put(symbol.getName(), symbol.getActions()); + + long symbolId = project.getNextSymbolId(); + project.setNextSymbolId(symbolId + 1); + + symbol.setId(symbolId); + symbol.setUser(project.getUser()); + symbol.setProject(project); + symbol.setGroup(project.getDefaultGroup()); + symbol.setRevision(0L); + symbol.setActions(new ArrayList<>()); + }); + + // link all entities for the other groups + project.getGroups().forEach(group -> { + long groupId = project.getNextGroupId(); + project.setNextGroupId(groupId + 1); - // error handling + group.setProject(project); + group.setUser(project.getUser()); + group.setId(groupId); + group.getSymbols().forEach(symbol -> { + actionMap.put(symbol.getName(), symbol.getActions()); + + long symbolId = project.getNextSymbolId(); + project.setNextSymbolId(symbolId + 1); + + symbol.setId(symbolId); + symbol.setUser(project.getUser()); + symbol.setProject(project); + symbol.setGroup(group); + symbol.setRevision(0L); + symbol.setActions(new ArrayList<>()); + }); + }); + + session.save(project); + HibernateUtil.commitTransaction(); + + // to this point the project, the symbol groups and all symbols are persisted + // now we have to make a new session to persist actions because ACID is for the weak! + final Session session2 = HibernateUtil.getSession(); + HibernateUtil.beginTransaction(); + + // add the actions to the corresponding symbol and update it + // TODO: at the current time, references from ExecuteSymbolAction are not respected here + project.getDefaultGroup().getSymbols().forEach(symbol -> { + symbol.setActions(actionMap.get(symbol.getName())); + symbolDAO.beforeSymbolSave(symbol); + session2.update(symbol); + }); + + project.getGroups().forEach(group -> { + group.getSymbols().forEach(symbol -> { + symbol.setActions(actionMap.get(symbol.getName())); + symbolDAO.beforeSymbolSave(symbol); + session2.update(symbol); + }); + }); + + HibernateUtil.commitTransaction(); + } + + // error handling } catch (javax.validation.ConstraintViolationException e) { HibernateUtil.rollbackTransaction(); LOGGER.info("Project creation failed:", e); @@ -142,8 +184,8 @@ public List getAll(User user, EmbeddableFields... embedFields) { // get the Projects @SuppressWarnings("unchecked") // it should be a list of Projects List result = session.createCriteria(Project.class) - .add(Restrictions.eq("user", user)) - .list(); + .add(Restrictions.eq("user", user)) + .list(); // load lazy relations for (Project p : result) { @@ -185,9 +227,9 @@ public Project getByName(Long userId, String projectName) { HibernateUtil.beginTransaction(); Project result = (Project) session.createCriteria(Project.class) - .add(Restrictions.eq("user.id", userId)) - .add(Restrictions.eq("name", projectName)) - .uniqueResult(); + .add(Restrictions.eq("user.id", userId)) + .add(Restrictions.eq("name", projectName)) + .uniqueResult(); HibernateUtil.commitTransaction(); @@ -211,7 +253,7 @@ public void update(Project project) throws NotFoundException, ValidationExceptio session.update(projectInDB); HibernateUtil.commitTransaction(); - // error handling + // error handling } catch (ObjectNotFoundException e) { LOGGER.info("Project update failed:", e); HibernateUtil.rollbackTransaction(); @@ -246,10 +288,9 @@ public void delete(Long userId, Long projectId) throws NotFoundException { /** * Load objects that are connected with a project over a 'lazy' relation ship. - * @param project - * The project which needs the 'lazy' objects. - * @param session * + * @param project The project which needs the 'lazy' objects. + * @param session */ private void initLazyRelations(Project project, Session session, EmbeddableFields... embedFields) { if (embedFields != null && embedFields.length > 0) { @@ -257,7 +298,7 @@ private void initLazyRelations(Project project, Session session, EmbeddableField if (fieldsToLoad.contains(EmbeddableFields.GROUPS)) { project.getGroups().forEach(group -> group.getSymbols() - .forEach(s -> symbolDAO.loadLazyRelations(session, s))); + .forEach(s -> symbolDAO.loadLazyRelations(session, s))); } else { project.setGroups(null); } diff --git a/main/src/main/java/de/learnlib/alex/core/dao/SymbolDAOImpl.java b/main/src/main/java/de/learnlib/alex/core/dao/SymbolDAOImpl.java index 70d70217e..52cae80da 100644 --- a/main/src/main/java/de/learnlib/alex/core/dao/SymbolDAOImpl.java +++ b/main/src/main/java/de/learnlib/alex/core/dao/SymbolDAOImpl.java @@ -754,7 +754,7 @@ private List getSymbols(Session session, Long userId, Long projectId, Lo return symbols; } - private void beforeSymbolSave(Symbol symbol) { + public void beforeSymbolSave(Symbol symbol) { for (int i = 0; i < symbol.getActions().size(); i++) { SymbolAction action = symbol.getActions().get(i); action.setId(null); diff --git a/main/src/main/java/de/learnlib/alex/core/dao/SymbolGroupDAOImpl.java b/main/src/main/java/de/learnlib/alex/core/dao/SymbolGroupDAOImpl.java index 11e336c66..97aa453a1 100644 --- a/main/src/main/java/de/learnlib/alex/core/dao/SymbolGroupDAOImpl.java +++ b/main/src/main/java/de/learnlib/alex/core/dao/SymbolGroupDAOImpl.java @@ -34,11 +34,7 @@ import javax.inject.Inject; import javax.validation.ConstraintViolationException; import javax.validation.ValidationException; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; +import java.util.*; /** * Implementation of a SymbolGroupDAO using Hibernate. @@ -219,7 +215,7 @@ private void initLazyRelations(Session session, User user, SymbolGroup group, Em group.getId(), SymbolVisibilityLevel.ALL); List symbols = symbolDAO.getAll(session, user, group.getProjectId(), idRevisionPairs); - group.setSymbols(new HashSet<>(symbols)); + group.setSymbols(new ArrayList<>(symbols)); } catch (NotFoundException e) { group.setSymbols(null); } diff --git a/main/src/main/java/de/learnlib/alex/core/entities/SymbolGroup.java b/main/src/main/java/de/learnlib/alex/core/entities/SymbolGroup.java index b1d2581cd..d376eafbd 100644 --- a/main/src/main/java/de/learnlib/alex/core/entities/SymbolGroup.java +++ b/main/src/main/java/de/learnlib/alex/core/entities/SymbolGroup.java @@ -35,8 +35,8 @@ import javax.persistence.ManyToOne; import javax.persistence.OneToMany; import java.io.Serializable; -import java.util.HashSet; -import java.util.Set; +import java.util.ArrayList; +import java.util.List; /** * Entity to organize symbols. @@ -84,7 +84,7 @@ public class SymbolGroup implements Serializable { /** The Symbols manged by this group. */ @OneToMany(mappedBy = "group", fetch = FetchType.LAZY) @Cascade({ CascadeType.SAVE_UPDATE, CascadeType.DELETE }) - private Set symbols; + private List symbols; /** * Default constructor. @@ -92,7 +92,7 @@ public class SymbolGroup implements Serializable { public SymbolGroup() { this.groupId = 0L; this.id = 0L; - this.symbols = new HashSet<>(); + this.symbols = new ArrayList<>(); } @JsonIgnore @@ -205,7 +205,7 @@ public void setName(String name) { * * @return The related symbols. */ - public Set getSymbols() { + public List getSymbols() { return symbols; } @@ -228,7 +228,7 @@ public int getSymbolSize() { * @param symbols * The new set of related symbols. */ - public void setSymbols(Set symbols) { + public void setSymbols(List symbols) { this.symbols = symbols; } diff --git a/main/src/main/webapp/src/html/components/project-create-form.html b/main/src/main/webapp/src/html/components/project-create-form.html index 2d39c2d5e..3b570c017 100644 --- a/main/src/main/webapp/src/html/components/project-create-form.html +++ b/main/src/main/webapp/src/html/components/project-create-form.html @@ -1,70 +1,99 @@ - + + +
-
- + - +
+ -
-
- The name is required + + +
+
+ The name is required +
+
-
-
-
- +
+ - + -
-
- The url is required -
-
- The url has to start with http[s]:// +
+
+ The url is required +
+
+ The url has to start with http[s]:// +
+
-
-
-
- - +
+ + + +
+
+ The description may not contain more than 250 characters +
+
+
-
-
- The description may not contain more than 250 characters +
+
+ + +
+
+ * These fields are required
-
-
- -
+ + +
+ + + Drag and drop a json file with the project here or click on it. + -
+
+
+ + +

Make sure the name of the project is not yet present.

+
+
+ +
+
+
+ * These fields are required +
+
-
- * These fields are required -
- \ No newline at end of file +
+ \ No newline at end of file diff --git a/main/src/main/webapp/src/js/components/forms/projectCreateForm.js b/main/src/main/webapp/src/js/components/forms/projectCreateForm.js index 2f9c73fc3..a67b49d09 100644 --- a/main/src/main/webapp/src/js/components/forms/projectCreateForm.js +++ b/main/src/main/webapp/src/js/components/forms/projectCreateForm.js @@ -23,11 +23,12 @@ class ProjectCreateForm { /** * Constructor - * @param ProjectResource - * @param ToastService - * @param EventBus + * @param $scope + * @param {ProjectResource} ProjectResource + * @param {ToastService} ToastService + * @param {EventBus} EventBus */ - constructor(ProjectResource, ToastService, EventBus) { + constructor($scope, ProjectResource, ToastService, EventBus) { this.ProjectResource = ProjectResource; this.ToastService = ToastService; this.EventBus = EventBus; @@ -37,6 +38,16 @@ class ProjectCreateForm { * @type {Project} */ this.project = new Project(); + + /** + * The project that should be imported + * @type {Project} + */ + this.projectToImport = null; + + EventBus.on(events.FILE_LOADED, (evt, data) => { + this.projectToImport = JSON.parse(data.file); + }, $scope); } /** Creates a new project */ @@ -55,6 +66,21 @@ class ProjectCreateForm { this.ToastService.danger('

Creation of project failed

' + response.data.message); }); } + + /** Imports a project **/ + importProject() { + if (this.projectToImport !== null) { + this.ProjectResource.create(this.projectToImport) + .then(importedProject => { + this.EventBus.emit(events.PROJECT_CREATED, {project: importedProject}); + this.projectToImport = null; + this.ToastService.success(`The project '${importedProject.name}' has been imported.`) + }) + .catch(response => { + this.ToastService.danger(`

The import failed!

${response.data.message}`) + }); + } + } } const projectCreateForm = { diff --git a/main/src/main/webapp/src/js/components/projectList.js b/main/src/main/webapp/src/js/components/projectList.js index 4610f9089..dd0190cfb 100644 --- a/main/src/main/webapp/src/js/components/projectList.js +++ b/main/src/main/webapp/src/js/components/projectList.js @@ -27,19 +27,24 @@ class ProjectList { /** * Constructor * @param $state - * @param ProjectResource - * @param ToastService - * @param SessionService - * @param PromptService - * @param EventBus + * @param {ProjectResource} ProjectResource + * @param {SymbolGroupResource} SymbolGroupResource + * @param {ToastService} ToastService + * @param {SessionService} SessionService + * @param {PromptService} PromptService + * @param {EventBus} EventBus + * @param {DownloadService} DownloadService */ - constructor($state, ProjectResource, ToastService, SessionService, PromptService, EventBus) { + constructor($state, ProjectResource, SymbolGroupResource, ToastService, SessionService, PromptService, EventBus, + DownloadService) { this.$state = $state; this.ProjectResource = ProjectResource; + this.SymbolGroupResource = SymbolGroupResource; this.ToastService = ToastService; this.SessionService = SessionService; this.PromptService = PromptService; this.EventBus = EventBus; + this.DownloadService = DownloadService; } /** @@ -69,6 +74,34 @@ class ProjectList { }); }); } + + /** + * Downloads the project in an importable format. + * @param {Project} project + */ + exportProject(project) { + this.ProjectResource.get(project.id).then(project => { + this.SymbolGroupResource.getAll(project.id, true).then(groups => { + groups.forEach(group => { + delete group.id; + delete group.project; + delete group.user; + + group.symbols = group.symbols.map(symbol => symbol.getExportableSymbol()) + }); + + delete project.id; + delete project.user; + project.defaultGroup = groups.shift(); + project.groups = groups; + + this.PromptService.prompt("Enter a filename for the project").then(filename => { + this.DownloadService.downloadObject(project, filename); + this.ToastService.success('The project has been exported.'); + }); + }) + }) + } } const projectList = { @@ -101,6 +134,11 @@ const projectList = { Delete
+
  • + + Export + +
  • diff --git a/main/src/main/webapp/src/js/resources/ProjectResource.js b/main/src/main/webapp/src/js/resources/ProjectResource.js index 00c1d543d..9cac772ca 100644 --- a/main/src/main/webapp/src/js/resources/ProjectResource.js +++ b/main/src/main/webapp/src/js/resources/ProjectResource.js @@ -30,6 +30,16 @@ class ProjectResource { this.$http = $http; } + /** + * Get a project by its id + * @param {number} projectId + * @returns {*} + */ + get(projectId) { + return this.$http.get(`rest/projects/${projectId}`) + .then(response => new Project(response.data)); + } + /** * Get all projects of a user * @returns {*} From d957cc105f0971ac999a70e1a31db58e2e615554 Mon Sep 17 00:00:00 2001 From: Alexander Bainczyk Date: Sat, 30 Apr 2016 11:30:22 +0200 Subject: [PATCH 15/44] added an action to execute JavaScript in the web driver This should make it easier to write symbols for applications that make use of JavaScript --- .../WebSymbolActions/ExecuteScriptAction.java | 84 +++++++++++++++++++ .../alex/core/entities/SymbolAction.java | 15 +--- main/src/main/webapp/gruntfile.js | 1 + .../webapp/src/html/actions/rest_call.html | 2 +- .../src/html/actions/web_executeScript.html | 17 ++++ .../src/html/modals/action-create-modal.html | 1 + .../js/components/views/symbolsActionsView.js | 6 +- main/src/main/webapp/src/js/constants.js | 2 + .../actions/webActions/ExecuteScriptAction.js | 46 ++++++++++ .../webapp/src/js/services/ActionService.js | 3 + .../webActions/ExecuteScriptAction.tests.js | 26 ++++++ .../ExecuteScriptActionTest.java | 74 ++++++++++++++++ .../ExecuteScriptTestData.json | 4 + 13 files changed, 264 insertions(+), 17 deletions(-) create mode 100644 main/src/main/java/de/learnlib/alex/actions/WebSymbolActions/ExecuteScriptAction.java create mode 100644 main/src/main/webapp/src/html/actions/web_executeScript.html create mode 100644 main/src/main/webapp/src/js/entities/actions/webActions/ExecuteScriptAction.js create mode 100644 main/src/main/webapp/tests/unit/entities/actions/webActions/ExecuteScriptAction.tests.js create mode 100644 main/src/test/java/de/learnlib/alex/actions/WebSymbolActions/ExecuteScriptActionTest.java create mode 100644 main/src/test/resources/actions/websymbolactions/ExecuteScriptTestData.json diff --git a/main/src/main/java/de/learnlib/alex/actions/WebSymbolActions/ExecuteScriptAction.java b/main/src/main/java/de/learnlib/alex/actions/WebSymbolActions/ExecuteScriptAction.java new file mode 100644 index 000000000..1502357f9 --- /dev/null +++ b/main/src/main/java/de/learnlib/alex/actions/WebSymbolActions/ExecuteScriptAction.java @@ -0,0 +1,84 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package de.learnlib.alex.actions.WebSymbolActions; + +import com.fasterxml.jackson.annotation.JsonTypeName; +import de.learnlib.alex.core.entities.ExecuteResult; +import de.learnlib.alex.core.learner.connectors.WebSiteConnector; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.hibernate.validator.constraints.NotBlank; +import org.openqa.selenium.JavascriptExecutor; + +import javax.persistence.Column; +import javax.persistence.DiscriminatorValue; +import javax.persistence.Entity; + +/** + * Action to execute JavaScript on the opened browser. + */ +@Entity +@DiscriminatorValue("web_executeScript") +@JsonTypeName("web_executeScript") +public class ExecuteScriptAction extends WebSymbolAction { + + /** + * to be serializable. + */ + private static final long serialVersionUID = 6118333853615934954L; + + /** + * Use the learner logger. + */ + private static final Logger LOGGER = LogManager.getLogger("learner"); + + /** + * The javascript to execute. + */ + @NotBlank + @Column(columnDefinition = "CLOB") + private String script; + + /** + * Get the script to execute. + * + * @return The script to execute. + */ + public String getScript() { + return script; + } + + /** + * Set the script to execute. + * + * @param script The script to execute. + */ + public void setScript(String script) { + this.script = script; + } + + @Override + public ExecuteResult execute(WebSiteConnector connector) { + if (connector.getDriver() instanceof JavascriptExecutor) { + ((JavascriptExecutor) connector.getDriver()).executeScript(script); + return getSuccessOutput(); + } else { + LOGGER.info("This driver does not support JavaScript!"); + return getFailedOutput(); + } + } +} diff --git a/main/src/main/java/de/learnlib/alex/core/entities/SymbolAction.java b/main/src/main/java/de/learnlib/alex/core/entities/SymbolAction.java index 0471ecedf..8deda3814 100644 --- a/main/src/main/java/de/learnlib/alex/core/entities/SymbolAction.java +++ b/main/src/main/java/de/learnlib/alex/core/entities/SymbolAction.java @@ -38,19 +38,7 @@ import de.learnlib.alex.actions.StoreSymbolActions.SetVariableByJSONAttributeAction; import de.learnlib.alex.actions.StoreSymbolActions.SetVariableByNodeAttributeAction; import de.learnlib.alex.actions.WaitAction; -import de.learnlib.alex.actions.WebSymbolActions.CheckNodeAction; -import de.learnlib.alex.actions.WebSymbolActions.CheckPageTitleAction; -import de.learnlib.alex.actions.WebSymbolActions.CheckTextWebAction; -import de.learnlib.alex.actions.WebSymbolActions.ClearAction; -import de.learnlib.alex.actions.WebSymbolActions.ClickAction; -import de.learnlib.alex.actions.WebSymbolActions.ClickLinkAction; -import de.learnlib.alex.actions.WebSymbolActions.FillAction; -import de.learnlib.alex.actions.WebSymbolActions.GotoAction; -import de.learnlib.alex.actions.WebSymbolActions.SelectAction; -import de.learnlib.alex.actions.WebSymbolActions.SubmitAction; -import de.learnlib.alex.actions.WebSymbolActions.WaitForNodeAction; -import de.learnlib.alex.actions.WebSymbolActions.WaitForTitleAction; -import de.learnlib.alex.actions.WebSymbolActions.WebSymbolAction; +import de.learnlib.alex.actions.WebSymbolActions.*; import de.learnlib.alex.core.learner.connectors.ConnectorManager; import de.learnlib.alex.utils.SearchHelper; import org.hibernate.annotations.NaturalId; @@ -100,6 +88,7 @@ @JsonSubTypes.Type(name = "web_clear", value = ClearAction.class), @JsonSubTypes.Type(name = "web_click", value = ClickAction.class), @JsonSubTypes.Type(name = "web_clickLinkByText", value = ClickLinkAction.class), + @JsonSubTypes.Type(name = "web_executeScript", value = ExecuteScriptAction.class), @JsonSubTypes.Type(name = "web_fill", value = FillAction.class), @JsonSubTypes.Type(name = "web_goto", value = GotoAction.class), @JsonSubTypes.Type(name = "web_submit", value = SubmitAction.class), diff --git a/main/src/main/webapp/gruntfile.js b/main/src/main/webapp/gruntfile.js index b9fc3cdda..b02c88962 100644 --- a/main/src/main/webapp/gruntfile.js +++ b/main/src/main/webapp/gruntfile.js @@ -4,6 +4,7 @@ module.exports = function (grunt) { 'node_modules/ace-builds/src/ace.js', 'node_modules/ace-builds/src/theme-eclipse.js', 'node_modules/ace-builds/src/mode-json.js', + 'node_modules/ace-builds/src/mode-javascript.js', 'node_modules/angular/angular.js', 'node_modules/angular-ui-ace/src/ui-ace.js', 'node_modules/n3-charts/build/LineChart.js', diff --git a/main/src/main/webapp/src/html/actions/rest_call.html b/main/src/main/webapp/src/html/actions/rest_call.html index 066887485..7682c90b0 100644 --- a/main/src/main/webapp/src/html/actions/rest_call.html +++ b/main/src/main/webapp/src/html/actions/rest_call.html @@ -18,7 +18,7 @@

    Call Url

    + ui-ace="{useWrapMode : true, showGutter: true, theme:'eclipse', mode: 'json', workerPath: '/node_modules/ace-builds/src-min/'}">

    diff --git a/main/src/main/webapp/src/html/actions/web_executeScript.html b/main/src/main/webapp/src/html/actions/web_executeScript.html new file mode 100644 index 000000000..ee69a92cb --- /dev/null +++ b/main/src/main/webapp/src/html/actions/web_executeScript.html @@ -0,0 +1,17 @@ +

    + Execute JavaScript +

    + +

    Execute JavaScript in the browser if it supports it.

    +
    + +
    + + +
    +
    + +
    + Note: This action only fails if JavaScript is not enabled in the web driver that is used. +
    \ No newline at end of file diff --git a/main/src/main/webapp/src/html/modals/action-create-modal.html b/main/src/main/webapp/src/html/modals/action-create-modal.html index 89f9a1551..499b19ce9 100644 --- a/main/src/main/webapp/src/html/modals/action-create-modal.html +++ b/main/src/main/webapp/src/html/modals/action-create-modal.html @@ -32,6 +32,7 @@ Select from list Wait for the title Wait for an element + Execute JavaScript diff --git a/main/src/main/webapp/src/js/components/views/symbolsActionsView.js b/main/src/main/webapp/src/js/components/views/symbolsActionsView.js index 9b6b9f42a..9ae6db248 100644 --- a/main/src/main/webapp/src/js/components/views/symbolsActionsView.js +++ b/main/src/main/webapp/src/js/components/views/symbolsActionsView.js @@ -14,9 +14,9 @@ * limitations under the License. */ -import _ from 'lodash'; -import {events} from '../../constants'; -import {AlphabetSymbol} from '../../entities/AlphabetSymbol'; +import _ from "lodash"; +import {events} from "../../constants"; +import {AlphabetSymbol} from "../../entities/AlphabetSymbol"; /** * The controller that handles the page for managing all actions of a symbol. The symbol whose actions should be diff --git a/main/src/main/webapp/src/js/constants.js b/main/src/main/webapp/src/js/constants.js index e5e323b75..3ee1fb75a 100644 --- a/main/src/main/webapp/src/js/constants.js +++ b/main/src/main/webapp/src/js/constants.js @@ -95,6 +95,8 @@ const actionType = { WEB_CLEAR: 'web_clear', WEB_CLICK_LINK_BY_TEXT: 'web_clickLinkByText', WEB_CLICK: 'web_click', + WEB_CONFIRM_DIALOG: 'web_confirmDialog', + WEB_EXECUTE_SCRIPT: 'web_executeScript', WEB_FILL: 'web_fill', WEB_GO_TO: 'web_goto', WEB_SELECT: 'web_select', diff --git a/main/src/main/webapp/src/js/entities/actions/webActions/ExecuteScriptAction.js b/main/src/main/webapp/src/js/entities/actions/webActions/ExecuteScriptAction.js new file mode 100644 index 000000000..02454577e --- /dev/null +++ b/main/src/main/webapp/src/js/entities/actions/webActions/ExecuteScriptAction.js @@ -0,0 +1,46 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {actionType} from "../../../constants"; +import Action from "../Action"; + +/** + * The action to execute a piece of JavaScript in the web browser. + */ +export default class ExecuteScriptAction extends Action { + + /** + * Constructor. + * @param obj {*} - The object to create the action from. + */ + constructor(obj) { + super(actionType.WEB_EXECUTE_SCRIPT, obj); + + /** + * The JavaScript to execute. + * @type {string} + */ + this.script = obj.script || null; + } + + /** + * The string representation of the action. + * @returns {string} + */ + toString() { + return 'Execute JavaScript in the browser'; + } +} \ No newline at end of file diff --git a/main/src/main/webapp/src/js/services/ActionService.js b/main/src/main/webapp/src/js/services/ActionService.js index 63d5896a3..333c0b5d9 100644 --- a/main/src/main/webapp/src/js/services/ActionService.js +++ b/main/src/main/webapp/src/js/services/ActionService.js @@ -20,6 +20,7 @@ import {actionType} from '../constants'; import SelectWebAction from '../entities/actions/webActions/SelectWebAction'; import SubmitWebAction from '../entities/actions/webActions/SubmitWebAction'; import GoToWebAction from '../entities/actions/webActions/GoToWebAction'; +import ExecuteScriptAction from '../entities/actions/webActions/ExecuteScriptAction'; import FillWebAction from '../entities/actions/webActions/FillWebAction'; import ClickWebAction from '../entities/actions/webActions/ClickWebAction'; import ClickLinkByTextWebAction from '../entities/actions/webActions/ClickLinkByTextWebAction'; @@ -72,6 +73,8 @@ class ActionService { return new GoToWebAction(data); case actionType.WEB_FILL: return new FillWebAction(data); + case actionType.WEB_EXECUTE_SCRIPT: + return new ExecuteScriptAction(data); case actionType.WEB_CLICK: return new ClickWebAction(data); case actionType.WEB_CLICK_LINK_BY_TEXT: diff --git a/main/src/main/webapp/tests/unit/entities/actions/webActions/ExecuteScriptAction.tests.js b/main/src/main/webapp/tests/unit/entities/actions/webActions/ExecuteScriptAction.tests.js new file mode 100644 index 000000000..881d6f135 --- /dev/null +++ b/main/src/main/webapp/tests/unit/entities/actions/webActions/ExecuteScriptAction.tests.js @@ -0,0 +1,26 @@ +import Action from '../../../../../src/js/entities/actions/Action'; +import ExecuteScriptAction from '../../../../../src/js/entities/actions/webActions/ExecuteScriptAction'; +import {actionType} from '../../../../../src/js/constants'; + +describe('ExecuteScriptAction', () => { + beforeEach(angular.mock.module('ALEX')); + + it('should extend the default action and should implement a toString method', () => { + const action = new ExecuteScriptAction({}); + expect(action instanceof Action).toBe(true); + expect(angular.isFunction(action.toString)).toBe(true); + }); + + it('should create a default action', () => { + const expectedAction = { + type: actionType.WEB_EXECUTE_SCRIPT, + negated: false, + ignoreFailure: false, + disabled: false, + + script: null + }; + const action = new ExecuteScriptAction({}); + expect(angular.toJson(action)).toEqual(angular.toJson(expectedAction)); + }); +}); diff --git a/main/src/test/java/de/learnlib/alex/actions/WebSymbolActions/ExecuteScriptActionTest.java b/main/src/test/java/de/learnlib/alex/actions/WebSymbolActions/ExecuteScriptActionTest.java new file mode 100644 index 000000000..c274e0218 --- /dev/null +++ b/main/src/test/java/de/learnlib/alex/actions/WebSymbolActions/ExecuteScriptActionTest.java @@ -0,0 +1,74 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package de.learnlib.alex.actions.WebSymbolActions; + +import com.fasterxml.jackson.databind.ObjectMapper; +import de.learnlib.alex.core.entities.Project; +import de.learnlib.alex.core.entities.User; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +import java.io.File; +import java.io.IOException; +import java.net.URISyntaxException; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +@RunWith(MockitoJUnitRunner.class) +public class ExecuteScriptActionTest { + + @Mock + private User user; + + @Mock + private Project project; + + private ExecuteScriptAction action; + + @Before + public void setUp() { + action = new ExecuteScriptAction(); + action.setUser(user); + action.setProject(project); + action.setScript("document.write('hello')"); + } + + @Test + public void testJSON() throws IOException { + ObjectMapper mapper = new ObjectMapper(); + String json = mapper.writeValueAsString(action); + ExecuteScriptAction c2 = mapper.readValue(json, ExecuteScriptAction.class); + + assertEquals(action.getScript(), c2.getScript()); + } + + @Test + public void testJSONFile() throws IOException, URISyntaxException { + ObjectMapper mapper = new ObjectMapper(); + + File file = new File(getClass().getResource("/actions/websymbolactions/ExecuteScriptTestData.json").toURI()); + WebSymbolAction obj = mapper.readValue(file, WebSymbolAction.class); + + assertTrue(obj instanceof ExecuteScriptAction); + ExecuteScriptAction objAsAction = (ExecuteScriptAction) obj; + assertEquals("document.write('hello')", objAsAction.getScript()); + } +} diff --git a/main/src/test/resources/actions/websymbolactions/ExecuteScriptTestData.json b/main/src/test/resources/actions/websymbolactions/ExecuteScriptTestData.json new file mode 100644 index 000000000..22a6aea76 --- /dev/null +++ b/main/src/test/resources/actions/websymbolactions/ExecuteScriptTestData.json @@ -0,0 +1,4 @@ +{ + "type": "web_executeScript", + "script": "document.write('hello')" +} \ No newline at end of file From 8aebec957adf9e8cab031d7803c0dae65ca96cd7 Mon Sep 17 00:00:00 2001 From: Alexander Bainczyk Date: Sun, 1 May 2016 14:08:14 +0200 Subject: [PATCH 16/44] smaller ui enhancement In the learn setup view, the flag for the reset symbol has changed from an empty circle to a more declarative label. --- .../main/webapp/src/html/pages/learner-setup.html | 13 ++++++++----- main/src/main/webapp/src/scss/style.scss | 14 ++++++++++++++ 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/main/src/main/webapp/src/html/pages/learner-setup.html b/main/src/main/webapp/src/html/pages/learner-setup.html index 09478952a..d0c2863d4 100644 --- a/main/src/main/webapp/src/html/pages/learner-setup.html +++ b/main/src/main/webapp/src/html/pages/learner-setup.html @@ -68,15 +68,18 @@ symbol="symbol" selection-model selection-model-selected-items="vm.selectedSymbols"> - - + selection-model-ignore + ng-click="vm.setResetSymbol(symbol)"> + Reset Reset Symbol + class="label label-primary label-reset"> + Reset +
    diff --git a/main/src/main/webapp/src/scss/style.scss b/main/src/main/webapp/src/scss/style.scss index 3d979f8aa..7d1349986 100644 --- a/main/src/main/webapp/src/scss/style.scss +++ b/main/src/main/webapp/src/scss/style.scss @@ -578,4 +578,18 @@ body { .learn-loading-indicator { margin-top: 54px; +} + + +.label-reset { + &.label-default { + background: #fff; + border: 1px solid #555; + color: #555; + opacity: .6; + } + + &.label-primary { + border: 1px solid #337ab7; + } } \ No newline at end of file From 708f4959250b56d9da497af8b9addc508196b4dd Mon Sep 17 00:00:00 2001 From: Alexander Bainczyk Date: Thu, 5 May 2016 17:25:38 +0200 Subject: [PATCH 17/44] added the possibility to batch delete users Admins can now delete multiple users at once via 'rest/users/batch/{userIds}'. This is for usability reasons and so that the frontend can be learned more easily. --- .../de/learnlib/alex/core/dao/UserDAO.java | 11 ++++++ .../learnlib/alex/core/dao/UserDAOImpl.java | 23 +++++++++++- .../de/learnlib/alex/rest/UserResource.java | 37 ++++++++++++++++++- .../webapp/src/html/pages/admin-users.html | 21 ++++++++++- .../main/webapp/src/html/pages/counters.html | 2 +- main/src/main/webapp/src/html/pages/home.html | 6 --- .../src/js/components/views/adminUsersView.js | 34 +++++++++++++++-- .../webapp/src/js/resources/UserResource.js | 8 ++++ .../alex/core/dao/UserDAOImplTest.java | 33 +++++++++++++++++ .../learnlib/alex/rest/UserResourceTest.java | 35 ++++++++++++++++++ 10 files changed, 195 insertions(+), 15 deletions(-) diff --git a/main/src/main/java/de/learnlib/alex/core/dao/UserDAO.java b/main/src/main/java/de/learnlib/alex/core/dao/UserDAO.java index 5ceab6909..c44061139 100644 --- a/main/src/main/java/de/learnlib/alex/core/dao/UserDAO.java +++ b/main/src/main/java/de/learnlib/alex/core/dao/UserDAO.java @@ -19,6 +19,7 @@ import de.learnlib.alex.core.entities.User; import de.learnlib.alex.core.entities.UserRole; import de.learnlib.alex.exceptions.NotFoundException; +import de.learnlib.alex.utils.IdsList; import javax.validation.ValidationException; import java.util.List; @@ -97,4 +98,14 @@ public interface UserDAO { */ void delete(Long id) throws NotFoundException; + /** + * Deletes multiple users from the database at once. This method should only be called by admins. + * The admin that calls this method cannot delete himself. + * + * @param ids + * The ids of the users to delete + * @throws NotFoundException + * If the user to delete was not found (and thus not deleted). + */ + void delete(IdsList ids) throws NotFoundException; } diff --git a/main/src/main/java/de/learnlib/alex/core/dao/UserDAOImpl.java b/main/src/main/java/de/learnlib/alex/core/dao/UserDAOImpl.java index de1eded30..a4c8acb35 100644 --- a/main/src/main/java/de/learnlib/alex/core/dao/UserDAOImpl.java +++ b/main/src/main/java/de/learnlib/alex/core/dao/UserDAOImpl.java @@ -20,6 +20,7 @@ import de.learnlib.alex.core.entities.UserRole; import de.learnlib.alex.exceptions.NotFoundException; import de.learnlib.alex.utils.HibernateUtil; +import de.learnlib.alex.utils.IdsList; import de.learnlib.alex.utils.ValidationExceptionHelper; import org.hibernate.Session; import org.hibernate.criterion.Restrictions; @@ -145,8 +146,8 @@ public void delete(Long id) throws NotFoundException { @SuppressWarnings("unchecked") List admins = session.createCriteria(User.class) - .add(Restrictions.eq("role", UserRole.ADMIN)) - .list(); + .add(Restrictions.eq("role", UserRole.ADMIN)) + .list(); if (admins.size() == 1) { throw new NotFoundException("There has to be at least one admin left"); @@ -157,6 +158,24 @@ public void delete(Long id) throws NotFoundException { HibernateUtil.commitTransaction(); } + @Override + public void delete(IdsList ids) throws NotFoundException { + Session session = HibernateUtil.getSession(); + HibernateUtil.beginTransaction(); + + try { + for (Long id: ids) { + User user = get(session, id); + session.delete(user); + } + } catch (NotFoundException e) { + HibernateUtil.rollbackTransaction(); + throw e; + } + + HibernateUtil.commitTransaction(); + } + private User get(Session session, Long id) throws NotFoundException { User user = session.get(User.class, id); diff --git a/main/src/main/java/de/learnlib/alex/rest/UserResource.java b/main/src/main/java/de/learnlib/alex/rest/UserResource.java index 5f4212608..157de9064 100644 --- a/main/src/main/java/de/learnlib/alex/rest/UserResource.java +++ b/main/src/main/java/de/learnlib/alex/rest/UserResource.java @@ -22,6 +22,7 @@ import de.learnlib.alex.exceptions.NotFoundException; import de.learnlib.alex.security.JWTHelper; import de.learnlib.alex.security.UserPrincipal; +import de.learnlib.alex.utils.IdsList; import de.learnlib.alex.utils.ResourceErrorHandler; import de.learnlib.alex.utils.ResponseHelper; import org.apache.logging.log4j.LogManager; @@ -343,7 +344,7 @@ public Response demoteUser(@PathParam("id") Long userId) { * The ID of the user to delete. * @return Nothing if the user was deleted. * - * @successResponse 204 No Contetnt + * @successResponse 204 No Content * @errorResponse 400 bad request `de.learnlib.alex.utils.ResourceErrorHandler.RESTError * @errorResponse 404 not found `de.learnlib.alex.utils.ResourceErrorHandler.RESTError */ @@ -368,6 +369,40 @@ public Response delete(@PathParam("id") long userId) { } } + /** + * Deletes multiples users. + * An admin cannot delete himself. + * + * @param ids + * The ids of the user to delete. + * @return Nothing if the users have been deleted. + * + * @successResponse 204 No Content + * @errorResponse 400 bad request `de.learnlib.alex.utils.ResourceErrorHandler.RESTError + * @errorResponse 404 not found `de.learnlib.alex.utils.ResourceErrorHandler.RESTError + */ + @DELETE + @Path("/batch/{ids}") + @Produces(MediaType.APPLICATION_JSON) + @RolesAllowed({"ADMIN"}) + public Response delete(@PathParam("ids") IdsList ids) { + User user = ((UserPrincipal) securityContext.getUserPrincipal()).getUser(); + LOGGER.trace("UserResource.delete(" + ids + ")."); + + if (ids.contains(user.getId())) { + Exception e = new Exception("You cannot delete your own account this way."); + return ResourceErrorHandler.createRESTErrorMessage("UserResource.delete", Status.BAD_REQUEST, e); + } + + try { + userDAO.delete(ids); + return Response.status(Status.NO_CONTENT).build(); + } catch (NotFoundException e) { + return ResourceErrorHandler.createRESTErrorMessage("UserResource.delete", Status.NOT_FOUND, e); + } + } + + /** * Logs in a user by generating a unique JWT for him that needs to be send in every request. * diff --git a/main/src/main/webapp/src/html/pages/admin-users.html b/main/src/main/webapp/src/html/pages/admin-users.html index 5d235bd19..a27815194 100644 --- a/main/src/main/webapp/src/html/pages/admin-users.html +++ b/main/src/main/webapp/src/html/pages/admin-users.html @@ -1,6 +1,19 @@
    + +
    + + +
    + +
    +
    +
    +
    @@ -9,6 +22,7 @@

    {{ vm.users.length}} Registered Users

    + @@ -17,7 +31,12 @@

    {{ vm.users.length}} Registered Users

    + ng-class="{'success': user.id === vm.user.id, 'info': user.role === 'ADMIN' && user.id !== vm.user.id}" + selection-model + selection-model-selected-items="vm.selectedUsers"> +
      ID Email Role
    + + diff --git a/main/src/main/webapp/src/html/pages/counters.html b/main/src/main/webapp/src/html/pages/counters.html index 5ee4b5334..9e672d4d5 100644 --- a/main/src/main/webapp/src/html/pages/counters.html +++ b/main/src/main/webapp/src/html/pages/counters.html @@ -2,7 +2,7 @@ -
    +
    diff --git a/main/src/main/webapp/src/html/pages/home.html b/main/src/main/webapp/src/html/pages/home.html index c2ac246ca..726aa7bd1 100644 --- a/main/src/main/webapp/src/html/pages/home.html +++ b/main/src/main/webapp/src/html/pages/home.html @@ -18,12 +18,6 @@ -
    - - -
    diff --git a/main/src/main/webapp/src/js/components/views/adminUsersView.js b/main/src/main/webapp/src/js/components/views/adminUsersView.js index 0d12b5f99..df63d7ad2 100644 --- a/main/src/main/webapp/src/js/components/views/adminUsersView.js +++ b/main/src/main/webapp/src/js/components/views/adminUsersView.js @@ -23,12 +23,15 @@ class AdminUsersView { /** * Constructor * @param $scope - * @param UserResource - * @param EventBus - * @param SessionService - * @param ToastService + * @param {UserResource} UserResource + * @param {EventBus} EventBus + * @param {SessionService} SessionService + * @param {ToastService} ToastService */ constructor($scope, UserResource, EventBus, SessionService, ToastService) { + this.UserResource = UserResource; + this.ToastService = ToastService; + this.EventBus = EventBus; /** * The user that is logged in @@ -42,6 +45,12 @@ class AdminUsersView { */ this.users = []; + /** + * All selected users + * @type {User[]} + */ + this.selectedUsers = []; + // fetch all users from the server UserResource.getAll() .then(users => { @@ -64,6 +73,23 @@ class AdminUsersView { if (i > -1) this.users.splice(i, 1); }, $scope); } + + /** + * Deletes selected users which are not admins. + */ + deleteSelectedUsers() { + const ids = this.selectedUsers.filter(user => user.role !== 'ADMIN') + .map(user => user.id); + + this.UserResource.removeManyUsers(ids) + .then(() => { + this.ToastService.success('The users have been deleted'); + ids.forEach(id => this.EventBus.emit(events.USER_DELETED, {user: {id: id}})); + }) + .catch(response => { + this.ToastService.danger(`Deleting failed! ${response.data.message}`); + }) + } } export const adminUsersView = { diff --git a/main/src/main/webapp/src/js/resources/UserResource.js b/main/src/main/webapp/src/js/resources/UserResource.js index 38d2c2f1a..55c358965 100644 --- a/main/src/main/webapp/src/js/resources/UserResource.js +++ b/main/src/main/webapp/src/js/resources/UserResource.js @@ -118,6 +118,14 @@ class UserResource { return this.$http.delete(`rest/users/${user.id}`, {}); } + /** + * Deletes the users with the specified ids + * @param {number[]} userIds + */ + removeManyUsers(userIds) { + return this.$http.delete(`rest/users/batch/${userIds.join(',')}`); + } + /** * Gives a registered user admin rights * diff --git a/main/src/test/java/de/learnlib/alex/core/dao/UserDAOImplTest.java b/main/src/test/java/de/learnlib/alex/core/dao/UserDAOImplTest.java index dc316baa3..f67197686 100644 --- a/main/src/test/java/de/learnlib/alex/core/dao/UserDAOImplTest.java +++ b/main/src/test/java/de/learnlib/alex/core/dao/UserDAOImplTest.java @@ -19,10 +19,12 @@ import de.learnlib.alex.core.entities.User; import de.learnlib.alex.core.entities.UserRole; import de.learnlib.alex.exceptions.NotFoundException; +import de.learnlib.alex.utils.IdsList; import org.junit.After; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; +import org.mockito.internal.matchers.Not; import javax.validation.ValidationException; import java.util.ArrayList; @@ -253,6 +255,37 @@ public void shouldDeleteAnAdminIfThereAreMoreThanOne() throws NotFoundException assertTrue(!admins.contains(u)); } + @Test + public void shouldDeleteMultipleUsers() throws NotFoundException { + User user1 = new User(); + user1.setEmail("user1@mail.de"); + user1.setEncryptedPassword("test"); + + User user2 = new User(); + user2.setEmail("user2@mail.de"); + user2.setEncryptedPassword("test"); + + userDAO.create(user1); + userDAO.create(user2); + + IdsList ids = new IdsList(String.valueOf(user1.getId()) + "," + String.valueOf(user2.getId())); + userDAO.delete(ids); + + assertEquals(userDAO.getAllByRole(UserRole.REGISTERED).size(), 0); + } + + @Test(expected = NotFoundException.class) + public void shouldNotDeleteMultipleUsersOnNotFound() throws NotFoundException { + User user1 = new User(); + user1.setEmail("user1@mail.de"); + user1.setEncryptedPassword("test"); + + userDAO.create(user1); + + IdsList ids = new IdsList(String.valueOf(user1.getId()) + ",123"); + userDAO.delete(ids); + } + @Test(expected = NotFoundException.class) public void shouldFailToDeleteAUserOnInvalidId() throws NotFoundException { userDAO.delete(-1L); diff --git a/main/src/test/java/de/learnlib/alex/rest/UserResourceTest.java b/main/src/test/java/de/learnlib/alex/rest/UserResourceTest.java index dad3d380e..6c4b0f254 100644 --- a/main/src/test/java/de/learnlib/alex/rest/UserResourceTest.java +++ b/main/src/test/java/de/learnlib/alex/rest/UserResourceTest.java @@ -5,6 +5,7 @@ import de.learnlib.alex.core.entities.User; import de.learnlib.alex.core.entities.UserRole; import de.learnlib.alex.exceptions.NotFoundException; +import de.learnlib.alex.utils.IdsList; import de.learnlib.alex.utils.UserHelper; import org.glassfish.jersey.test.JerseyTest; import org.junit.Ignore; @@ -344,6 +345,40 @@ public void shouldReturn404IfUserToDeleteWasNotFound() throws NotFoundException assertEquals(Response.Status.NOT_FOUND.getStatusCode(), response.getStatus()); } + @Test + public void shouldDeleteMultipleUsers() throws NotFoundException { + userDAO.delete(new IdsList("2,3")); + + Response response = target("/users/batch/2,3") + .request() + .header("Authorization", adminToken) + .delete(); + + assertEquals(Response.Status.NO_CONTENT.getStatusCode(), response.getStatus()); + } + + @Test + public void shouldReturn400IfAdminsIdIsInList() { + Response response = target("/users/batch/2,3," + String.valueOf(admin.getId())) + .request() + .header("Authorization", adminToken) + .delete(); + + assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), response.getStatus()); + } + + @Test + public void shouldReturn404IfAtLeastOneUserHasNotBeenFound() throws NotFoundException { + willThrow(NotFoundException.class).given(userDAO).delete(new IdsList("2,3")); + + Response response = target("/users/batch/2,3") + .request() + .header("Authorization", adminToken) + .delete(); + + assertEquals(Response.Status.NOT_FOUND.getStatusCode(), response.getStatus()); + } + @Test public void shouldLogin() throws NotFoundException { String json = "{\"email\": \"fake_user@alex.example\", \"password\": \"fake_password\"}"; From b465ee421d13e950c4b17ef07ad93f298b23cd1c Mon Sep 17 00:00:00 2001 From: Alexander Bainczyk Date: Fri, 6 May 2016 14:04:39 +0200 Subject: [PATCH 18/44] made components of the last few directives --- main/src/main/webapp/index.html | 2 +- .../src/html/actions/setVariableByHTML.html | 2 +- .../actions/setVariableByNodeAttribute.html | 2 +- .../src/html/actions/web_checkForNode.html | 2 +- .../src/html/actions/web_checkForText.html | 2 +- .../webapp/src/html/actions/web_clear.html | 2 +- .../webapp/src/html/actions/web_click.html | 2 +- .../src/html/actions/web_clickLinkByText.html | 2 +- .../webapp/src/html/actions/web_fill.html | 2 +- .../webapp/src/html/actions/web_select.html | 2 +- .../webapp/src/html/actions/web_submit.html | 2 +- .../src/html/actions/web_waitForNode.html | 2 +- .../html-element-picker.html | 22 +- .../html/components/learn-result-panel.html | 2 +- .../webapp/src/html/pages/statistics.html | 6 +- .../src/js/components/discriminationTree.js | 230 +++++++++++++ .../src/js/components/htmlElementPicker.js | 257 +++++++++++++++ .../webapp/src/js/components/hypothesis.js | 253 +++++++++++++++ .../main/webapp/src/js/components/index.js | 6 + .../src/js/components/views/statisticsView.js | 3 - .../src/js/directives/discriminationTree.js | 222 ------------- .../webapp/src/js/directives/dropdownHover.js | 6 +- .../src/js/directives/htmlElementPicker.js | 307 ------------------ .../js/directives/htmlElementPickerHandle.js | 68 ++++ .../webapp/src/js/directives/hypothesis.js | 237 -------------- .../main/webapp/src/js/directives/index.js | 11 +- .../js/services/HtmlElementPickerService.js | 41 +++ main/src/main/webapp/src/js/services/index.js | 4 +- 28 files changed, 892 insertions(+), 807 deletions(-) rename main/src/main/webapp/src/html/{directives => components}/html-element-picker.html (69%) create mode 100644 main/src/main/webapp/src/js/components/discriminationTree.js create mode 100644 main/src/main/webapp/src/js/components/htmlElementPicker.js create mode 100644 main/src/main/webapp/src/js/components/hypothesis.js delete mode 100644 main/src/main/webapp/src/js/directives/discriminationTree.js delete mode 100644 main/src/main/webapp/src/js/directives/htmlElementPicker.js create mode 100644 main/src/main/webapp/src/js/directives/htmlElementPickerHandle.js delete mode 100644 main/src/main/webapp/src/js/directives/hypothesis.js create mode 100644 main/src/main/webapp/src/js/services/HtmlElementPickerService.js diff --git a/main/src/main/webapp/index.html b/main/src/main/webapp/index.html index 919374d98..34589fdc0 100644 --- a/main/src/main/webapp/index.html +++ b/main/src/main/webapp/index.html @@ -15,7 +15,7 @@ - + \ No newline at end of file diff --git a/main/src/main/webapp/src/html/actions/setVariableByHTML.html b/main/src/main/webapp/src/html/actions/setVariableByHTML.html index fc2a53954..3e9f2d29c 100644 --- a/main/src/main/webapp/src/html/actions/setVariableByHTML.html +++ b/main/src/main/webapp/src/html/actions/setVariableByHTML.html @@ -16,6 +16,6 @@

    Set Variable By Node Value

    - +   WebPicker \ No newline at end of file diff --git a/main/src/main/webapp/src/html/actions/setVariableByNodeAttribute.html b/main/src/main/webapp/src/html/actions/setVariableByNodeAttribute.html index f993dafe7..2a1f9fefa 100644 --- a/main/src/main/webapp/src/html/actions/setVariableByNodeAttribute.html +++ b/main/src/main/webapp/src/html/actions/setVariableByNodeAttribute.html @@ -17,7 +17,7 @@

    Set Variable by Node Attribute

    diff --git a/main/src/main/webapp/src/html/actions/web_checkForNode.html b/main/src/main/webapp/src/html/actions/web_checkForNode.html index 7c7fb75b8..960644a34 100644 --- a/main/src/main/webapp/src/html/actions/web_checkForNode.html +++ b/main/src/main/webapp/src/html/actions/web_checkForNode.html @@ -10,6 +10,6 @@

    Search for Node

    - +   Element Picker \ No newline at end of file diff --git a/main/src/main/webapp/src/html/actions/web_checkForText.html b/main/src/main/webapp/src/html/actions/web_checkForText.html index f134e3742..10ed5856b 100644 --- a/main/src/main/webapp/src/html/actions/web_checkForText.html +++ b/main/src/main/webapp/src/html/actions/web_checkForText.html @@ -16,6 +16,6 @@

    Use Regular Expression - +   Element Picker \ No newline at end of file diff --git a/main/src/main/webapp/src/html/actions/web_clear.html b/main/src/main/webapp/src/html/actions/web_clear.html index b3cd69701..90f170c38 100644 --- a/main/src/main/webapp/src/html/actions/web_clear.html +++ b/main/src/main/webapp/src/html/actions/web_clear.html @@ -12,6 +12,6 @@

    - +   Element Picker \ No newline at end of file diff --git a/main/src/main/webapp/src/html/actions/web_click.html b/main/src/main/webapp/src/html/actions/web_click.html index 1a67d5c46..bc9c63ea5 100644 --- a/main/src/main/webapp/src/html/actions/web_click.html +++ b/main/src/main/webapp/src/html/actions/web_click.html @@ -12,6 +12,6 @@

    - +   Element Picker \ No newline at end of file diff --git a/main/src/main/webapp/src/html/actions/web_clickLinkByText.html b/main/src/main/webapp/src/html/actions/web_clickLinkByText.html index 108d4ff57..072d9c4ff 100644 --- a/main/src/main/webapp/src/html/actions/web_clickLinkByText.html +++ b/main/src/main/webapp/src/html/actions/web_clickLinkByText.html @@ -11,6 +11,6 @@

    - +   Element Picker \ No newline at end of file diff --git a/main/src/main/webapp/src/html/actions/web_fill.html b/main/src/main/webapp/src/html/actions/web_fill.html index e1cedce45..90da57d25 100644 --- a/main/src/main/webapp/src/html/actions/web_fill.html +++ b/main/src/main/webapp/src/html/actions/web_fill.html @@ -13,6 +13,6 @@

    Fill Node

    - +   Element Picker \ No newline at end of file diff --git a/main/src/main/webapp/src/html/actions/web_select.html b/main/src/main/webapp/src/html/actions/web_select.html index de02ab1a7..918043c48 100644 --- a/main/src/main/webapp/src/html/actions/web_select.html +++ b/main/src/main/webapp/src/html/actions/web_select.html @@ -11,7 +11,7 @@

    Select value

    diff --git a/main/src/main/webapp/src/html/actions/web_submit.html b/main/src/main/webapp/src/html/actions/web_submit.html index d2e8e011b..082d3d0ff 100644 --- a/main/src/main/webapp/src/html/actions/web_submit.html +++ b/main/src/main/webapp/src/html/actions/web_submit.html @@ -9,6 +9,6 @@

    Submit Form

    - +   Element Picker \ No newline at end of file diff --git a/main/src/main/webapp/src/html/actions/web_waitForNode.html b/main/src/main/webapp/src/html/actions/web_waitForNode.html index 3f260bdeb..8f1f1b21a 100644 --- a/main/src/main/webapp/src/html/actions/web_waitForNode.html +++ b/main/src/main/webapp/src/html/actions/web_waitForNode.html @@ -37,7 +37,7 @@

    Wait for an element

    diff --git a/main/src/main/webapp/src/html/directives/html-element-picker.html b/main/src/main/webapp/src/html/components/html-element-picker.html similarity index 69% rename from main/src/main/webapp/src/html/directives/html-element-picker.html rename to main/src/main/webapp/src/html/components/html-element-picker.html index 2f38c35a7..4a8abefb6 100644 --- a/main/src/main/webapp/src/html/directives/html-element-picker.html +++ b/main/src/main/webapp/src/html/components/html-element-picker.html @@ -3,13 +3,13 @@
    -
    +
    -
    +
    ...
    - + @@ -20,10 +20,10 @@
    -
    @@ -31,19 +31,19 @@
    - - Enable the selection mode + + Enable the selection mode
    -
    -
    -
    diff --git a/main/src/main/webapp/src/html/components/learn-result-panel.html b/main/src/main/webapp/src/html/components/learn-result-panel.html index 0a015e39a..2d7c72e13 100644 --- a/main/src/main/webapp/src/html/components/learn-result-panel.html +++ b/main/src/main/webapp/src/html/components/learn-result-panel.html @@ -87,7 +87,7 @@
    - diff --git a/main/src/main/webapp/src/html/pages/statistics.html b/main/src/main/webapp/src/html/pages/statistics.html index c38bab9d1..2bafcf8d5 100644 --- a/main/src/main/webapp/src/html/pages/statistics.html +++ b/main/src/main/webapp/src/html/pages/statistics.html @@ -48,7 +48,7 @@
    -
    +
    @@ -80,6 +80,10 @@
    +
    + You have not run any tests yet. +
    +
    \ No newline at end of file diff --git a/main/src/main/webapp/src/js/components/discriminationTree.js b/main/src/main/webapp/src/js/components/discriminationTree.js new file mode 100644 index 000000000..4ee7cec5d --- /dev/null +++ b/main/src/main/webapp/src/js/components/discriminationTree.js @@ -0,0 +1,230 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import _ from "lodash"; +import {graphlib, dagre, render as Renderer} from "dagre-d3"; +import d3 from "d3/d3"; + +const STYLE = { + edgeLabel: 'display: inline; font-weight: bold; line-height: 1; text-align: center; white-space: nowrap; vertical-align: baseline; font-size: 10px', + nodeLabel: 'display: inline; font-weight: bold; line-height: 1; text-align: center; white-space: nowrap; vertical-align: baseline; font-size: 12px', + edge: 'stroke: rgba(0, 0, 0, 0.3); stroke-width: 3; fill:none', + node: 'fill: #fff; stroke: #000; stroke-width: 1' +}; + +/** + * The component for displaying a discrimination tree in an svg element. + * Expects another property 'data' which holds the string representation of the discrimination tree. + * + * Use it like: '' + */ +// @ngInject +class DiscriminationTreeComponent { + + /** + * Constructor. + * @param $scope + * @param $element + */ + constructor($scope, $element) { + + // the svg where the discrimination tree is drawn into + this.svg = $element.find('svg')[0]; + + // the first g node of the svg for rendering + this.svgGroup = $element.find('g')[0]; + + // the parent of the svg to fit its size accordingly + this.svgContainer = this.svg.parentNode; + + this.renderer = new Renderer(); + + this.graph = null; + + // render the new discrimination tree when property 'data' changes + $scope.$watch('vm.data', data => { + if (data) { + const graph = this.createGraph(angular.fromJson(data)); + this.layout(graph); + this.render(); + } + }); + + const resizeHandler = this.fitSize.bind(this); + + // resize the svg to its parents size on window resize + // and call it once so that svg gets the proper dimensions + window.addEventListener('resize', resizeHandler); + + $scope.$on('$destroy', () => { + window.removeEventListener('resize', resizeHandler); + }); + } + + fitSize() { + this.svg.setAttribute("width", this.svgContainer.clientWidth); + this.svg.setAttribute("height", this.svgContainer.clientHeight); + } + + /** + * Creates a graph structure from a discrimination tree in order to layout it with the given dagreD3 library + * + * @param {Object} dt - The discrimination tree + * @returns {{nodes: Array, edges: Array}} - The tree as graph representation + */ + createGraph(dt) { + const nodes = []; + const edges = []; + + const createGraphData = (node, parent) => { + + // root without children + if (!node.children && parent === null) { + nodes.push(node.data); + return; + } + + // is leaf? + if (node.children.length === 0) { + return; + } + + // add node if not exists + if (!_.find(nodes, node.discriminator)) { + nodes.push(node.discriminator); + } + + if (parent !== null) { + edges.push({ + from: parent.discriminator, + to: node.discriminator, + label: node.edgeLabel + }); + } + + node.children.forEach(child => { + if (child.data) { + nodes.push(child.data); + edges.push({ + from: node.discriminator, + to: child.data, + label: child.edgeLabel + }); + } + }); + + node.children.forEach(child => { + if (child.discriminator) createGraphData(child, node); + }); + }; + + createGraphData(dt, null); + + return { + nodes: nodes, + edges: edges + }; + } + + /** + * Creates positions for nodes and edges of the discrimination tree that can be rendered with dagreD3 + * + * @param {Object} graph - The discrimination tree as graph + */ + layout(graph) { + + // initialize graph + this.graph = new graphlib.Graph({ + directed: true + }); + this.graph.setGraph({}); + + // add nodes to the graph + graph.nodes.forEach(node => { + this.graph.setNode(node, { + shape: node[0] === 'q' ? 'rect' : 'circle', // draw a rectangle when node is a leaf + label: node, + width: 25, + style: STYLE.node, + labelStyle: STYLE.nodeLabel + }); + }); + + //add edges to the graph + graph.edges.forEach(edge => { + this.graph.setEdge(edge.from, edge.to, { + lineInterpolate: 'basis', + style: STYLE.edge, + label: edge.label, + labelStyle: STYLE.edgeLabel + }); + }); + + // layout + dagre.layout(this.graph, {}); + } + + /** + * Renders the discrimination tree in the svg with the dagreD3 library. + */ + render() { + + // render the graph + this.renderer(d3.select(this.svgGroup), this.graph); + + // position it in the center of the svg parent + const xCenterOffset = (this.svgContainer.clientWidth - this.graph.graph().width) / 2; + this.svgGroup.setAttribute("transform", "translate(" + xCenterOffset + ", 100)"); + + // swap defs and paths children of .edgepaths because arrows are not shown + // on export otherwise <.< + _.forEach(this.svg.querySelectorAll('.edgePath'), edgePath => { + edgePath.insertBefore(edgePath.childNodes[1], edgePath.firstChild); + }); + + const zoomHandler = () => { + this.svgGroup.setAttribute('transform', `translate(${zoom.translate()}) scale(${zoom.scale()})`); + }; + + // Create and handle zoom & pan event + const zoom = d3.behavior.zoom().scaleExtent([0.1, 10]) + .translate([(this.svgContainer.clientWidth - this.graph.graph().width) / 2, 100]) + .on("zoom", zoomHandler); + + zoom(d3.select(this.svg)); + + // in order to prevent only a white screen in some browsers, firing a resize event on the window + // displays the svg contents + window.setTimeout(() => { + window.dispatchEvent(new Event('resize')); + }, 100); + } +} + +export const discriminationTree = { + template: ` +
    + + + +
    + `, + controller: DiscriminationTreeComponent, + controllerAs: 'vm', + bindings: { + data: '=' + } +}; \ No newline at end of file diff --git a/main/src/main/webapp/src/js/components/htmlElementPicker.js b/main/src/main/webapp/src/js/components/htmlElementPicker.js new file mode 100644 index 000000000..d67bdec9f --- /dev/null +++ b/main/src/main/webapp/src/js/components/htmlElementPicker.js @@ -0,0 +1,257 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * The actual HTML element picker. Handles the complete window including the selection of elements and loading + * of urls. Works as a 'mini embedded browser' + * + * Use: '' + * + * @param SessionService - The SessionService + */ +// @ngInject +class HtmlElementPickerComponent { + /** + * Constructor. + * @param {SessionService} SessionService + * @param {HtmlElementPickerService} HtmlElementPickerService + * @param $element + * @param $scope + */ + constructor(SessionService, HtmlElementPickerService, $element, $scope) { + this.SessionService = SessionService; + this.HtmlElementPickerService = HtmlElementPickerService; + this.iframe = $element.find('iframe'); + this.$scope = $scope; + + this.proxyUrl = null; + + // when moving with the mouse over an element, this elements gets saved in this variable in order to + // prevent multiple calls of getCssPath for the same element + this.lastTarget = null; + + /** + * flag for selection mode + * @type {boolean} + */ + this.isSelectable = false; + + /** + * The XPath of the selected element + * @type {null|string} + */ + this.selector = null; + + /** + * The element.textContent value + * @type {null|string} + */ + this.textContent = null; + + /** + * The url that is loaded in the iframe + * @type {string} + */ + this.url = null; + + /** + * The project in the session + * @type {null|Project} + */ + this.project = null; + + this.mouseMoveHandler = null; + this.keyUpHandler = null; + this.clickHandler = null; + + this.init(); + } + + /** + * Load project, create proxy address and load the last url in the iframe. + */ + init() { + this.project = this.SessionService.getProject(); + this.proxyUrl = 'rest/proxy?url='; + this.url = this.HtmlElementPickerService.lastUrl; + this.loadUrl(); + } + + /** + * Loads an entered url into the iframe and handles the click on every a element + */ + loadUrl() { + const self = this; + + this.iframe.attr('src', this.proxyUrl + this.project.baseUrl + '/' + (this.url === null ? '' : this.url)); + this.iframe.on('load', () => { + angular.element(this.iframe.contents()[0].body.getElementsByTagName('a')) + .on('click', function () { + if (!self.isSelectable) { + const _this = this; + if (this.getAttribute('href') !== '' && this.getAttribute('href')[0] !== '#') { + self.$scope.$apply(() => { + self.url = decodeURIComponent(_this.getAttribute('href')) + .replace(self.proxyUrl + self.project.baseUrl + '/', ''); + }); + } + } + }); + }); + } + + /** + * Get the unique CSS XPath from selected Element + * http://stackoverflow.com/questions/4588119/get-elements-css-selector-without-element-id + * + * @param el - The element to get the unique css path from + * @returns {String} - The unique css path ot the element + * @private + */ + getCssPath(el) { + var names = []; + while (el.parentNode) { + if (el.id) { + names.unshift('#' + el.id); + break; + } else { + if (el == el.ownerDocument.documentElement) names.unshift(el.tagName); + else { + for (var c = 1, e = el; e.previousElementSibling; e = e.previousElementSibling, c++); + names.unshift(el.tagName + ":nth-child(" + c + ")"); + } + el = el.parentNode; + } + } + return names.join(" > "); + } + + /** + * Makes the web element picker invisible and fires the close event + */ + close() { + this.HtmlElementPickerService.lastUrl = this.url; + this.HtmlElementPickerService.deferred.reject(); + } + + /** + * Makes the web element Picker invisible and fires the ok event with the selector of the element that was + * selected. If no selector is defined, then it just closes the picker + */ + ok() { + this.HtmlElementPickerService.lastUrl = this.url; + this.HtmlElementPickerService.deferred.resolve({ + selector: this.selector, + textContent: this.textContent + }); + } + + /** + * Removes the outline from the selected element, removes all events from the iframe and removes the + * keypress event. When this function is called the selected element is fixed and won't change by any + * further interaction with the iframe + * + * @param e - js event + */ + handleClick(e) { + if (angular.isDefined(e)) { + e.preventDefault(); + e.stopPropagation(); + } + + if (this.lastTarget !== null) { + this.lastTarget.style.outline = '0px'; + } + this.lastTarget = null; + + angular.element(this.iframe.contents()[0].body).off('mousemove', this.mouseMoveHandler); + angular.element(this.iframe.contents()[0].body).off('click', this.clickHandler); + angular.element(document.body).off('keyup', this.keyUpHandler); + } + + /** + * Saves the element that is under the cursor so that it can be selected. Adds an outline to the element + * in order to highlight it. + * + * @param e - js event + * @returns {boolean} + */ + handleMouseMove(e) { + if (this.lastTarget === e.target) { + return false; + } else { + if (this.lastTarget !== null) { + this.lastTarget.style.outline = '0px'; + } + this.lastTarget = e.target; + } + this.lastTarget.style.outline = '5px solid red'; + this.selector = this.getCssPath(this.lastTarget); + + if (this.lastTarget.nodeName.toLowerCase() === 'input') { + this.textContent = this.lastTarget.value; + } else { + this.textContent = this.lastTarget.textContent; + } + + this.$scope.$apply(); + } + + /** + * Calls handleClick() when control key is pressed to have an alternative for selecting a dom node without + * firing any click events on it. + * + * @param e + */ + handleKeyUp(e) { + if (e.keyCode == 17) { // strg + this.handleClick(); + this.isSelectable = false; + } + } + + /** + * Enables the selection mode and therefore adds events to the iframe + */ + toggleSelection() { + if (!this.isSelectable) { + const iframeBody = angular.element(this.iframe.contents()[0].body); + this.mouseMoveHandler = this.handleMouseMove.bind(this); + this.keyUpHandler = this.handleKeyUp.bind(this); + + this.clickHandler = (e) => { + this.handleClick(e); + this.$scope.$apply(() => { + this.isSelectable = false; + }); + }; + + iframeBody.on('mousemove', this.mouseMoveHandler); + iframeBody.one('click', this.clickHandler); + angular.element(document.body).on('keyup', this.keyUpHandler); + } else { + this.handleClick(); + this.selector = null; + } + this.isSelectable = !this.isSelectable; + } +} + +export const htmlElementPicker = { + templateUrl: 'html/components/html-element-picker.html', + controller: HtmlElementPickerComponent, + controllerAs: 'vm' +}; \ No newline at end of file diff --git a/main/src/main/webapp/src/js/components/hypothesis.js b/main/src/main/webapp/src/js/components/hypothesis.js new file mode 100644 index 000000000..29eef882a --- /dev/null +++ b/main/src/main/webapp/src/js/components/hypothesis.js @@ -0,0 +1,253 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import _ from 'lodash'; +import {graphlib, dagre, render as Renderer} from 'dagre-d3'; +import d3 from 'd3/d3'; + +import {events} from '../constants'; + +// various styles used to style the hypothesis +const STYLE = { + edge: 'stroke: rgba(0, 0, 0, 0.3); stroke-width: 3; fill:none', + edgeLabel: 'display: inline; font-weight: bold; line-height: 1; text-align: center; white-space: nowrap; vertical-align: baseline; font-size: 10px', + nodeLabel: 'display: inline; font-weight: bold; line-height: 1; text-align: center; white-space: nowrap; vertical-align: baseline; font-size: 12px', + node: 'fill: #fff; stroke: #000; stroke-width: 1', + initNode: 'fill: #B3E6B3; stroke: #5cb85c; stroke-width: 3', + svg: { + 'font-family': '"Helvetica Neue",Helvetica,Arial,sans-serif', + 'font-size': '12px', + 'line-height': '1.42857', + 'color': '#333' + } +}; + +/** + * The component that is used to display hypotheses. + * + * Attribute 'isSelectable' should only be true if it should be possible to select input output pairs from the + * hypothesis + * + * Attribute 'layoutSettings' is optional. + * + * Use: */ +// @ngInject +class HypothesisComponent { + + /** + * Constructor. + * @param $scope + * @param $element + * @param {EventBus} EventBus + */ + constructor($scope, $element, EventBus) { + this.$scope = $scope; + this.$element = $element; + this.EventBus = EventBus; + + this.renderer = new Renderer(); + this.graph = null; + + this.svg = $element.find('svg')[0]; + this.svgGroup = $element.find('g')[0]; + this.svgContainer = this.svg.parentNode; + + this.resizeHandler = this.fitSize.bind(this); + + d3.select(this.svg).style(STYLE.svg); + + $scope.$watch('vm.data', data => { + if (data) { + this.data = data; + this.init(); + } + }); + + $scope.$watch('vm.layoutSettings', settings => { + if (settings) { + this.layoutSettings = settings; + this.init(); + } + }); + + $scope.$on('$destroy', () => { + window.removeEventListener('resize', this.resizeHandler); + }); + + // do this whole stuff so that the size of the svg adjusts to the window + window.addEventListener('resize', this.resizeHandler); + + this.init(); + } + + init() { + this.layout(); + this.render(); + this.handleEvents(); + } + + /** + * Adjust the size of the svg to the size of the visible container. + */ + fitSize() { + this.svg.setAttribute("width", this.svgContainer.clientWidth); + this.svg.setAttribute("height", this.svgContainer.clientHeight); + } + + /** + * Layout the graph. + */ + layout() { + this.graph = new graphlib.Graph({ + directed: true, + multigraph: true + }); + + if (this.layoutSettings !== null) { + this.graph.setGraph({ + edgesep: this.layoutSettings.edgesep, + nodesep: this.layoutSettings.nodesep, + ranksep: this.layoutSettings.ranksep + }); + } else { + this.graph.setGraph({ + edgesep: 25 + }); + } + + // add nodes to the graph + this.data.nodes.forEach(node => { + this.graph.setNode(node.toString(), { + shape: 'circle', + label: node.toString(), + width: 25, + labelStyle: STYLE.nodeLabel, + style: node === this.data.initNode ? STYLE.initNode : STYLE.node + }) + }); + + // another format of a graph for merged multi edges + // graph = {: {: , ...}, ...} + const graph = {}; + + // build data structure for the alternative representation by + // pushing some data + this.data.edges.forEach(edge => { + if (!graph[edge.from]) { + graph[edge.from] = {}; + graph[edge.from][edge.to] = [edge.input + "/" + edge.output]; + } else { + if (!graph[edge.from][edge.to]) { + graph[edge.from][edge.to] = [edge.input + "/" + edge.output]; + } else { + graph[edge.from][edge.to].push(edge.input + "/" + edge.output); + } + } + }); + + // add edges to the rendered graph and combine + _.forEach(graph, (k, from) => { + _.forEach(k, (labels, to) => { + this.graph.setEdge(from, to, { + label: labels.join('\n'), + labeloffset: 5, + lineInterpolate: 'basis', + style: STYLE.edge, + labelStyle: STYLE.edgeLabel + }, (from + '' + to)); + }); + }); + + // layout with dagre + dagre.layout(this.graph, {}); + } + + /** + * Render the graph to the svg. + */ + render() { + + // clear the svg so that there aren't rendered multiple hypotheses + this.svgGroup.innerHTML = ''; + + // render the graph in the svg + this.renderer(d3.select(this.svgGroup), this.graph); + + // Center graph horizontally + const xCenterOffset = (this.svgContainer.clientWidth - this.graph.graph().width) / 2; + this.svgGroup.setAttribute("transform", "translate(" + xCenterOffset + ", 100)"); + + // swap defs and paths children of .edgepaths because arrows are not shown + // on export otherwise <.< + _.forEach(this.svg.querySelectorAll('.edgePath'), edgePath => { + edgePath.insertBefore(edgePath.childNodes[1], edgePath.firstChild); + }); + } + + /** + * Create click and zoom events. + */ + handleEvents() { + const $scope = this.$scope; + const EventBus = this.EventBus; + + // attach click events for the selection of counter examples to the edge labels + // only if counterExamples is defined + if (this.isSelectable) { + d3.select(this.svg).selectAll('.edgeLabel tspan').on('click', function () { + const label = this.innerHTML.split('/'); // separate abbreviation from output + $scope.$apply(() => { + EventBus.emit(events.HYPOTHESIS_LABEL_SELECTED, { + input: label[0], + output: label[1] + }); + }); + }); + } + + // Create and handle zoom & pan event + const zoom = d3.behavior.zoom() + .scaleExtent([0.1, 10]) + .translate([(this.svgContainer.clientWidth - this.graph.graph().width) / 2, 100]) + .on("zoom", () => { + this.svgGroup.setAttribute('transform', `translate(${zoom.translate()}) scale(${zoom.scale()})`); + }); + + zoom(d3.select(this.svg)); + + // prevent hypothesis not to be rendered instantly + window.setTimeout(() => { + window.dispatchEvent(new Event('resize')); + }, 100); + } +} + +export const hypothesis = { + template: ` +
    + + + +
    + `, + bindings: { + data: '=', + layoutSettings: '=', + isSelectable: '@' + }, + controller: HypothesisComponent, + controllerAs: 'vm' +}; \ No newline at end of file diff --git a/main/src/main/webapp/src/js/components/index.js b/main/src/main/webapp/src/js/components/index.js index e5a45b3b4..0c31403fd 100644 --- a/main/src/main/webapp/src/js/components/index.js +++ b/main/src/main/webapp/src/js/components/index.js @@ -66,6 +66,9 @@ import {observationTable} from './observationTable'; import {symbolListItem} from './symbolListItem'; import {symbolGroupListItem} from './symbolGroupListItem'; import {learnResultListItem} from './learnResultListItem'; +import {hypothesis} from './hypothesis'; +import {discriminationTree} from './discriminationTree'; +import {htmlElementPicker} from './htmlElementPicker'; const moduleName = 'ALEX.components'; @@ -113,6 +116,8 @@ angular .component('alex', alex) .component('actionBar', actionBar) .component('checkbox', checkbox) + .component('hypothesis', hypothesis) + .component('discriminationTree', discriminationTree) .component('checkboxMultiple', checkboxMultiple) .component('fileDropzone', fileDropzone) .component('loadScreen', loadScreen) @@ -120,6 +125,7 @@ angular .component('sidebar', sidebar) .component('responsiveIframe', responsiveIframe) .component('viewHeader', viewHeader) + .component('htmlElementPicker', htmlElementPicker) .component('learnResultPanel', learnResultPanel) .component('observationTable', observationTable) .component('symbolListItem', symbolListItem) diff --git a/main/src/main/webapp/src/js/components/views/statisticsView.js b/main/src/main/webapp/src/js/components/views/statisticsView.js index 72e452506..5c3b5ed9f 100644 --- a/main/src/main/webapp/src/js/components/views/statisticsView.js +++ b/main/src/main/webapp/src/js/components/views/statisticsView.js @@ -56,9 +56,6 @@ class StatisticsView { this.LearnResultResource.getAll(this.project.id) .then(results => { this.results = results; - }) - .catch(response => { - this.ToastService.danger(`The results could not be loaded. ${response.data.message}`); }); } diff --git a/main/src/main/webapp/src/js/directives/discriminationTree.js b/main/src/main/webapp/src/js/directives/discriminationTree.js deleted file mode 100644 index 061c65713..000000000 --- a/main/src/main/webapp/src/js/directives/discriminationTree.js +++ /dev/null @@ -1,222 +0,0 @@ -/* - * Copyright 2016 TU Dortmund - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import _ from 'lodash'; -import {graphlib, dagre, render as Renderer} from 'dagre-d3'; -import d3 from 'd3/d3'; - -/** - * The directive for displaying a discrimination tree in an svg element. Can be used as an attribute or an element. - * Expects another property 'data' which holds the string representation of the discrimination tree. - * - * Use it like: '' - * - * @param $window - angular $window object - * @returns {{scope: {data: string}, template: string, link: link}} - */ -// @ngInject -function discriminationTree($window) { - return { - scope: { - data: '=' - }, - template: ` -
    - -
    - `, - link: link - }; - - function link(scope, el) { - - var labelStyle = 'display: inline; font-weight: bold; line-height: 1; text-align: center; white-space: nowrap; vertical-align: baseline;'; - var labelStyleEdge = labelStyle + 'font-size: 10px'; - var labelStyleNode = labelStyle + 'font-size: 12px'; - - // the svg where the discrimination tree is drawn into - var svg = d3.select(el.find('svg')[0]); - - // the first g node of the svg for rendering - var svgGroup = d3.select(el.find('svg').find('g')[0]); - - // the parent of the svg to fit its size accordingly - var svgContainer = svg[0].parentNode; - - // render the new discrimination tree when property 'data' changes - scope.$watch('data', function (newValue) { - if (angular.isDefined(newValue)) { - var data = angular.fromJson(newValue); - var graph = createGraph(data); - var layoutedGraph = layout(graph); - renderDT(layoutedGraph); - } - }); - - /** - * Creates a graph structure from a discrimination tree in order to layout it with the given dagreD3 library - * - * @param {Object} dt - The discrimination tree - * @returns {{nodes: Array, edges: Array}} - The tree as graph representation - */ - function createGraph(dt) { - - var nodes = []; - var edges = []; - - function createGraphData(node, parent) { - - // root without children - if (!node.children && parent === null) { - nodes.push(node.data); - return; - } - - // is leaf? - if (node.children.length === 0) { - return; - } - - // add node if not exists - if (!_.find(nodes, node.discriminator)) { - nodes.push(node.discriminator); - } - - if (parent !== null) { - edges.push({ - from: parent.discriminator, - to: node.discriminator, - label: node.edgeLabel - }); - } - - node.children.forEach(child => { - if (child.data) { - nodes.push(child.data); - edges.push({ - from: node.discriminator, - to: child.data, - label: child.edgeLabel - }); - } - }); - - node.children.forEach(child => { - if (child.discriminator) createGraphData(child, node); - }); - } - - createGraphData(dt, null); - - return { - nodes: nodes, - edges: edges - }; - } - - /** - * Creates positions for nodes and edges of the discrimination tree that can be rendered with dagreD3 - * - * @param {Object} graph - The discrimination tree as graph - * @returns {exports.Graph} - The graph with positions of nodes - */ - function layout(graph) { - - // initialize graph - var _graph = new graphlib.Graph({ - directed: true - }); - _graph.setGraph({}); - - // add nodes to the graph - _.forEach(graph.nodes, function (node) { - _graph.setNode(node, { - shape: node[0] === 'q' ? 'rect' : 'circle', // draw a rectangle when node is a leaf - label: node, - width: 25, - style: 'fill: #fff; stroke: #000; stroke-width: 1', - labelStyle: labelStyleNode - }); - }); - - //add edges to the graph - _.forEach(graph.edges, function (edge) { - _graph.setEdge(edge.from, edge.to, { - lineInterpolate: 'basis', - style: "stroke: rgba(0, 0, 0, 0.3); stroke-width: 3; fill:none", - label: edge.label, - labelStyle: labelStyleEdge - }); - }); - - // layout - dagre.layout(_graph, {}); - - return _graph; - } - - /** - * Renders the discrimination tree in the svg with the dagreD3 library - * - * @param {exports.Graph} graph - The graph with position information - */ - function renderDT(graph) { - - // render the graph - new Renderer()(svgGroup, graph); - - // position it in the center of the svg parent - var xCenterOffset = (svgContainer.clientWidth - graph.graph().width) / 2; - svgGroup.attr("transform", "translate(" + xCenterOffset + ", 100)"); - - // swap defs and paths children of .edgepaths because arrows are not shown - // on export otherwise <.< - _.forEach(el.find('svg')[0].querySelectorAll('.edgePath'), function (edgePath) { - edgePath.insertBefore(edgePath.childNodes[1], edgePath.firstChild); - }); - - // Create and handle zoom & pan event - var zoom = d3.behavior.zoom().scaleExtent([0.1, 10]) - .translate([(svgContainer.clientWidth - graph.graph().width) / 2, 100]).on("zoom", zoomHandler); - zoom(svg); - - function zoomHandler() { - svgGroup.attr('transform', 'translate(' + zoom.translate() - + ')' + ' scale(' + zoom.scale() + ')'); - } - - // resize the svg to its parents size on window resize - // and call it once so that svg gets the proper dimensions - $window.addEventListener('resize', fitSize); - scope.$on('$destroy', function () { - $window.removeEventListener('resize', fitSize); - }); - - function fitSize() { - svg.attr("width", svgContainer.clientWidth); - svg.attr("height", svgContainer.clientHeight); - } - - // in order to prevent only a white screen in some browsers, firing a resize event on the window - // displays the svg contents - window.setTimeout(() => { - window.dispatchEvent(new Event('resize')); - }, 100); - } - } -} - -export default discriminationTree; \ No newline at end of file diff --git a/main/src/main/webapp/src/js/directives/dropdownHover.js b/main/src/main/webapp/src/js/directives/dropdownHover.js index e2bef28c8..33a6d139b 100644 --- a/main/src/main/webapp/src/js/directives/dropdownHover.js +++ b/main/src/main/webapp/src/js/directives/dropdownHover.js @@ -21,7 +21,7 @@ * * @return {{require: string, link: link}} */ -function dropdownHover() { +export function dropdownHover() { return { require: '^uibDropdown', link: link @@ -40,6 +40,4 @@ function dropdownHover() { }); }); } -} - -export default dropdownHover; \ No newline at end of file +} \ No newline at end of file diff --git a/main/src/main/webapp/src/js/directives/htmlElementPicker.js b/main/src/main/webapp/src/js/directives/htmlElementPicker.js deleted file mode 100644 index 790aaabec..000000000 --- a/main/src/main/webapp/src/js/directives/htmlElementPicker.js +++ /dev/null @@ -1,307 +0,0 @@ -/* - * Copyright 2016 TU Dortmund - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// the promise that is used to communicate between the picker and the handle -let deferred = null; - -// the url that was opened until the picker got closed -let lastUrl = null; - -/** - * The directive that creates a new HTML element picker. Can only be used as an attribute and attaches a click - * event to the source element that opens the picker. On first start, it loads the page that is defined in the - * projects baseUrl. On following calls the last visited page is loaded. - * - * Attribute 'model' is the model for the XPath - * Attribute 'text' is the model for the .textContent value of the selected element - * - * Use: '' - * - * @param $document - * @param $compile - * @param $q - * @returns {{restrict: string, scope: {selectorModel: string}, link: Function}} - */ -// @ngInject -function htmlElementPicker($document, $compile, $q) { - return { - restrict: 'A', - scope: { - selectorModel: '=model', - textModel: '=text' - }, - link: link - }; - - function link(scope, el) { - - // The HTML picker element that is dynamically appended and removed to/from the pages DOM tree - let picker; - - el.on('click', () => { - - // create a new element picker under the current scope and append to the body - picker = $compile('')(scope); - $document.find('body').prepend(picker); - - deferred = $q.defer(); - deferred.promise - .then(data => { - - // copy the selected XPath and .textContent value to the scopes models - if (angular.isDefined(scope.selectorModel)) { - scope.selectorModel = data.xPath; - } - if (angular.isDefined(scope.textModel)) { - scope.textModel = data.textContent; - } - }) - .finally(() => { - picker.remove(); - }); - }); - } -} - -/** - * The actual HTML element picker. Handles the complete window including the selection of elements and loading - * of urls. Works as a 'mini embedded browser' - * - * Use: '' - * - * @param $window - angular window wrapper - * @param SessionService - The SessionService - * @returns {{scope: {}, templateUrl: string, link: link}} - */ -// @ngInject -function htmlElementPickerWindow($window, SessionService) { - return { - restrict: 'E', - scope: {}, - templateUrl: 'html/directives/html-element-picker.html', - link: link - }; - - function link(scope, el) { - - // the iframe where the projects site gets loaded into - var iframe = el.find('iframe'); - - // when moving with the mouse over an element, this elements gets saved in this variable in order to - // prevent multiple calls of getCssPath for the same element - var lastTarget = null; - - // the url of the proxy - var proxyUrl = null; - - /** - * flag for selection mode - * @type {boolean} - */ - scope.isSelectable = false; - - /** - * The XPath of the selected element - * @type {null|string} - */ - scope.selector = null; - - /** - * The element.textContent value - * @type {null|string} - */ - scope.textContent = null; - - /** - * The url that is loaded in the iframe - * @type {string} - */ - scope.url = null; - - /** - * The project in the session - * @type {null|Project} - */ - scope.project = null; - - /** - * Get the unique CSS XPath from selected Element - * http://stackoverflow.com/questions/4588119/get-elements-css-selector-without-element-id - * - * @param el - The element to get the unique css path from - * @returns {String} - The unique css path ot the element - * @private - */ - function getCssPath(el) { - - var names = []; - while (el.parentNode) { - if (el.id) { - names.unshift('#' + el.id); - break; - } else { - if (el == el.ownerDocument.documentElement) names.unshift(el.tagName); - else { - for (var c = 1, e = el; e.previousElementSibling; e = e.previousElementSibling, c++); - names.unshift(el.tagName + ":nth-child(" + c + ")"); - } - el = el.parentNode; - } - } - return names.join(" > "); - } - - /** - * Saves the element that is under the cursor so that it can be selected. Adds an outline to the element - * in order to highlight it. - * - * @param e - js event - * @returns {boolean} - */ - function handleMouseMove(e) { - if (lastTarget === e.target) { - return false; - } else { - if (lastTarget !== null) { - lastTarget.style.outline = '0px'; - } - lastTarget = e.target; - } - lastTarget.style.outline = '5px solid red'; - scope.selector = getCssPath(lastTarget); - - if (lastTarget.nodeName.toLowerCase() === 'input') { - scope.textContent = lastTarget.value; - } else { - scope.textContent = lastTarget.textContent; - } - - scope.$apply(); - } - - /** - * Removes the outline from the selected element, removes all events from the iframe and removes the - * keypress event. When this function is called the selected element is fixed and won't change by any - * further interaction with the iframe - * - * @param e - js event - */ - function handleClick(e) { - if (angular.isDefined(e)) { - e.preventDefault(); - e.stopPropagation(); - } - - if (lastTarget !== null) { - lastTarget.style.outline = '0px'; - } - lastTarget = null; - - angular.element(iframe.contents()[0].body).off('mousemove', handleMouseMove); - angular.element(iframe.contents()[0].body).off('click', handleClick); - angular.element(document.body).off('keyup', handleKeyUp); - } - - /** - * Calls handleClick() when control key is pressed to have an alternative for selecting a dom node without - * firing any click events on it. - * - * @param e - */ - function handleKeyUp(e) { - if (e.keyCode == 17) { // strg - handleClick(); - scope.isSelectable = false; - } - } - - // load project, create proxy address and load the last url in the iframe - function init() { - scope.project = SessionService.getProject(); - proxyUrl = 'rest/proxy?url='; - - scope.url = lastUrl; - scope.loadUrl(); - } - - /** - * Loads an entered url into the iframe and handles the click on every a element - */ - scope.loadUrl = function () { - iframe[0].setAttribute('src', proxyUrl + scope.project.baseUrl + '/' + (scope.url === null ? '' : scope.url)); - iframe[0].onload = function () { - angular.element(iframe.contents()[0].body.getElementsByTagName('a')) - .on('click', function () { - if (!scope.isSelectable) { - var _this = this; - if (_this.getAttribute('href') !== '' && _this.getAttribute('href')[0] !== '#') { - scope.$apply(function () { - scope.url = decodeURIComponent(_this.getAttribute('href')) - .replace(proxyUrl + scope.project.baseUrl + '/', ''); - }); - } - } - } - ); - }; - }; - - /** - * Enables the selection mode and therefore adds events to the iframe - */ - scope.toggleSelection = function () { - if (!scope.isSelectable) { - var iframeBody = angular.element(iframe.contents()[0].body); - iframeBody.on('mousemove', handleMouseMove); - iframeBody.one('click', function (e) { - handleClick(e); - scope.$apply(function () { - scope.isSelectable = false; - }); - }); - angular.element(document.body).on('keyup', handleKeyUp); - } else { - handleClick(); - scope.selector = null; - } - scope.isSelectable = !scope.isSelectable; - }; - - /** - * Makes the web element picker invisible and fires the close event - */ - scope.close = function () { - lastUrl = scope.url; - deferred.reject(); - }; - - /** - * Makes the web element Picker invisible and fires the ok event with the selector of the element that was - * selected. If no selector is defined, then it just closes the picker - */ - scope.ok = function () { - lastUrl = scope.url; - deferred.resolve({ - xPath: scope.selector, - textContent: scope.textContent - }); - }; - - init(); - } -} - -export {htmlElementPicker, htmlElementPickerWindow}; \ No newline at end of file diff --git a/main/src/main/webapp/src/js/directives/htmlElementPickerHandle.js b/main/src/main/webapp/src/js/directives/htmlElementPickerHandle.js new file mode 100644 index 000000000..9bf80f490 --- /dev/null +++ b/main/src/main/webapp/src/js/directives/htmlElementPickerHandle.js @@ -0,0 +1,68 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * The directive that creates a new HTML element picker. Can only be used as an attribute and attaches a click + * event to the source element that opens the picker. On first start, it loads the page that is defined in the + * projects baseUrl. On following calls the last visited page is loaded. + * + * Attribute 'model' is the model for the XPath + * Attribute 'text' is the model for the .textContent value of the selected element + * + * Use: '' + * + * @param $document + * @param $compile + * @param $q + * @param HtmlElementPickerService + * @returns {{restrict: string, scope: {selectorModel: string}, link: Function}} + */ +// @ngInject +export function htmlElementPickerHandle($document, $compile, $q, HtmlElementPickerService) { + return { + restrict: 'A', + scope: { + selectorModel: '=model', + textModel: '=text' + }, + link: link + }; + + function link(scope, el) { + el.on('click', () => { + + // create a new element picker under the current scope and append to the body + const picker = $compile('')(scope); + $document.find('body').prepend(picker); + + HtmlElementPickerService.deferred = $q.defer(); + HtmlElementPickerService.deferred.promise + .then(data => { + + // copy the selected selector and .textContent value to the scopes models + if (angular.isDefined(scope.selectorModel)) { + scope.selectorModel = data.selector; + } + if (angular.isDefined(scope.textModel)) { + scope.textModel = data.textContent; + } + }) + .finally(() => { + picker.remove(); + }); + }); + } +} \ No newline at end of file diff --git a/main/src/main/webapp/src/js/directives/hypothesis.js b/main/src/main/webapp/src/js/directives/hypothesis.js deleted file mode 100644 index bed593384..000000000 --- a/main/src/main/webapp/src/js/directives/hypothesis.js +++ /dev/null @@ -1,237 +0,0 @@ -/* - * Copyright 2016 TU Dortmund - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import _ from 'lodash'; -import {graphlib, dagre, render as Renderer} from 'dagre-d3'; -import d3 from 'd3/d3'; - -import {events} from '../constants'; - -/** - * The directive that is used to display hypotheses. - * - * Attribute 'isSelectable' should only be true if it should be possible to select input output pairs from the - * hypothesis - * - * Attribute 'layoutSettings' is optional. - * - * Use: - * - * @param $window - * @param EventBus - * @returns {{scope: {result: string, layoutSettings: string, isSelectable: string}, template: string, link: link}} - */ -// @ngInject -function hypothesis($window, EventBus) { - return { - scope: { - result: '=', - layoutSettings: '=', - isSelectable: '@' - }, - template: ` -
    - -
    - `, - link: link - }; - function link(scope, el) { - var _svg; // the svg element - var _svgGroup; // the first g element in the svg the hypothesis is rendered into - var _svgContainer; // the parent of the svg element - var _graph; // the graphlib graph object that represents the hypothesis - var _renderer; // the dagreD3 renderer that renders _graph in _svgGroup - - var styles = { - edge: 'stroke: rgba(0, 0, 0, 0.3); stroke-width: 3; fill:none', - edgeLabel: 'display: inline; font-weight: bold; line-height: 1; text-align: center; white-space: nowrap; vertical-align: baseline; font-size: 10px', - nodeLabel: 'display: inline; font-weight: bold; line-height: 1; text-align: center; white-space: nowrap; vertical-align: baseline; font-size: 12px', - node: 'fill: #fff; stroke: #000; stroke-width: 1', - initNode: 'fill: #B3E6B3; stroke: #5cb85c; stroke-width: 3' - }; - - scope.$watch('result', result => { - if (angular.isDefined(result) && result !== null) { - createHypothesis(); - } - }); - - scope.$watch('layoutSettings', ls => { - if (angular.isDefined(ls)) { - createHypothesis(); - } - }); - - // create the hypothesis. Call this on re-render - function createHypothesis() { - clearSvg(); - init(); - layout(); - render(); - handleEvents(); - } - - function clearSvg() { - el.find('svg')[0].innerHTML = ''; - } - - function init() { - _svg = d3.select(el.find('svg')[0]); - _svgGroup = _svg.append("g"); - _svgContainer = _svg.node().parentNode; - - _svg.style({ - 'font-family': '"Helvetica Neue",Helvetica,Arial,sans-serif', - 'font-size': '12px', - 'line-height': '1.42857', - 'color': '#333' - }); - - _graph = new graphlib.Graph({ - directed: true, - multigraph: true - }); - - if (scope.layoutSettings !== null) { - _graph.setGraph({ - edgesep: scope.layoutSettings.edgesep, - nodesep: scope.layoutSettings.nodesep, - ranksep: scope.layoutSettings.ranksep - }); - } else { - _graph.setGraph({ - edgesep: 25 - }); - } - } - - function layout() { - function createEdgeObject(label) { - return { - label: label, - labeloffset: 5, - lineInterpolate: 'basis', - style: styles.edge, - labelStyle: styles.edgeLabel - }; - } - - // add nodes to the graph - for (var i = 0; i < scope.result.hypothesis.nodes.length; i++) { - let node = scope.result.hypothesis.nodes[i]; - _graph.setNode(node.toString(), { - shape: 'circle', - label: node.toString(), - width: 25, - labelStyle: styles.nodeLabel, - style: node === scope.result.hypothesis.initNode ? styles.initNode : styles.node - }); - } - - // another format of a graph for merged multi edges - // graph = {: {: , ...}, ...} - var graph = {}; - - // build data structure for the alternative representation by - // pushing some data - scope.result.hypothesis.edges.forEach(edge => { - if (!graph[edge.from]) { - graph[edge.from] = {}; - graph[edge.from][edge.to] = [edge.input + "/" + edge.output]; - } else { - if (!graph[edge.from][edge.to]) { - graph[edge.from][edge.to] = [edge.input + "/" + edge.output]; - } else { - graph[edge.from][edge.to].push(edge.input + "/" + edge.output); - } - } - }); - - // add edges to the rendered graph and combine - _.forEach(graph, function (k, from) { - _.forEach(k, function (labels, to) { - _graph.setEdge(from, to, createEdgeObject(labels.join('\n')), (from + '' + to)); - }); - }); - - // layout with dagre - dagre.layout(_graph, {}); - } - - function render() { - - // render the graph in the svg - _renderer = new Renderer(); - _renderer(_svgGroup, _graph); - - // Center graph horizontally - var xCenterOffset = (_svgContainer.clientWidth - _graph.graph().width) / 2; - _svgGroup.attr("transform", "translate(" + xCenterOffset + ", 100)"); - - // swap defs and paths children of .edgepaths because arrows are not shown - // on export otherwise <.< - _.forEach(el.find('svg')[0].querySelectorAll('.edgePath'), function (edgePath) { - edgePath.insertBefore(edgePath.childNodes[1], edgePath.firstChild); - }); - } - - function handleEvents() { - - // attach click events for the selection of counter examples to the edge labels - // only if counterExamples is defined - if (angular.isDefined(scope.isSelectable)) { - _svg.selectAll('.edgeLabel tspan').on('click', function () { - const label = this.innerHTML.split('/'); // separate abbreviation from output - scope.$apply(() => { - EventBus.emit(events.HYPOTHESIS_LABEL_SELECTED, { - input: label[0], - output: label[1] - }); - }); - }); - } - - // Create and handle zoom & pan event - const zoom = d3.behavior.zoom() - .scaleExtent([0.1, 10]) - .translate([(_svgContainer.clientWidth - _graph.graph().width) / 2, 100]) - .on("zoom", () => { - _svgGroup.attr('transform', `translate(${zoom.translate()}) scale(${zoom.scale()})`); - }); - - zoom(_svg); - - // do this whole stuff so that the size of the svg adjusts to the window - $window.addEventListener('resize', fitSize); - scope.$on('$destroy', () => { - $window.removeEventListener('resize', fitSize); - }); - - function fitSize() { - _svg.attr("width", _svgContainer.clientWidth); - _svg.attr("height", _svgContainer.clientHeight); - } - - // prevent hypothesis not to be rendered instantly - window.setTimeout(() => { - window.dispatchEvent(new Event('resize')); - }, 100); - } - } -} - -export default hypothesis; \ No newline at end of file diff --git a/main/src/main/webapp/src/js/directives/index.js b/main/src/main/webapp/src/js/directives/index.js index 59fcbb16c..4870aa259 100644 --- a/main/src/main/webapp/src/js/directives/index.js +++ b/main/src/main/webapp/src/js/directives/index.js @@ -14,10 +14,8 @@ * limitations under the License. */ -import discriminationTree from './discriminationTree'; -import dropdownHover from './dropdownHover'; -import {htmlElementPicker, htmlElementPickerWindow} from './htmlElementPicker'; -import hypothesis from './hypothesis'; +import {dropdownHover} from './dropdownHover'; +import {htmlElementPickerHandle} from './htmlElementPickerHandle'; // modal handles import {actionCreateModalHandle} from './modals/actionCreateModalHandle'; @@ -38,11 +36,8 @@ const moduleName = 'ALEX.directives'; angular .module(moduleName, []) - .directive('discriminationTree', discriminationTree) .directive('dropdownHover', dropdownHover) - .directive('htmlElementPicker', htmlElementPicker) - .directive('htmlElementPickerWindow', htmlElementPickerWindow) - .directive('hypothesis', hypothesis) + .directive('htmlElementPickerHandle', htmlElementPickerHandle) // modal handles .directive('actionCreateModalHandle', actionCreateModalHandle) diff --git a/main/src/main/webapp/src/js/services/HtmlElementPickerService.js b/main/src/main/webapp/src/js/services/HtmlElementPickerService.js new file mode 100644 index 000000000..7a5d08b91 --- /dev/null +++ b/main/src/main/webapp/src/js/services/HtmlElementPickerService.js @@ -0,0 +1,41 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// the instance of the service +let instance = null; + +/** + * The service fot the html element picker + */ +export class HtmlElementPickerService { + constructor() { + if (instance !== null) return instance; + + /** + * The promise that is used to communicate between the picker and the handle + * @type {Promise|null} + */ + this.deferred = null; + + /** + * The url that was opened until the picker got closed. + * @type {string|null} + */ + this.lastUrl = null; + + instance = this; + } +} \ No newline at end of file diff --git a/main/src/main/webapp/src/js/services/index.js b/main/src/main/webapp/src/js/services/index.js index c526a24a7..27653a051 100644 --- a/main/src/main/webapp/src/js/services/index.js +++ b/main/src/main/webapp/src/js/services/index.js @@ -25,6 +25,7 @@ import {PromptService} from './PromptService'; import SessionService from './SessionService'; import ToastService from './ToastService'; import LearnerResultDownloadService from './LearnerResultDownloadService'; +import {HtmlElementPickerService} from './HtmlElementPickerService'; const moduleName = 'ALEX.services'; @@ -40,6 +41,7 @@ angular .service('PromptService', PromptService) .service('SessionService', SessionService) .service('ToastService', ToastService) - .service('LearnerResultDownloadService', LearnerResultDownloadService); + .service('LearnerResultDownloadService', LearnerResultDownloadService) + .service('HtmlElementPickerService', HtmlElementPickerService); export const services = moduleName; \ No newline at end of file From ea2109ea3151a2b069ddd002f3ea2cac2f4f6b78 Mon Sep 17 00:00:00 2001 From: Alexander Bainczyk Date: Sat, 21 May 2016 12:26:49 +0200 Subject: [PATCH 19/44] changed the display of symbol groups a bit moved a template to the correct directory --- .../symbol-list-item.html | 2 +- .../webapp/src/js/components/symbolListItem.js | 2 +- main/src/main/webapp/src/scss/style.scss | 17 ++++++++++++----- 3 files changed, 14 insertions(+), 7 deletions(-) rename main/src/main/webapp/src/html/{directives => components}/symbol-list-item.html (95%) diff --git a/main/src/main/webapp/src/html/directives/symbol-list-item.html b/main/src/main/webapp/src/html/components/symbol-list-item.html similarity index 95% rename from main/src/main/webapp/src/html/directives/symbol-list-item.html rename to main/src/main/webapp/src/html/components/symbol-list-item.html index bb1fc2e8d..b85c0c0b4 100644 --- a/main/src/main/webapp/src/html/directives/symbol-list-item.html +++ b/main/src/main/webapp/src/html/components/symbol-list-item.html @@ -7,7 +7,7 @@

    - []
    +  []
    Actions   diff --git a/main/src/main/webapp/src/js/components/symbolListItem.js b/main/src/main/webapp/src/js/components/symbolListItem.js index 5ec626548..103612e81 100644 --- a/main/src/main/webapp/src/js/components/symbolListItem.js +++ b/main/src/main/webapp/src/js/components/symbolListItem.js @@ -48,7 +48,7 @@ class SymbolListItem { } const symbolListItem = { - templateUrl: 'html/directives/symbol-list-item.html', + templateUrl: 'html/components/symbol-list-item.html', controller: SymbolListItem, controllerAs: 'vm', transclude: true, diff --git a/main/src/main/webapp/src/scss/style.scss b/main/src/main/webapp/src/scss/style.scss index 7d1349986..b384ad422 100644 --- a/main/src/main/webapp/src/scss/style.scss +++ b/main/src/main/webapp/src/scss/style.scss @@ -402,12 +402,23 @@ body { .symbol-group-list-item { margin-bottom: 20px; transition: opacity, margin 100ms; + border: 1px solid #dadada; + border-radius: 4px; + box-shadow: 0 2px 2px rgba(0,0,0,.075); + + .symbol-list-item { + border-bottom: 0; + border-top: 1px solid #ddd; + padding: 8px 10px; + } .symbol-group-list-item-header { display: flex; flex-direction: row; - border-bottom: 1px solid #ddd; padding: 10px; + background: #f2f2f2; + border-top-left-radius: 4px; + border-top-right-radius: 4px; .symbol-group-title { font-size: 16px; @@ -422,10 +433,6 @@ body { } } } - - .symbol-group-list-item-body { - padding-left: 20px; - } } } From 84b1eae0fc85e180e24fceaffdd4f5cfd918ce33 Mon Sep 17 00:00:00 2001 From: Alexander Schieweck Date: Sat, 28 May 2016 15:29:58 +0200 Subject: [PATCH 20/44] (Re-)Added the link to the Swagger Doc. --- main/src/main/webapp/src/html/pages/about.html | 2 ++ 1 file changed, 2 insertions(+) diff --git a/main/src/main/webapp/src/html/pages/about.html b/main/src/main/webapp/src/html/pages/about.html index e7aba4bc9..7f32dbf8f 100644 --- a/main/src/main/webapp/src/html/pages/about.html +++ b/main/src/main/webapp/src/html/pages/about.html @@ -38,6 +38,8 @@

    Help

    GitHub Repository and the GitHub page which includes a user manual.

    + + View the docs
    From c97d26c655e3db2ce0970d360a5be8d3721a3d9a Mon Sep 17 00:00:00 2001 From: Alexander Bainczyk Date: Sun, 29 May 2016 10:30:08 +0200 Subject: [PATCH 21/44] rearranged frontend imports fixed display of symbol lists in trash & history view --- .../src/html/pages/symbols-history.html | 4 +- .../webapp/src/html/pages/symbols-trash.html | 4 +- .../main/webapp/src/js/components/index.js | 135 ---------- .../main/webapp/src/js/directives/index.js | 57 ----- .../webapp/src/js/{filters => }/filters.js | 16 +- main/src/main/webapp/src/js/filters/index.js | 29 --- main/src/main/webapp/src/js/index.js | 236 +++++++++++++++--- .../src/main/webapp/src/js/resources/index.js | 39 --- main/src/main/webapp/src/js/services/index.js | 47 ---- 9 files changed, 218 insertions(+), 349 deletions(-) delete mode 100644 main/src/main/webapp/src/js/components/index.js delete mode 100644 main/src/main/webapp/src/js/directives/index.js rename main/src/main/webapp/src/js/{filters => }/filters.js (92%) delete mode 100644 main/src/main/webapp/src/js/filters/index.js delete mode 100644 main/src/main/webapp/src/js/resources/index.js delete mode 100644 main/src/main/webapp/src/js/services/index.js diff --git a/main/src/main/webapp/src/html/pages/symbols-history.html b/main/src/main/webapp/src/html/pages/symbols-history.html index 02209a2a4..37156eafa 100644 --- a/main/src/main/webapp/src/html/pages/symbols-history.html +++ b/main/src/main/webapp/src/html/pages/symbols-history.html @@ -9,7 +9,7 @@
    - +
    Latest @@ -21,7 +21,7 @@ - +

    diff --git a/main/src/main/webapp/src/html/pages/symbols-trash.html b/main/src/main/webapp/src/html/pages/symbols-trash.html index 144b95b9d..65355c1d2 100644 --- a/main/src/main/webapp/src/html/pages/symbols-trash.html +++ b/main/src/main/webapp/src/html/pages/symbols-trash.html @@ -21,7 +21,7 @@ There aren't any deleted symbols.
    - +
    @@ -30,7 +30,7 @@ - +
    diff --git a/main/src/main/webapp/src/js/components/index.js b/main/src/main/webapp/src/js/components/index.js deleted file mode 100644 index 0c31403fd..000000000 --- a/main/src/main/webapp/src/js/components/index.js +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright 2016 TU Dortmund - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// views -import {aboutView} from './views/aboutView'; -import {adminUsersView} from './views/adminUsersView'; -import {countersView} from './views/countersView'; -import {errorView} from './views/errorView'; -import {filesView} from './views/filesView'; -import {homeView} from './views/homeView'; -import {learnerSetupView} from './views/learnerSetupView'; -import {learnerStartView} from './views/learnerStartView'; -import {projectsView} from './views/projectsView'; -import {projectsDashboardView} from './views/projectsDashboardView'; -import {resultsCompareView} from './views/resultsCompareView'; -import {resultsView} from './views/resultsView'; -import {statisticsCompareView} from './views/statisticsCompareView'; -import {statisticsView} from './views/statisticsView'; -import {symbolsActionsView} from './views/symbolsActionsView'; -import {symbolsView} from './views/symbolsView'; -import {symbolsHistoryView} from './views/symbolsHistoryView'; -import {symbolsImportView} from './views/symbolsImportView'; -import {symbolsTrashView} from './views/symbolsTrashView'; -import {usersSettingsView} from './views/usersSettingsView'; - -// forms -import actionCreateEditForm from './forms/actionCreateEditForm'; -import projectCreateForm from './forms/projectCreateForm'; -import userEditForm from './forms/userEditForm'; -import userLoginForm from './forms/userLoginForm'; -import userRegisterForm from './forms/userRegisterForm'; - -// widgets -import widget from './widgets/widget'; -import projectDetailsWidget from './widgets/projectDetailsWidget'; -import learnResumeSettingsWidget from './widgets/learnResumeSettingsWidget'; -import learnerStatusWidget from './widgets/learnerStatusWidget'; -import latestLearnResultWidget from './widgets/latestLearnResultWidget'; -import counterexamplesWidget from './widgets/counterexamplesWidget'; - -// misc -import alex from './alex'; -import {actionBar} from './actionBar'; -import {checkbox, checkboxMultiple} from './checkbox'; -import {fileDropzone} from './fileDropzone'; -import loadScreen from './loadScreen'; -import projectList from './projectList'; -import {sidebar} from './sidebar'; -import viewHeader from './viewHeader'; -import {responsiveIframe} from './responsiveIframe'; -import {learnResultPanel} from './learnResultPanel'; -import {observationTable} from './observationTable'; -import {symbolListItem} from './symbolListItem'; -import {symbolGroupListItem} from './symbolGroupListItem'; -import {learnResultListItem} from './learnResultListItem'; -import {hypothesis} from './hypothesis'; -import {discriminationTree} from './discriminationTree'; -import {htmlElementPicker} from './htmlElementPicker'; - -const moduleName = 'ALEX.components'; - -angular - .module(moduleName, []) - - // views - .component('aboutView', aboutView) - .component('adminUsersView', adminUsersView) - .component('countersView', countersView) - .component('errorView', errorView) - .component('filesView', filesView) - .component('homeView', homeView) - .component('learnerSetupView', learnerSetupView) - .component('learnerStartView', learnerStartView) - .component('projectsView', projectsView) - .component('projectsDashboardView', projectsDashboardView) - .component('resultsCompareView', resultsCompareView) - .component('resultsView', resultsView) - .component('statisticsCompareView', statisticsCompareView) - .component('statisticsView', statisticsView) - .component('symbolsActionsView', symbolsActionsView) - .component('symbolsView', symbolsView) - .component('symbolsHistoryView', symbolsHistoryView) - .component('symbolsImportView', symbolsImportView) - .component('symbolsTrashView', symbolsTrashView) - .component('usersSettingsView', usersSettingsView) - - // forms - .component('actionCreateEditForm', actionCreateEditForm) - .component('projectCreateForm', projectCreateForm) - .component('userEditForm', userEditForm) - .component('userLoginForm', userLoginForm) - .component('userRegisterForm', userRegisterForm) - - // widgets - .component('widget', widget) - .component('counterexamplesWidget', counterexamplesWidget) - .component('learnResumeSettingsWidget', learnResumeSettingsWidget) - .component('learnerStatusWidget', learnerStatusWidget) - .component('latestLearnResultWidget', latestLearnResultWidget) - .component('projectDetailsWidget', projectDetailsWidget) - - // misc - .component('alex', alex) - .component('actionBar', actionBar) - .component('checkbox', checkbox) - .component('hypothesis', hypothesis) - .component('discriminationTree', discriminationTree) - .component('checkboxMultiple', checkboxMultiple) - .component('fileDropzone', fileDropzone) - .component('loadScreen', loadScreen) - .component('projectList', projectList) - .component('sidebar', sidebar) - .component('responsiveIframe', responsiveIframe) - .component('viewHeader', viewHeader) - .component('htmlElementPicker', htmlElementPicker) - .component('learnResultPanel', learnResultPanel) - .component('observationTable', observationTable) - .component('symbolListItem', symbolListItem) - .component('symbolGroupListItem', symbolGroupListItem) - .component('learnResultListItem', learnResultListItem); - -export const components = moduleName; \ No newline at end of file diff --git a/main/src/main/webapp/src/js/directives/index.js b/main/src/main/webapp/src/js/directives/index.js deleted file mode 100644 index 4870aa259..000000000 --- a/main/src/main/webapp/src/js/directives/index.js +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2016 TU Dortmund - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import {dropdownHover} from './dropdownHover'; -import {htmlElementPickerHandle} from './htmlElementPickerHandle'; - -// modal handles -import {actionCreateModalHandle} from './modals/actionCreateModalHandle'; -import {actionEditModalHandle} from './modals/actionEditModalHandle'; -import hypothesisLayoutSettingsModalHandle from './modals/hypothesisLayoutSettingsModalHandle'; -import learnResultDetailsModalHandle from './modals/learnResultDetailsModalHandle'; -import learnSetupSettingsModalHandle from './modals/learnSetupSettingsModalHandle'; -import {projectSettingsModalHandle} from './modals/projectSettingsModalHandle'; -import {symbolCreateModalHandle} from './modals/symbolCreateModalHandle'; -import {symbolEditModalHandle} from './modals/symbolEditModalHandle'; -import {symbolGroupCreateModalHandle} from './modals/symbolGroupCreateModalHandle'; -import {symbolGroupEditModalHandle} from './modals/symbolGroupEditModalHandle'; -import symbolMoveModalHandle from './modals/symbolMoveModalHandle'; -import {userEditModalHandle} from './modals/userEditModalHandle'; -import {resultListModalHandle} from './modals/resultListModalHandle'; - -const moduleName = 'ALEX.directives'; - -angular - .module(moduleName, []) - .directive('dropdownHover', dropdownHover) - .directive('htmlElementPickerHandle', htmlElementPickerHandle) - - // modal handles - .directive('actionCreateModalHandle', actionCreateModalHandle) - .directive('actionEditModalHandle', actionEditModalHandle) - .directive('hypothesisLayoutSettingsModalHandle', hypothesisLayoutSettingsModalHandle) - .directive('learnResultDetailsModalHandle', learnResultDetailsModalHandle) - .directive('learnSetupSettingsModalHandle', learnSetupSettingsModalHandle) - .directive('projectSettingsModalHandle', projectSettingsModalHandle) - .directive('symbolCreateModalHandle', symbolCreateModalHandle) - .directive('symbolEditModalHandle', symbolEditModalHandle) - .directive('symbolGroupCreateModalHandle', symbolGroupCreateModalHandle) - .directive('symbolGroupEditModalHandle', symbolGroupEditModalHandle) - .directive('symbolMoveModalHandle', symbolMoveModalHandle) - .directive('userEditModalHandle', userEditModalHandle) - .directive('resultListModalHandle', resultListModalHandle); - -export const directives = moduleName; \ No newline at end of file diff --git a/main/src/main/webapp/src/js/filters/filters.js b/main/src/main/webapp/src/js/filters.js similarity index 92% rename from main/src/main/webapp/src/js/filters/filters.js rename to main/src/main/webapp/src/js/filters.js index 26fd2e9d6..0aea700ca 100644 --- a/main/src/main/webapp/src/js/filters/filters.js +++ b/main/src/main/webapp/src/js/filters.js @@ -14,13 +14,13 @@ * limitations under the License. */ -import {learnAlgorithm, eqOracleType, userRole, webBrowser} from '../constants'; +import {learnAlgorithm, eqOracleType, userRole, webBrowser} from './constants'; /** * The filter to format a EQ type constant to something more readable * @returns {Function} */ -function formatEqOracle() { +export function formatEqOracle() { return type => { switch (type) { case eqOracleType.RANDOM: @@ -42,7 +42,7 @@ function formatEqOracle() { * The filter to format a user role * @returns {Function} */ -function formatUserRole() { +export function formatUserRole() { return role => { switch (role) { case userRole.ADMIN: @@ -60,7 +60,7 @@ function formatUserRole() { * Formats the web browser dictionary * @returns {Function} */ -function formatWebBrowser() { +export function formatWebBrowser() { return browser => { switch (browser) { case webBrowser.HTMLUNITDRIVER: @@ -82,7 +82,7 @@ function formatWebBrowser() { * The filter to format a learn algorithm name to something more readable * @returns {Function} */ -function formatAlgorithm() { +export function formatAlgorithm() { return name => { switch (name) { case learnAlgorithm.LSTAR: @@ -103,7 +103,7 @@ function formatAlgorithm() { * The filter takes a number representing milliseconds and formats it to [h] [min] s * @returns {Function} */ -function formatMilliseconds() { +export function formatMilliseconds() { return ms => { let hours, minutes, seconds; @@ -120,6 +120,4 @@ function formatMilliseconds() { return Math.floor(ms / 1000) + 's'; } }; -} - -export {formatAlgorithm, formatEqOracle, formatMilliseconds, formatUserRole, formatWebBrowser}; \ No newline at end of file +} \ No newline at end of file diff --git a/main/src/main/webapp/src/js/filters/index.js b/main/src/main/webapp/src/js/filters/index.js deleted file mode 100644 index e2a97b78d..000000000 --- a/main/src/main/webapp/src/js/filters/index.js +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright 2016 TU Dortmund - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import * as filter from './filters'; - -const moduleName = 'ALEX.filters'; - -angular - .module(moduleName, []) - .filter('formatEqOracle', filter.formatEqOracle) - .filter('formatAlgorithm', filter.formatAlgorithm) - .filter('formatMilliseconds', filter.formatMilliseconds) - .filter('formatUserRole', filter.formatUserRole) - .filter('formatWebBrowser', filter.formatWebBrowser); - -export const filters = moduleName; \ No newline at end of file diff --git a/main/src/main/webapp/src/js/index.js b/main/src/main/webapp/src/js/index.js index c2d379f2e..25e0c49fe 100644 --- a/main/src/main/webapp/src/js/index.js +++ b/main/src/main/webapp/src/js/index.js @@ -14,25 +14,102 @@ * limitations under the License. */ -import angularDragula from 'angular-dragula'; -import ngFileUpload from 'ng-file-upload'; -import angularJwt from 'angular-jwt'; -import d3 from 'd3/d3'; -import uiBootstrap from 'angular-ui-bootstrap'; -import uiRouter from 'angular-ui-router'; -import toastr from 'angular-toastr'; -import ngAnimate from 'angular-animate'; -import ngMessages from 'angular-messages'; - -import {configuration} from './config'; -import {routes} from './routes'; -import * as constant from './constants'; - -import {directives} from './directives/index'; -import {filters} from './filters/index'; -import {resources} from './resources/index'; -import {services} from './services/index'; -import {components} from './components/index'; +import angularDragula from "angular-dragula"; +import ngFileUpload from "ng-file-upload"; +import angularJwt from "angular-jwt"; +import d3 from "d3/d3"; +import uiBootstrap from "angular-ui-bootstrap"; +import uiRouter from "angular-ui-router"; +import toastr from "angular-toastr"; +import ngAnimate from "angular-animate"; +import ngMessages from "angular-messages"; +import {configuration} from "./config"; +import {routes} from "./routes"; +import * as constant from "./constants"; +import CounterResource from "./resources/CounterResource"; +import FileResource from "./resources/FileResource"; +import LearnerResource from "./resources/LearnerResource"; +import LearnResultResource from "./resources/LearnResultResource"; +import ProjectResource from "./resources/ProjectResource"; +import SymbolGroupResource from "./resources/SymbolGroupResource"; +import SymbolResource from "./resources/SymbolResource"; +import UserResource from "./resources/UserResource"; +import ActionService from "./services/ActionService"; +import ClipboardService from "./services/ClipboardService"; +import ErrorService from "./services/ErrorService"; +import EventBus from "./services/EventBus"; +import EqOracleService from "./services/EqOracleService"; +import {DownloadService} from "./services/DownloadService"; +import LearnerResultChartService from "./services/LearnerResultChartService"; +import {PromptService} from "./services/PromptService"; +import SessionService from "./services/SessionService"; +import ToastService from "./services/ToastService"; +import LearnerResultDownloadService from "./services/LearnerResultDownloadService"; +import {HtmlElementPickerService} from "./services/HtmlElementPickerService"; +import * as filter from "./filters"; +import {actionCreateModalHandle} from "./directives/modals/actionCreateModalHandle"; +import {actionEditModalHandle} from "./directives/modals/actionEditModalHandle"; +import hypothesisLayoutSettingsModalHandle from "./directives/modals/hypothesisLayoutSettingsModalHandle"; +import learnResultDetailsModalHandle from "./directives/modals/learnResultDetailsModalHandle"; +import learnSetupSettingsModalHandle from "./directives/modals/learnSetupSettingsModalHandle"; +import {projectSettingsModalHandle} from "./directives/modals/projectSettingsModalHandle"; +import {symbolCreateModalHandle} from "./directives/modals/symbolCreateModalHandle"; +import {symbolEditModalHandle} from "./directives/modals/symbolEditModalHandle"; +import {symbolGroupCreateModalHandle} from "./directives/modals/symbolGroupCreateModalHandle"; +import {symbolGroupEditModalHandle} from "./directives/modals/symbolGroupEditModalHandle"; +import symbolMoveModalHandle from "./directives/modals/symbolMoveModalHandle"; +import {userEditModalHandle} from "./directives/modals/userEditModalHandle"; +import {resultListModalHandle} from "./directives/modals/resultListModalHandle"; +import {dropdownHover} from "./directives/dropdownHover"; +import {htmlElementPickerHandle} from "./directives/htmlElementPickerHandle"; +import {aboutView} from "./components/views/aboutView"; +import {adminUsersView} from "./components/views/adminUsersView"; +import {countersView} from "./components/views/countersView"; +import {errorView} from "./components/views/errorView"; +import {filesView} from "./components/views/filesView"; +import {homeView} from "./components/views/homeView"; +import {learnerSetupView} from "./components/views/learnerSetupView"; +import {learnerStartView} from "./components/views/learnerStartView"; +import {projectsView} from "./components/views/projectsView"; +import {projectsDashboardView} from "./components/views/projectsDashboardView"; +import {resultsCompareView} from "./components/views/resultsCompareView"; +import {resultsView} from "./components/views/resultsView"; +import {statisticsCompareView} from "./components/views/statisticsCompareView"; +import {statisticsView} from "./components/views/statisticsView"; +import {symbolsActionsView} from "./components/views/symbolsActionsView"; +import {symbolsView} from "./components/views/symbolsView"; +import {symbolsHistoryView} from "./components/views/symbolsHistoryView"; +import {symbolsImportView} from "./components/views/symbolsImportView"; +import {symbolsTrashView} from "./components/views/symbolsTrashView"; +import {usersSettingsView} from "./components/views/usersSettingsView"; +import actionCreateEditForm from "./components/forms/actionCreateEditForm"; +import projectCreateForm from "./components/forms/projectCreateForm"; +import userEditForm from "./components/forms/userEditForm"; +import userLoginForm from "./components/forms/userLoginForm"; +import userRegisterForm from "./components/forms/userRegisterForm"; +import widget from "./components/widgets/widget"; +import projectDetailsWidget from "./components/widgets/projectDetailsWidget"; +import learnResumeSettingsWidget from "./components/widgets/learnResumeSettingsWidget"; +import learnerStatusWidget from "./components/widgets/learnerStatusWidget"; +import latestLearnResultWidget from "./components/widgets/latestLearnResultWidget"; +import counterexamplesWidget from "./components/widgets/counterexamplesWidget"; +import alex from "./components/alex"; +import {actionBar} from "./components/actionBar"; +import {checkbox, checkboxMultiple} from "./components/checkbox"; +import {fileDropzone} from "./components/fileDropzone"; +import loadScreen from "./components/loadScreen"; +import projectList from "./components/projectList"; +import {sidebar} from "./components/sidebar"; +import viewHeader from "./components/viewHeader"; +import {responsiveIframe} from "./components/responsiveIframe"; +import {learnResultPanel} from "./components/learnResultPanel"; +import {observationTable} from "./components/observationTable"; +import {symbolListItem} from "./components/symbolListItem"; +import {symbolGroupListItem} from "./components/symbolGroupListItem"; +import {learnResultListItem} from "./components/learnResultListItem"; +import {hypothesis} from "./components/hypothesis"; +import {discriminationTree} from "./components/discriminationTree"; +import {htmlElementPicker} from "./components/htmlElementPicker"; angular .module('ALEX', [ @@ -50,24 +127,125 @@ angular ngFileUpload, angularDragula(angular), - // application specific modules - 'ALEX.templates', - components, - directives, - filters, - resources, - services + 'ALEX.templates' ]) .config(configuration.config) .config(routes.config) .run(routes.run) + + // constants .constant('learnAlgorithm', constant.learnAlgorithm) .constant('webBrowser', constant.webBrowser) .constant('eqOracleType', constant.eqOracleType) .constant('events', constant.events) .constant('actionType', constant.actionType) - .constant('chartMode', constant.chartMode); + .constant('chartMode', constant.chartMode) + + // filters + .filter('formatEqOracle', filter.formatEqOracle) + .filter('formatAlgorithm', filter.formatAlgorithm) + .filter('formatMilliseconds', filter.formatMilliseconds) + .filter('formatUserRole', filter.formatUserRole) + .filter('formatWebBrowser', filter.formatWebBrowser) + + // resources + .service('CounterResource', CounterResource) + .service('FileResource', FileResource) + .service('LearnerResource', LearnerResource) + .service('LearnResultResource', LearnResultResource) + .service('ProjectResource', ProjectResource) + .service('SymbolGroupResource', SymbolGroupResource) + .service('SymbolResource', SymbolResource) + .service('UserResource', UserResource) + + // services + .service('ActionService', ActionService) + .service('ClipboardService', ClipboardService) + .service('ErrorService', ErrorService) + .service('EventBus', EventBus) + .service('EqOracleService', EqOracleService) + .service('DownloadService', DownloadService) + .service('LearnerResultChartService', LearnerResultChartService) + .service('PromptService', PromptService) + .service('SessionService', SessionService) + .service('ToastService', ToastService) + .service('LearnerResultDownloadService', LearnerResultDownloadService) + .service('HtmlElementPickerService', HtmlElementPickerService) + + // directives + .directive('dropdownHover', dropdownHover) + .directive('htmlElementPickerHandle', htmlElementPickerHandle) + .directive('actionCreateModalHandle', actionCreateModalHandle) + .directive('actionEditModalHandle', actionEditModalHandle) + .directive('hypothesisLayoutSettingsModalHandle', hypothesisLayoutSettingsModalHandle) + .directive('learnResultDetailsModalHandle', learnResultDetailsModalHandle) + .directive('learnSetupSettingsModalHandle', learnSetupSettingsModalHandle) + .directive('projectSettingsModalHandle', projectSettingsModalHandle) + .directive('symbolCreateModalHandle', symbolCreateModalHandle) + .directive('symbolEditModalHandle', symbolEditModalHandle) + .directive('symbolGroupCreateModalHandle', symbolGroupCreateModalHandle) + .directive('symbolGroupEditModalHandle', symbolGroupEditModalHandle) + .directive('symbolMoveModalHandle', symbolMoveModalHandle) + .directive('userEditModalHandle', userEditModalHandle) + .directive('resultListModalHandle', resultListModalHandle) + + // view components + .component('aboutView', aboutView) + .component('adminUsersView', adminUsersView) + .component('countersView', countersView) + .component('errorView', errorView) + .component('filesView', filesView) + .component('homeView', homeView) + .component('learnerSetupView', learnerSetupView) + .component('learnerStartView', learnerStartView) + .component('projectsView', projectsView) + .component('projectsDashboardView', projectsDashboardView) + .component('resultsCompareView', resultsCompareView) + .component('resultsView', resultsView) + .component('statisticsCompareView', statisticsCompareView) + .component('statisticsView', statisticsView) + .component('symbolsActionsView', symbolsActionsView) + .component('symbolsView', symbolsView) + .component('symbolsHistoryView', symbolsHistoryView) + .component('symbolsImportView', symbolsImportView) + .component('symbolsTrashView', symbolsTrashView) + .component('usersSettingsView', usersSettingsView) + + // forms components + .component('actionCreateEditForm', actionCreateEditForm) + .component('projectCreateForm', projectCreateForm) + .component('userEditForm', userEditForm) + .component('userLoginForm', userLoginForm) + .component('userRegisterForm', userRegisterForm) + + // widgets components + .component('widget', widget) + .component('counterexamplesWidget', counterexamplesWidget) + .component('learnResumeSettingsWidget', learnResumeSettingsWidget) + .component('learnerStatusWidget', learnerStatusWidget) + .component('latestLearnResultWidget', latestLearnResultWidget) + .component('projectDetailsWidget', projectDetailsWidget) + + // misc components + .component('alex', alex) + .component('actionBar', actionBar) + .component('checkbox', checkbox) + .component('hypothesis', hypothesis) + .component('discriminationTree', discriminationTree) + .component('checkboxMultiple', checkboxMultiple) + .component('fileDropzone', fileDropzone) + .component('loadScreen', loadScreen) + .component('projectList', projectList) + .component('sidebar', sidebar) + .component('responsiveIframe', responsiveIframe) + .component('viewHeader', viewHeader) + .component('htmlElementPicker', htmlElementPicker) + .component('learnResultPanel', learnResultPanel) + .component('observationTable', observationTable) + .component('symbolListItem', symbolListItem) + .component('symbolGroupListItem', symbolGroupListItem) + .component('learnResultListItem', learnResultListItem); -window.d3 = d3; +angular.bootstrap(document, ['ALEX']); -angular.bootstrap(document, ['ALEX']); \ No newline at end of file +window.d3 = d3; \ No newline at end of file diff --git a/main/src/main/webapp/src/js/resources/index.js b/main/src/main/webapp/src/js/resources/index.js deleted file mode 100644 index 273e76fbf..000000000 --- a/main/src/main/webapp/src/js/resources/index.js +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2016 TU Dortmund - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import CounterResource from './CounterResource'; -import FileResource from './FileResource'; -import LearnerResource from './LearnerResource'; -import LearnResultResource from './LearnResultResource'; -import ProjectResource from './ProjectResource'; -import SymbolGroupResource from './SymbolGroupResource'; -import SymbolResource from './SymbolResource'; -import UserResource from './UserResource'; - -const moduleName = 'ALEX.resources'; - -angular - .module(moduleName, []) - .service('CounterResource', CounterResource) - .service('FileResource', FileResource) - .service('LearnerResource', LearnerResource) - .service('LearnResultResource', LearnResultResource) - .service('ProjectResource', ProjectResource) - .service('SymbolGroupResource', SymbolGroupResource) - .service('SymbolResource', SymbolResource) - .service('UserResource', UserResource); - -export const resources = moduleName; \ No newline at end of file diff --git a/main/src/main/webapp/src/js/services/index.js b/main/src/main/webapp/src/js/services/index.js deleted file mode 100644 index 27653a051..000000000 --- a/main/src/main/webapp/src/js/services/index.js +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2016 TU Dortmund - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import ActionService from './ActionService'; -import ClipboardService from './ClipboardService'; -import ErrorService from './ErrorService'; -import EventBus from './EventBus'; -import EqOracleService from './EqOracleService'; -import {DownloadService} from './DownloadService'; -import LearnerResultChartService from './LearnerResultChartService'; -import {PromptService} from './PromptService'; -import SessionService from './SessionService'; -import ToastService from './ToastService'; -import LearnerResultDownloadService from './LearnerResultDownloadService'; -import {HtmlElementPickerService} from './HtmlElementPickerService'; - -const moduleName = 'ALEX.services'; - -angular - .module(moduleName, []) - .service('ActionService', ActionService) - .service('ClipboardService', ClipboardService) - .service('ErrorService', ErrorService) - .service('EventBus', EventBus) - .service('EqOracleService', EqOracleService) - .service('DownloadService', DownloadService) - .service('LearnerResultChartService', LearnerResultChartService) - .service('PromptService', PromptService) - .service('SessionService', SessionService) - .service('ToastService', ToastService) - .service('LearnerResultDownloadService', LearnerResultDownloadService) - .service('HtmlElementPickerService', HtmlElementPickerService); - -export const services = moduleName; \ No newline at end of file From 3b5a0a4a5ee943a51a3b12b895c91708183cb3ff Mon Sep 17 00:00:00 2001 From: Alexander Bainczyk Date: Sun, 29 May 2016 11:15:14 +0200 Subject: [PATCH 22/44] updated frontend dependencies changed the appearance of the load screen --- main/src/main/webapp/package.json | 52 +++++++++---------- .../webapp/src/js/components/loadScreen.js | 10 ++-- .../src/main/webapp/src/scss/_animations.scss | 6 +++ main/src/main/webapp/src/scss/style.scss | 27 +++++----- 4 files changed, 50 insertions(+), 45 deletions(-) diff --git a/main/src/main/webapp/package.json b/main/src/main/webapp/package.json index 777e2da87..957dce2f6 100644 --- a/main/src/main/webapp/package.json +++ b/main/src/main/webapp/package.json @@ -28,48 +28,48 @@ "main": "index.html", "dependencies": { "ace-builds": "^1.2.2", - "angular": "^1.5.5", - "angular-animate": "^1.5.5", - "angular-dragula": "^1.1.9", + "angular": "^1.5.6", + "angular-animate": "^1.5.6", + "angular-dragula": "^1.2.7", "angular-jwt": "0.0.9", - "angular-messages": "^1.5.5", + "angular-messages": "^1.5.6", "angular-toastr": "^1.7.0", "angular-ui-ace": "^0.2.3", - "angular-ui-bootstrap": "^1.2.5", - "angular-ui-router": "^0.2.17", + "angular-ui-bootstrap": "^1.3.3", + "angular-ui-router": "^0.3.0", "bootstrap-sass": "^3.3.6", - "d3": "^3.5.9", + "d3": "^3.5.17", "dagre-d3": "^0.4.17", - "font-awesome": "^4.4.0", - "lodash": "^3.10.1", - "n3-charts": "^2.0.3", + "font-awesome": "^4.6.3", + "lodash": "^4.13.1", + "n3-charts": "^2.0.26", "ng-file-upload": "^12.0.4", - "selection-model": "^0.10.0" + "selection-model": "^0.10.1" }, "devDependencies": { - "angular-mocks": "^1.5.3", - "autoprefixer": "^6.3.5", - "babel-preset-es2015": "^6.5.0", - "babelify": "^7.2.0", + "angular-mocks": "^1.5.6", + "autoprefixer": "^6.3.6", + "babel-preset-es2015": "^6.9.0", + "babelify": "^7.3.0", "browserify-istanbul": "^2.0.0", "grunt": "^1.0.1", "grunt-browserify": "^5.0.0", - "grunt-contrib-concat": "^1.0.0", + "grunt-contrib-concat": "^1.0.1", "grunt-contrib-cssmin": "^1.0.1", "grunt-contrib-jshint": "^1.0.0", "grunt-contrib-uglify": "^1.0.1", "grunt-contrib-watch": "^1.0.0", - "grunt-html2js": "^0.3.5", - "grunt-karma": "^0.12.2", - "grunt-ng-annotate": "^2.0.1", + "grunt-html2js": "^0.3.6", + "grunt-karma": "^2.0.0", + "grunt-ng-annotate": "^2.0.2", "grunt-postcss": "^0.8.0", - "grunt-sass": "^1.1.0", - "istanbul": "^0.4.2", + "grunt-sass": "^1.2.0", + "istanbul": "^0.4.3", "jasmine-core": "^2.4.1", - "karma": "^0.13.15", - "karma-browserify": "^5.0.3", - "karma-chrome-launcher": "^0.2.3", - "karma-coverage": "^0.5.3", - "karma-jasmine": "^0.3.7" + "karma": "^0.13.22", + "karma-browserify": "^5.0.5", + "karma-chrome-launcher": "^1.0.1", + "karma-coverage": "^1.0.0", + "karma-jasmine": "^1.0.2" } } diff --git a/main/src/main/webapp/src/js/components/loadScreen.js b/main/src/main/webapp/src/js/components/loadScreen.js index da86db6e6..3066a3a78 100644 --- a/main/src/main/webapp/src/js/components/loadScreen.js +++ b/main/src/main/webapp/src/js/components/loadScreen.js @@ -37,18 +37,18 @@ class LoadScreen { $scope.$watch(() => $http.pendingRequests.length > 0, value => { this.visible = value ? true : false; }); + } } // the component definition const loadScreen = { controller: LoadScreen, - controllerAs: 'loadScreen', + controllerAs: 'vm', template: ` -
    -

    - -

    +
    + + Loading...
    ` }; diff --git a/main/src/main/webapp/src/scss/_animations.scss b/main/src/main/webapp/src/scss/_animations.scss index cf7686e34..3984c41d3 100644 --- a/main/src/main/webapp/src/scss/_animations.scss +++ b/main/src/main/webapp/src/scss/_animations.scss @@ -28,4 +28,10 @@ animation: fadeOut .15s both ease-in; z-index: 9999; } +} + +#load-screen { + &.ng-leave { + animation: fadeOut 1s both ease-in; + } } \ No newline at end of file diff --git a/main/src/main/webapp/src/scss/style.scss b/main/src/main/webapp/src/scss/style.scss index b384ad422..334ad4d8f 100644 --- a/main/src/main/webapp/src/scss/style.scss +++ b/main/src/main/webapp/src/scss/style.scss @@ -37,10 +37,6 @@ body { left: 0; } - #load-screen { - left: $sidebar-width-collapsed; - } - #toast-container { left: $sidebar-width-collapsed / 2 } @@ -72,11 +68,20 @@ body { } #load-screen { - @include position(fixed, 0, 0, 0, $sidebar-width); - - transition: left 200ms; - background: rgba(255, 255, 255, 0.6); + position: fixed; + bottom: 6px; + right: 6px; + border-radius: 4px; + text-align: center; + padding: 12px 16px; + font-size: 14px; + color: #fff; + background: #337ab7; z-index: 1100; + + i { + margin-right: 6px; + } } .alx-container { @@ -519,12 +524,6 @@ body { } } -#load-screen-indicator { - position: absolute; - top: 50%; - width: 100% -} - .alert-container { margin-top: 25px; } From e921b2a9df25461b4dcf53450ec2462131c6222a Mon Sep 17 00:00:00 2001 From: Alexander Schieweck Date: Sun, 29 May 2016 16:09:27 +0200 Subject: [PATCH 23/44] Added support for HTTP Basic Auth to the REST Call Action. --- .../de/learnlib/alex/actions/Credentials.java | 75 +++++++++++++++++++ .../actions/RESTSymbolActions/CallAction.java | 57 +++++++++++++- .../alex/actions/CredentialsTest.java | 18 +++++ 3 files changed, 146 insertions(+), 4 deletions(-) create mode 100644 main/src/main/java/de/learnlib/alex/actions/Credentials.java create mode 100644 main/src/test/java/de/learnlib/alex/actions/CredentialsTest.java diff --git a/main/src/main/java/de/learnlib/alex/actions/Credentials.java b/main/src/main/java/de/learnlib/alex/actions/Credentials.java new file mode 100644 index 000000000..a61978a71 --- /dev/null +++ b/main/src/main/java/de/learnlib/alex/actions/Credentials.java @@ -0,0 +1,75 @@ +package de.learnlib.alex.actions; + +import org.apache.commons.codec.binary.Base64; + +import javax.persistence.Embeddable; + +/** + * Helper class to store and transfer authentication credentials, i.e. user name / email and password. + */ +@Embeddable +public class Credentials { + + /** The name to use for authentication. */ + private String name; + + /** The password to use for authentication. */ + private String password; + + /** + * Default constructor; + */ + public Credentials() { + this("", ""); + } + + /** + * Create a credential with a user and password. + * + * @param name The name to use. + * @param password The password to use. + */ + public Credentials(String name, String password) { + this.name = name; + this.password = password; + } + + /** + * @return The name to use for authentication. + */ + public String getName() { + return name; + } + + /** + * @param name The new name to use for authentication. + */ + public void setName(String name) { + this.name = name; + } + + /** + * @return The password to use for authentication. + */ + public String getPassword() { + return password; + } + + /** + * @param password The new password to use for authentication. + */ + public void setPassword(String password) { + this.password = password; + } + + /** + * Return the name and password encoded in Base64 to be used in the HTTP Basic Authentication. + * + * @return "name:password" encoded in Base64. + */ + public String toBase64() { + String credentialsAsString = name + ":" + password; + return Base64.encodeBase64String(credentialsAsString.getBytes()); + } + +} diff --git a/main/src/main/java/de/learnlib/alex/actions/RESTSymbolActions/CallAction.java b/main/src/main/java/de/learnlib/alex/actions/RESTSymbolActions/CallAction.java index 87eb6d152..137b82d6d 100644 --- a/main/src/main/java/de/learnlib/alex/actions/RESTSymbolActions/CallAction.java +++ b/main/src/main/java/de/learnlib/alex/actions/RESTSymbolActions/CallAction.java @@ -16,7 +16,9 @@ package de.learnlib.alex.actions.RESTSymbolActions; +import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonTypeName; +import de.learnlib.alex.actions.Credentials; import de.learnlib.alex.core.entities.ExecuteResult; import de.learnlib.alex.core.learner.connectors.WebServiceConnector; import org.apache.logging.log4j.LogManager; @@ -25,6 +27,7 @@ import javax.persistence.Column; import javax.persistence.DiscriminatorValue; +import javax.persistence.Embedded; import javax.persistence.Entity; import javax.persistence.Lob; import javax.validation.constraints.NotNull; @@ -40,6 +43,7 @@ @Entity @DiscriminatorValue("rest_call") @JsonTypeName("rest_call") +@JsonInclude(JsonInclude.Include.NON_NULL) public class CallAction extends RESTSymbolAction { /** to be serializable. */ @@ -80,6 +84,12 @@ public enum Method { @Lob private HashMap headers; + /** + * Optional credentials to authenticate via HTTP basic auth. + */ + @Embedded + private Credentials credentials; + /** * Map to store cookies, that will be send with the request. * Cookies are a normal header field, but this should make things easier. @@ -179,6 +189,40 @@ public void setHeaders(HashMap headers) { this.headers = headers; } + /** + * Get the credentials to authenticate. + * + * @return The credentials to use for authentication. + */ + public Credentials getCredentials() { + return credentials; + } + + /** + * Like {@link #getCredentials()}, but the name and password will have all variables and counters inserted. + * + * @return The credentials to use, with the actual values of counters and variables in their values. + */ + private Credentials getCredentialsWithVariableValues() { + if (credentials == null) { + return new Credentials(); + } + + String name = insertVariableValues(credentials.getName()); + String password = insertVariableValues(credentials.getPassword()); + + return new Credentials(name, password); + } + + /** + * Set the credentials to use for authentication. + * + * @param credentials The new credentials to use. + */ + public void setCredentials(Credentials credentials) { + this.credentials = credentials; + } + /** * Get the map of cookies that will be used for the requests. * @@ -254,21 +298,26 @@ public ExecuteResult execute(WebServiceConnector target) { } private void doRequest(WebServiceConnector target) { + Map headers = getHeadersWithVariableValues(); + if (credentials != null) { + headers.put("Authorization", "Basic " + getCredentialsWithVariableValues().toBase64()); + } + switch (method) { case GET: - target.get(getUrlWithVariableValues(), getHeadersWithVariableValues(), + target.get(getUrlWithVariableValues(), headers, getCookiesWithVariableValues()); break; case POST: - target.post(getUrlWithVariableValues(), getHeadersWithVariableValues(), + target.post(getUrlWithVariableValues(), headers, getCookiesWithVariableValues(), getDataWithVariableValues()); break; case PUT: - target.put(getUrlWithVariableValues(), getHeadersWithVariableValues(), + target.put(getUrlWithVariableValues(), headers, getCookiesWithVariableValues(), getDataWithVariableValues()); break; case DELETE: - target.delete(getUrlWithVariableValues(), getHeadersWithVariableValues(), + target.delete(getUrlWithVariableValues(), headers, getCookiesWithVariableValues()); break; default: diff --git a/main/src/test/java/de/learnlib/alex/actions/CredentialsTest.java b/main/src/test/java/de/learnlib/alex/actions/CredentialsTest.java new file mode 100644 index 000000000..58d38a263 --- /dev/null +++ b/main/src/test/java/de/learnlib/alex/actions/CredentialsTest.java @@ -0,0 +1,18 @@ +package de.learnlib.alex.actions; + +import org.junit.Test; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +public class CredentialsTest { + + @Test + public void shouldCreateCorrectBase64() { + Credentials credentials = new Credentials("alex", "alex"); + + assertThat(credentials.toBase64(), is(equalTo("YWxleDphbGV4"))); + } + +} From 72e4184c2fe3aa841b261f729893710071287a28 Mon Sep 17 00:00:00 2001 From: Alexander Schieweck Date: Sun, 29 May 2016 19:58:44 +0200 Subject: [PATCH 24/44] Added support for HTTP Basic Auth to the Web Goto Action. --- .../actions/WebSymbolActions/GotoAction.java | 46 ++++++++++++++- .../alex/core/learner/BaseUrlManager.java | 18 ++++++ .../learner/connectors/WebSiteConnector.java | 17 ++++-- .../WebSymbolActions/GotoActionTest.java | 7 ++- .../alex/core/learner/BaseUrlManagerTest.java | 59 ++++++++++++++++--- 5 files changed, 130 insertions(+), 17 deletions(-) diff --git a/main/src/main/java/de/learnlib/alex/actions/WebSymbolActions/GotoAction.java b/main/src/main/java/de/learnlib/alex/actions/WebSymbolActions/GotoAction.java index 35c24d963..6ae75d929 100644 --- a/main/src/main/java/de/learnlib/alex/actions/WebSymbolActions/GotoAction.java +++ b/main/src/main/java/de/learnlib/alex/actions/WebSymbolActions/GotoAction.java @@ -17,13 +17,16 @@ package de.learnlib.alex.actions.WebSymbolActions; import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonTypeName; +import de.learnlib.alex.actions.Credentials; import de.learnlib.alex.core.entities.ExecuteResult; import de.learnlib.alex.core.learner.connectors.WebSiteConnector; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import javax.persistence.DiscriminatorValue; +import javax.persistence.Embedded; import javax.persistence.Entity; /** @@ -32,6 +35,7 @@ @Entity @DiscriminatorValue("web_goto") @JsonTypeName("web_goto") +@JsonInclude(JsonInclude.Include.NON_NULL) public class GotoAction extends WebSymbolAction { /** to be serializable. */ @@ -52,6 +56,12 @@ public String getUrl() { return url; } + /** + * Optional credentials to authenticate via HTTP basic auth. + */ + @Embedded + private Credentials credentials; + /** * Get the URL this action will navigate to. * All variables and counters will be replaced with their values. @@ -73,10 +83,44 @@ public void setUrl(String url) { this.url = url; } + /** + * Get the credentials to authenticate. + * + * @return The credentials to use for authentication. + */ + public Credentials getCredentials() { + return credentials; + } + + /** + * Like {@link #getCredentials()}, but the name and password will have all variables and counters inserted. + * + * @return The credentials to use, with the actual values of counters and variables in their values. + */ + private Credentials getCredentialsWithVariableValues() { + if (credentials == null) { + return new Credentials(); + } + + String name = insertVariableValues(credentials.getName()); + String password = insertVariableValues(credentials.getPassword()); + + return new Credentials(name, password); + } + + /** + * Set the credentials to use for authentication. + * + * @param credentials The new credentials to use. + */ + public void setCredentials(Credentials credentials) { + this.credentials = credentials; + } + @Override public ExecuteResult execute(WebSiteConnector connector) { try { - connector.get(getURLWithVariableValues()); + connector.get(getURLWithVariableValues(), getCredentialsWithVariableValues()); LOGGER.info("Could goto '" + url + "'."); return getSuccessOutput(); } catch (Exception e) { diff --git a/main/src/main/java/de/learnlib/alex/core/learner/BaseUrlManager.java b/main/src/main/java/de/learnlib/alex/core/learner/BaseUrlManager.java index 700882d05..f7bf7bc0e 100644 --- a/main/src/main/java/de/learnlib/alex/core/learner/BaseUrlManager.java +++ b/main/src/main/java/de/learnlib/alex/core/learner/BaseUrlManager.java @@ -16,6 +16,8 @@ package de.learnlib.alex.core.learner; +import de.learnlib.alex.actions.Credentials; + /** * Class to mange a URL and get URL based on this. */ @@ -64,6 +66,22 @@ public String getAbsoluteUrl(String path) { return combineUrls(baseUrl, path); } + /** + * Get the absolute URL of a path, i.e. based on the base url (base url + '/' + path'), as String + * and insert the credentials if possible. + * + * @param path + * The path to append on the base url. + * @param credentials + * The credentials to insert into the URL. + * @return An absolute URL as String + */ + public String getAbsoluteUrl(String path, Credentials credentials) { + String url = combineUrls(baseUrl, path); + url = url.replaceFirst("^(http[s]?://)", "$1" + credentials.getName() + ":" + credentials.getPassword() + "@"); + return url; + } + /** * Append apiPath to basePath and make sure that only one '/' is between them. * diff --git a/main/src/main/java/de/learnlib/alex/core/learner/connectors/WebSiteConnector.java b/main/src/main/java/de/learnlib/alex/core/learner/connectors/WebSiteConnector.java index 491727ad4..df1e1f482 100644 --- a/main/src/main/java/de/learnlib/alex/core/learner/connectors/WebSiteConnector.java +++ b/main/src/main/java/de/learnlib/alex/core/learner/connectors/WebSiteConnector.java @@ -16,6 +16,7 @@ package de.learnlib.alex.core.learner.connectors; +import de.learnlib.alex.actions.Credentials; import de.learnlib.alex.core.learner.BaseUrlManager; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -83,13 +84,16 @@ public void dispose() { } /** - * Do a HTTP GET request. + * Do a HTTP GET request within the browser. + * Optionally credentials for HTTP Basic Auth can be provided. * * @param path * The path to send the request to. + * @param credentials + * The credential to use. Can be null. */ - public void get(String path) { - String url = getAbsoluteUrl(path); + public void get(String path, Credentials credentials) { + String url = getAbsoluteUrl(path, credentials); driver.get(url); // wait for page to have loaded everything @@ -153,11 +157,14 @@ public String getBaseUrl() { /** * @param path * The path to append on the base url. + * @param credentials + * The credentials to use for HTTP Basic Auth. Can be null. + * * @return An absolute URL as String * @see BaseUrlManager#getAbsoluteUrl(String) */ - private String getAbsoluteUrl(String path) { - return baseUrl.getAbsoluteUrl(path); + private String getAbsoluteUrl(String path, Credentials credentials) { + return baseUrl.getAbsoluteUrl(path, credentials); } /** diff --git a/main/src/test/java/de/learnlib/alex/actions/WebSymbolActions/GotoActionTest.java b/main/src/test/java/de/learnlib/alex/actions/WebSymbolActions/GotoActionTest.java index 86fa27de6..af306df47 100644 --- a/main/src/test/java/de/learnlib/alex/actions/WebSymbolActions/GotoActionTest.java +++ b/main/src/test/java/de/learnlib/alex/actions/WebSymbolActions/GotoActionTest.java @@ -17,6 +17,7 @@ package de.learnlib.alex.actions.WebSymbolActions; import com.fasterxml.jackson.databind.ObjectMapper; +import de.learnlib.alex.actions.Credentials; import de.learnlib.alex.core.entities.ExecuteResult; import de.learnlib.alex.core.entities.Project; import de.learnlib.alex.core.entities.User; @@ -34,6 +35,8 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.mockito.BDDMockito.willThrow; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @@ -84,13 +87,13 @@ public void shouldReturnOKIfTheUrlCouldBeFound() { WebSiteConnector connector = mock(WebSiteConnector.class); assertEquals(ExecuteResult.OK, g.execute(connector)); - verify(connector).get(FAKE_URL); + verify(connector).get(eq(FAKE_URL), any(Credentials.class)); } @Test public void shouldReturnFailedIfTheUrlCouldNotBeFound() { WebSiteConnector connector = mock(WebSiteConnector.class); - willThrow(Exception.class).given(connector).get(FAKE_URL); + willThrow(Exception.class).given(connector).get(eq(FAKE_URL), any(Credentials.class)); assertEquals(ExecuteResult.FAILED, g.execute(connector)); } diff --git a/main/src/test/java/de/learnlib/alex/core/learner/BaseUrlManagerTest.java b/main/src/test/java/de/learnlib/alex/core/learner/BaseUrlManagerTest.java index 4b0fd1d4a..0c7838458 100644 --- a/main/src/test/java/de/learnlib/alex/core/learner/BaseUrlManagerTest.java +++ b/main/src/test/java/de/learnlib/alex/core/learner/BaseUrlManagerTest.java @@ -16,25 +16,28 @@ package de.learnlib.alex.core.learner; -import org.junit.Before; +import de.learnlib.alex.actions.Credentials; import org.junit.Test; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; public class BaseUrlManagerTest { - private static final String BASE_URL = "http://localhost"; - private static final String FINAL_URL = "http://localhost/test"; + private static final String BASE_URL = "http://localhost"; + private static final String FINAL_URL = "http://localhost/test"; + private static final String FINAL_URL_WITH_CREDENTIALS = "http://alex:alex@localhost/test"; - private BaseUrlManager manager; - - @Before - public void setUp() { - manager = new BaseUrlManager(BASE_URL); - } + private static final String SECURE_BASE_URL = "https://localhost"; + private static final String SECURE_FINAL_URL = "https://localhost/test"; + private static final String SECURE_FINAL_URL_WITH_CREDENTIALS = "https://alex:alex@localhost/test"; @Test public void shouldCreateCorrectUrls() { + BaseUrlManager manager = new BaseUrlManager(BASE_URL); + String url = manager.getAbsoluteUrl("test"); assertEquals(FINAL_URL, url); @@ -49,4 +52,42 @@ public void shouldCreateCorrectUrls() { assertEquals(FINAL_URL, url); } + @Test + public void shouldCreateCorrectUrlsWithCredentials() { + BaseUrlManager manager = new BaseUrlManager(BASE_URL); + // + Credentials credentials = new Credentials("alex", "alex"); + + String url = manager.getAbsoluteUrl("/test", credentials); + assertThat(url, is(equalTo(FINAL_URL_WITH_CREDENTIALS))); + } + + @Test + public void shouldCreateCorrectSecureUrls() { + BaseUrlManager manager = new BaseUrlManager(SECURE_BASE_URL); + + String url = manager.getAbsoluteUrl("test"); + assertEquals(SECURE_FINAL_URL, url); + + url = manager.getAbsoluteUrl("/test"); + assertEquals(SECURE_FINAL_URL, url); + + manager.setBaseUrl(SECURE_BASE_URL + "/"); + url = manager.getAbsoluteUrl("test"); + assertEquals(SECURE_FINAL_URL, url); + + url = manager.getAbsoluteUrl("/test"); + assertEquals(SECURE_FINAL_URL, url); + } + + @Test + public void shouldCreateCorrectSecureUrlsWithCredentials() { + BaseUrlManager manager = new BaseUrlManager(SECURE_BASE_URL); + // + Credentials credentials = new Credentials("alex", "alex"); + + String url = manager.getAbsoluteUrl("/test", credentials); + assertThat(url, is(equalTo(SECURE_FINAL_URL_WITH_CREDENTIALS))); + } + } From 981ac38b4c4b3fa271d30a089b084635b2302b5f Mon Sep 17 00:00:00 2001 From: Alexander Schieweck Date: Sun, 29 May 2016 20:10:27 +0200 Subject: [PATCH 25/44] Added support for HTTP Basic Auth into the Frontend. --- .../main/webapp/src/html/actions/rest_call.html | 14 ++++++++++++++ .../src/main/webapp/src/html/actions/web_goto.html | 12 +++++++++++- .../entities/actions/restActions/CallRestAction.js | 6 ++++++ .../entities/actions/webActions/GoToWebAction.js | 6 ++++++ 4 files changed, 37 insertions(+), 1 deletion(-) diff --git a/main/src/main/webapp/src/html/actions/rest_call.html b/main/src/main/webapp/src/html/actions/rest_call.html index 7682c90b0..02b055610 100644 --- a/main/src/main/webapp/src/html/actions/rest_call.html +++ b/main/src/main/webapp/src/html/actions/rest_call.html @@ -50,6 +50,20 @@

    Call Url

    + + + +
    + +
    + + +
    +
    + + +
    +
    diff --git a/main/src/main/webapp/src/html/actions/web_goto.html b/main/src/main/webapp/src/html/actions/web_goto.html index 2007e51d2..e65150225 100644 --- a/main/src/main/webapp/src/html/actions/web_goto.html +++ b/main/src/main/webapp/src/html/actions/web_goto.html @@ -8,4 +8,14 @@

    Go to URL

    -
    \ No newline at end of file +
    + + +
    + + +
    +
    + + +
    diff --git a/main/src/main/webapp/src/js/entities/actions/restActions/CallRestAction.js b/main/src/main/webapp/src/js/entities/actions/restActions/CallRestAction.js index 0674f5e78..4cfb5067c 100644 --- a/main/src/main/webapp/src/js/entities/actions/restActions/CallRestAction.js +++ b/main/src/main/webapp/src/js/entities/actions/restActions/CallRestAction.js @@ -60,6 +60,12 @@ class CallRestAction extends Action { * @type {*|{}} */ this.headers = obj.headers || {}; + + /** + * The HTTP Basic auth credentials of the request (optional). + * @type {*|{}} + */ + this.credentials = obj.credentials || {}; } /** diff --git a/main/src/main/webapp/src/js/entities/actions/webActions/GoToWebAction.js b/main/src/main/webapp/src/js/entities/actions/webActions/GoToWebAction.js index 02a1c5131..fffeed4cc 100644 --- a/main/src/main/webapp/src/js/entities/actions/webActions/GoToWebAction.js +++ b/main/src/main/webapp/src/js/entities/actions/webActions/GoToWebAction.js @@ -33,6 +33,12 @@ class GoToWebAction extends Action { * @type {*|string} */ this.url = obj.url || ''; + + /** + * The HTTP Basic auth credentials of the request (optional). + * @type {*|{}} + */ + this.credentials = obj.credentials || {}; } /** From 2213a54beed0759e85da9a762d6c68a2db1eb672 Mon Sep 17 00:00:00 2001 From: Alexander Bainczyk Date: Mon, 30 May 2016 11:59:35 +0200 Subject: [PATCH 26/44] allow variables and counters to be inserted in variables changed the appearance of two templates --- .../StoreSymbolActions/SetVariableAction.java | 2 +- .../main/webapp/src/html/actions/rest_call.html | 4 ++-- .../main/webapp/src/html/actions/web_goto.html | 15 ++++++++++----- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/main/src/main/java/de/learnlib/alex/actions/StoreSymbolActions/SetVariableAction.java b/main/src/main/java/de/learnlib/alex/actions/StoreSymbolActions/SetVariableAction.java index 952d6ffb3..3d864cac6 100644 --- a/main/src/main/java/de/learnlib/alex/actions/StoreSymbolActions/SetVariableAction.java +++ b/main/src/main/java/de/learnlib/alex/actions/StoreSymbolActions/SetVariableAction.java @@ -77,7 +77,7 @@ public void setValue(String value) { @Override public ExecuteResult execute(ConnectorManager connector) { VariableStoreConnector storeConnector = connector.getConnector(VariableStoreConnector.class); - storeConnector.set(name, value); + storeConnector.set(name, insertVariableValues(value)); return getSuccessOutput(); } } diff --git a/main/src/main/webapp/src/html/actions/rest_call.html b/main/src/main/webapp/src/html/actions/rest_call.html index 02b055610..5036d21b9 100644 --- a/main/src/main/webapp/src/html/actions/rest_call.html +++ b/main/src/main/webapp/src/html/actions/rest_call.html @@ -56,11 +56,11 @@

    Call Url


    - +
    - +
    diff --git a/main/src/main/webapp/src/html/actions/web_goto.html b/main/src/main/webapp/src/html/actions/web_goto.html index e65150225..83e49989f 100644 --- a/main/src/main/webapp/src/html/actions/web_goto.html +++ b/main/src/main/webapp/src/html/actions/web_goto.html @@ -1,7 +1,7 @@ -

    Go to URL

    +

    Open URL

    - Go to a url that is relative to your projects' base url + Open a URL that is relative to your projects' base URL


    @@ -10,12 +10,17 @@

    Go to URL

    - +
    + +

    Authentication

    +

    + Use Basic HTTP authentication (optional) +

    - +
    - +
    From c80f219820f0f57ae4fc6706f334d165e4123b72 Mon Sep 17 00:00:00 2001 From: Alexander Schieweck Date: Mon, 30 May 2016 13:01:01 +0200 Subject: [PATCH 27/44] fixed test that failed after the previous commit. added new test to ensure variables / counters are indeed replaced in the Set Variable Action. --- .../SetVariableActionTest.java | 35 ++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/main/src/test/java/de/learnlib/alex/actions/StoreSymbolActions/SetVariableActionTest.java b/main/src/test/java/de/learnlib/alex/actions/StoreSymbolActions/SetVariableActionTest.java index c568719fe..d1adb5001 100644 --- a/main/src/test/java/de/learnlib/alex/actions/StoreSymbolActions/SetVariableActionTest.java +++ b/main/src/test/java/de/learnlib/alex/actions/StoreSymbolActions/SetVariableActionTest.java @@ -18,8 +18,11 @@ import com.fasterxml.jackson.databind.ObjectMapper; import de.learnlib.alex.core.entities.ExecuteResult; +import de.learnlib.alex.core.entities.Project; import de.learnlib.alex.core.entities.SymbolAction; +import de.learnlib.alex.core.entities.User; import de.learnlib.alex.core.learner.connectors.ConnectorManager; +import de.learnlib.alex.core.learner.connectors.CounterStoreConnector; import de.learnlib.alex.core.learner.connectors.VariableStoreConnector; import org.junit.Before; import org.junit.Test; @@ -36,14 +39,25 @@ public class SetVariableActionTest { + private static long USER_ID = 21; + private static long PROJECT_ID = 42; + private static final String TEST_VALUE = "foobar"; - private static final String TEST_NAME = "variable"; + private static final String TEST_NAME = "variable"; private SetVariableAction setAction; @Before public void setUp() { + User user = new User(); + user.setId(USER_ID); + + Project project = new Project(); + project.setId(PROJECT_ID); + setAction = new SetVariableAction(); + setAction.setUser(user); + setAction.setProject(project); setAction.setName(TEST_NAME); setAction.setValue(TEST_VALUE); } @@ -83,4 +97,23 @@ public void shouldSuccessfulSetTheVariableValue() { verify(variables).set(TEST_NAME, TEST_VALUE); } + @Test + public void shouldSuccessfulSetTheVariableValueWithCounter() { + ConnectorManager connector = mock(ConnectorManager.class); + // + CounterStoreConnector counters = mock(CounterStoreConnector.class); + given(counters.get(USER_ID, PROJECT_ID, "counter")).willReturn(2); + given(connector.getConnector(CounterStoreConnector.class)).willReturn(counters); + // + VariableStoreConnector variables = mock(VariableStoreConnector.class); + given(connector.getConnector(VariableStoreConnector.class)).willReturn(variables); + // + setAction.setValue(TEST_VALUE + "{{#counter}}"); + + ExecuteResult result = setAction.executeAction(connector); + + assertEquals(ExecuteResult.OK, result); + verify(variables).set(TEST_NAME, TEST_VALUE + "2"); + } + } From 6121b53071ca6b332bfb83284a405c8e7e50378f Mon Sep 17 00:00:00 2001 From: Alexander Schieweck Date: Mon, 30 May 2016 13:03:43 +0200 Subject: [PATCH 28/44] fixed that the Learning Context, including the Web Driver, was sometimes not properly closed. --- .../learnlib/alex/core/learner/Learner.java | 5 ++- .../alex/core/learner/LearnerThread.java | 1 + .../core/learner/connectors/Connector.java | 2 +- .../connectors/ConnectorContextHandler.java | 2 +- .../learner/connectors/ConnectorManager.java | 17 +++++--- .../alex/core/learner/LearnerTest.java | 9 ++++ .../connectors/ConnectorManagerTest.java | 43 +++++++++++++++++++ 7 files changed, 69 insertions(+), 10 deletions(-) create mode 100644 main/src/test/java/de/learnlib/alex/core/learner/connectors/ConnectorManagerTest.java diff --git a/main/src/main/java/de/learnlib/alex/core/learner/Learner.java b/main/src/main/java/de/learnlib/alex/core/learner/Learner.java index fed3f101a..ac5f2cec1 100644 --- a/main/src/main/java/de/learnlib/alex/core/learner/Learner.java +++ b/main/src/main/java/de/learnlib/alex/core/learner/Learner.java @@ -452,8 +452,11 @@ public List readOutputs(User user, Project project, Symbol resetSymbol, ConnectorManager connectors = contextHandler.createContext(); try { - return symbols.stream().map(s -> s.execute(connectors).toString()).collect(Collectors.toList()); + List output = symbols.stream().map(s -> s.execute(connectors).toString()).collect(Collectors.toList()); + connectors.dispose(); + return output; } catch (Exception e) { + connectors.dispose(); throw new LearnerException("Could not read the outputs", e); } } diff --git a/main/src/main/java/de/learnlib/alex/core/learner/LearnerThread.java b/main/src/main/java/de/learnlib/alex/core/learner/LearnerThread.java index a7e1b3c72..e7c9af740 100644 --- a/main/src/main/java/de/learnlib/alex/core/learner/LearnerThread.java +++ b/main/src/main/java/de/learnlib/alex/core/learner/LearnerThread.java @@ -249,6 +249,7 @@ public void run() { LOGGER.warn("Something in the LearnerThread went wrong:", e); currentStep.setErrorText(e.getMessage()); learnerResultDAO.saveStep(result, currentStep); + sul.post(); } LOGGER.trace("LearnThread.run() - exit"); diff --git a/main/src/main/java/de/learnlib/alex/core/learner/connectors/Connector.java b/main/src/main/java/de/learnlib/alex/core/learner/connectors/Connector.java index 20bdd27b7..efb49e347 100644 --- a/main/src/main/java/de/learnlib/alex/core/learner/connectors/Connector.java +++ b/main/src/main/java/de/learnlib/alex/core/learner/connectors/Connector.java @@ -29,7 +29,7 @@ public interface Connector { /** * Dispose the connector. - * This method will be called after the learning and allow to do necessary clean ups. + * This method will be called after the learning and allows to do necessary clean ups. * After this method is called, the connector should not work anymore. */ void dispose(); diff --git a/main/src/main/java/de/learnlib/alex/core/learner/connectors/ConnectorContextHandler.java b/main/src/main/java/de/learnlib/alex/core/learner/connectors/ConnectorContextHandler.java index f01810a5d..796982641 100644 --- a/main/src/main/java/de/learnlib/alex/core/learner/connectors/ConnectorContextHandler.java +++ b/main/src/main/java/de/learnlib/alex/core/learner/connectors/ConnectorContextHandler.java @@ -84,7 +84,7 @@ private void executeResetSymbol() throws LearnerException { @Override public void disposeContext(ConnectorManager connector) { - connectors.forEach(Connector::dispose); + connectors.dispose(); } } diff --git a/main/src/main/java/de/learnlib/alex/core/learner/connectors/ConnectorManager.java b/main/src/main/java/de/learnlib/alex/core/learner/connectors/ConnectorManager.java index 76507700e..181e64e5a 100644 --- a/main/src/main/java/de/learnlib/alex/core/learner/connectors/ConnectorManager.java +++ b/main/src/main/java/de/learnlib/alex/core/learner/connectors/ConnectorManager.java @@ -37,13 +37,6 @@ public ConnectorManager() { this.connectors = new HashMap<>(); } - /** - * Removes all connectors. - */ - public void reset() { - this.connectors.clear(); - } - /** * Adds a new connector to the manager. * @@ -69,4 +62,14 @@ public T getConnector(Class type) { public Iterator iterator() { return connectors.values().iterator(); } + + /** + * Disposes all connectors and clear the list of managed connectors. + * This method is idempotent. + */ + public void dispose() { + connectors.forEach((t, u) -> u.dispose()); + connectors.clear(); + } + } diff --git a/main/src/test/java/de/learnlib/alex/core/learner/LearnerTest.java b/main/src/test/java/de/learnlib/alex/core/learner/LearnerTest.java index 642ff93b3..e55fabef8 100644 --- a/main/src/test/java/de/learnlib/alex/core/learner/LearnerTest.java +++ b/main/src/test/java/de/learnlib/alex/core/learner/LearnerTest.java @@ -30,6 +30,7 @@ import de.learnlib.alex.core.learner.connectors.ConnectorManager; import de.learnlib.alex.core.learner.connectors.WebBrowser; import de.learnlib.alex.exceptions.NotFoundException; +import de.learnlib.mapper.ContextExecutableInputSUL; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; @@ -46,6 +47,7 @@ import static org.mockito.BDDMockito.given; import static org.mockito.Matchers.any; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; @RunWith(MockitoJUnitRunner.class) public class LearnerTest { @@ -142,17 +144,24 @@ public void shouldOnlyStartTheThreadOnce() throws NotFoundException { public void shouldReadTheCorrectOutputOfSomeSymbols() { Symbol resetSymbol = mock(Symbol.class); given(resetSymbol.execute(any(ConnectorManager.class))).willReturn(ExecuteResult.OK); + // List symbols = new LinkedList<>(); for (int i = 0; i < SYMBOL_AMOUNT; i++) { Symbol symbol = mock(Symbol.class); given(symbol.execute(any(ConnectorManager.class))).willReturn(ExecuteResult.OK); symbols.add(symbol); } + // + ConnectorContextHandler contextHandler = mock(ConnectorContextHandler.class); + ConnectorManager connectorManager = mock(ConnectorManager.class); + given(contextHandler.createContext()).willReturn(connectorManager); + given(contextHandlerFactory.createContext(project, WebBrowser.HTMLUNITDRIVER)).willReturn(contextHandler); List outputs = learner.readOutputs(user, project, resetSymbol, symbols); assertEquals(symbols.size(), outputs.size()); assertTrue("at least one output was not OK", outputs.stream().allMatch(output -> output.equals("OK"))); + verify(connectorManager).dispose(); } } diff --git a/main/src/test/java/de/learnlib/alex/core/learner/connectors/ConnectorManagerTest.java b/main/src/test/java/de/learnlib/alex/core/learner/connectors/ConnectorManagerTest.java new file mode 100644 index 000000000..54bc03410 --- /dev/null +++ b/main/src/test/java/de/learnlib/alex/core/learner/connectors/ConnectorManagerTest.java @@ -0,0 +1,43 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package de.learnlib.alex.core.learner.connectors; + +import org.junit.Test; + +import static org.junit.Assert.assertFalse; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + + +public class ConnectorManagerTest { + + @Test + public void shouldDisposeAllConnectors() { + Connector connector1 = mock(Connector.class); + Connector connector2 = mock(Connector.class); + // + ConnectorManager manager = new ConnectorManager(); + manager.addConnector(VariableStoreConnector.class, connector1); + manager.addConnector(CounterStoreConnector.class, connector2); + + manager.dispose(); + + verify(connector1).dispose(); + verify(connector2).dispose(); + assertFalse(manager.iterator().hasNext()); // i.e. the Manager is empty + } + +} \ No newline at end of file From 49ea60bab36189c9431f1c5ec4c33918b1dc81ab Mon Sep 17 00:00:00 2001 From: Alexander Bainczyk Date: Sat, 4 Jun 2016 11:33:22 +0200 Subject: [PATCH 29/44] fixed that symbols could not be imported into an empty project additionally, fixed some jshint issues --- .../webapp/src/html/pages/symbols-import.html | 4 +++ .../js/components/forms/projectCreateForm.js | 4 +-- .../webapp/src/js/components/hypothesis.js | 2 +- .../webapp/src/js/components/projectList.js | 6 ++--- .../src/js/components/views/adminUsersView.js | 2 +- .../js/components/views/symbolsImportView.js | 26 +++++++++++++------ 6 files changed, 29 insertions(+), 15 deletions(-) diff --git a/main/src/main/webapp/src/html/pages/symbols-import.html b/main/src/main/webapp/src/html/pages/symbols-import.html index f6c2b4230..8c76f86ef 100644 --- a/main/src/main/webapp/src/html/pages/symbols-import.html +++ b/main/src/main/webapp/src/html/pages/symbols-import.html @@ -23,6 +23,10 @@
    + +
    diff --git a/main/src/main/webapp/src/js/components/forms/projectCreateForm.js b/main/src/main/webapp/src/js/components/forms/projectCreateForm.js index a67b49d09..695d56673 100644 --- a/main/src/main/webapp/src/js/components/forms/projectCreateForm.js +++ b/main/src/main/webapp/src/js/components/forms/projectCreateForm.js @@ -74,10 +74,10 @@ class ProjectCreateForm { .then(importedProject => { this.EventBus.emit(events.PROJECT_CREATED, {project: importedProject}); this.projectToImport = null; - this.ToastService.success(`The project '${importedProject.name}' has been imported.`) + this.ToastService.success(`The project '${importedProject.name}' has been imported.`); }) .catch(response => { - this.ToastService.danger(`

    The import failed!

    ${response.data.message}`) + this.ToastService.danger(`

    The import failed!

    ${response.data.message}`); }); } } diff --git a/main/src/main/webapp/src/js/components/hypothesis.js b/main/src/main/webapp/src/js/components/hypothesis.js index 29eef882a..c543f8bcb 100644 --- a/main/src/main/webapp/src/js/components/hypothesis.js +++ b/main/src/main/webapp/src/js/components/hypothesis.js @@ -136,7 +136,7 @@ class HypothesisComponent { width: 25, labelStyle: STYLE.nodeLabel, style: node === this.data.initNode ? STYLE.initNode : STYLE.node - }) + }); }); // another format of a graph for merged multi edges diff --git a/main/src/main/webapp/src/js/components/projectList.js b/main/src/main/webapp/src/js/components/projectList.js index dd0190cfb..7b694c062 100644 --- a/main/src/main/webapp/src/js/components/projectList.js +++ b/main/src/main/webapp/src/js/components/projectList.js @@ -87,7 +87,7 @@ class ProjectList { delete group.project; delete group.user; - group.symbols = group.symbols.map(symbol => symbol.getExportableSymbol()) + group.symbols = group.symbols.map(symbol => symbol.getExportableSymbol()); }); delete project.id; @@ -99,8 +99,8 @@ class ProjectList { this.DownloadService.downloadObject(project, filename); this.ToastService.success('The project has been exported.'); }); - }) - }) + }); + }); } } diff --git a/main/src/main/webapp/src/js/components/views/adminUsersView.js b/main/src/main/webapp/src/js/components/views/adminUsersView.js index df63d7ad2..53c49d98a 100644 --- a/main/src/main/webapp/src/js/components/views/adminUsersView.js +++ b/main/src/main/webapp/src/js/components/views/adminUsersView.js @@ -88,7 +88,7 @@ class AdminUsersView { }) .catch(response => { this.ToastService.danger(`Deleting failed! ${response.data.message}`); - }) + }); } } diff --git a/main/src/main/webapp/src/js/components/views/symbolsImportView.js b/main/src/main/webapp/src/js/components/views/symbolsImportView.js index f704a105a..df4a022ae 100644 --- a/main/src/main/webapp/src/js/components/views/symbolsImportView.js +++ b/main/src/main/webapp/src/js/components/views/symbolsImportView.js @@ -27,10 +27,10 @@ class SymbolsImportView { /** * Constructor * @param $scope - * @param SessionService - * @param SymbolResource - * @param ToastService - * @param EventBus + * @param {SessionService} SessionService + * @param {SymbolResource} SymbolResource + * @param {ToastService} ToastService + * @param {EventBus} EventBus */ constructor($scope, SessionService, SymbolResource, ToastService, EventBus) { this.SymbolResource = SymbolResource; @@ -83,7 +83,7 @@ class SymbolsImportView { return new AlphabetSymbol(s); }); } catch (e) { - this.ToastService.danger('

    Loading json file failed

    ' + e); + this.ToastService.danger('

    Loading json file failed

    The file is not properly formatted'); } } @@ -95,7 +95,8 @@ class SymbolsImportView { if (this.selectedSymbols.length > 0) { this.SymbolResource.getAll(this.project.id) .then(existingSymbols => { - const maxId = _.max(existingSymbols, 'id').id; + let maxId = _.max(existingSymbols, 'id'); + maxId = typeof maxId === "undefined" ? 0 : maxId; const symbols = this.selectedSymbols.map(s => new AlphabetSymbol(s)); symbols.forEach(symbol => { delete symbol.id; @@ -118,13 +119,22 @@ class SymbolsImportView { _.remove(this.symbols, {name: symbol.name}); }); }) - .catch(response => { - this.ToastService.danger('

    Symbol upload failed

    ' + response.data.message); + .catch(() => { + this.ToastService.danger('

    Symbol upload failed

    It seems at least on symbol already exists'); }); }); } } + /** + * Remove selected symbols from the list. + */ + removeSelectedSymbols() { + this.selectedSymbols.forEach(symbol => { + _.remove(this.symbols, {name: symbol.name}); + }); + } + /** * Changes the name and/or the abbreviation a symbol before uploading it to prevent naming conflicts in the * database. From 90ff4000961fed91db2e8fece0ce38e37cb0cfdd Mon Sep 17 00:00:00 2001 From: Alexander Bainczyk Date: Sat, 4 Jun 2016 14:13:27 +0200 Subject: [PATCH 30/44] fixed basic http auth handling if the credentials were not null, but password and name were empty strings the url looks like http://:@host:port which throws a MalformedURLException in the WebDriver --- .../main/java/de/learnlib/alex/actions/Credentials.java | 7 +++++++ .../java/de/learnlib/alex/core/learner/BaseUrlManager.java | 4 +++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/main/src/main/java/de/learnlib/alex/actions/Credentials.java b/main/src/main/java/de/learnlib/alex/actions/Credentials.java index a61978a71..1db9851ff 100644 --- a/main/src/main/java/de/learnlib/alex/actions/Credentials.java +++ b/main/src/main/java/de/learnlib/alex/actions/Credentials.java @@ -72,4 +72,11 @@ public String toBase64() { return Base64.encodeBase64String(credentialsAsString.getBytes()); } + /** + * Check if the credentials are not empty. + * @return If the credentials are valid. + */ + public boolean areValid() { + return !name.trim().equals("") && !password.trim().equals(""); + } } diff --git a/main/src/main/java/de/learnlib/alex/core/learner/BaseUrlManager.java b/main/src/main/java/de/learnlib/alex/core/learner/BaseUrlManager.java index f7bf7bc0e..300077d2d 100644 --- a/main/src/main/java/de/learnlib/alex/core/learner/BaseUrlManager.java +++ b/main/src/main/java/de/learnlib/alex/core/learner/BaseUrlManager.java @@ -78,7 +78,9 @@ public String getAbsoluteUrl(String path) { */ public String getAbsoluteUrl(String path, Credentials credentials) { String url = combineUrls(baseUrl, path); - url = url.replaceFirst("^(http[s]?://)", "$1" + credentials.getName() + ":" + credentials.getPassword() + "@"); + if (credentials != null && credentials.areValid()) { + url = url.replaceFirst("^(http[s]?://)", "$1" + credentials.getName() + ":" + credentials.getPassword() + "@"); + } return url; } From 89b907a1a4b05cdc0d61b440ed6a3f8e21ac7719 Mon Sep 17 00:00:00 2001 From: Alexander Schieweck Date: Mon, 30 May 2016 13:03:43 +0200 Subject: [PATCH 31/44] Revert "fixed that the Learning Context, including the Web Driver, was sometimes not properly closed." This reverts commit 6121b53071ca6b332bfb83284a405c8e7e50378f. While learning, at some seemingly random point, the connectormanager that is passed to a symbol gets null. That results in a NullpointerException and the app has to be force closed --- .../learnlib/alex/core/learner/Learner.java | 5 +-- .../alex/core/learner/LearnerThread.java | 1 - .../core/learner/connectors/Connector.java | 2 +- .../connectors/ConnectorContextHandler.java | 2 +- .../learner/connectors/ConnectorManager.java | 17 +++----- .../alex/core/learner/LearnerTest.java | 9 ---- .../connectors/ConnectorManagerTest.java | 43 ------------------- 7 files changed, 10 insertions(+), 69 deletions(-) delete mode 100644 main/src/test/java/de/learnlib/alex/core/learner/connectors/ConnectorManagerTest.java diff --git a/main/src/main/java/de/learnlib/alex/core/learner/Learner.java b/main/src/main/java/de/learnlib/alex/core/learner/Learner.java index ac5f2cec1..fed3f101a 100644 --- a/main/src/main/java/de/learnlib/alex/core/learner/Learner.java +++ b/main/src/main/java/de/learnlib/alex/core/learner/Learner.java @@ -452,11 +452,8 @@ public List readOutputs(User user, Project project, Symbol resetSymbol, ConnectorManager connectors = contextHandler.createContext(); try { - List output = symbols.stream().map(s -> s.execute(connectors).toString()).collect(Collectors.toList()); - connectors.dispose(); - return output; + return symbols.stream().map(s -> s.execute(connectors).toString()).collect(Collectors.toList()); } catch (Exception e) { - connectors.dispose(); throw new LearnerException("Could not read the outputs", e); } } diff --git a/main/src/main/java/de/learnlib/alex/core/learner/LearnerThread.java b/main/src/main/java/de/learnlib/alex/core/learner/LearnerThread.java index e7c9af740..a7e1b3c72 100644 --- a/main/src/main/java/de/learnlib/alex/core/learner/LearnerThread.java +++ b/main/src/main/java/de/learnlib/alex/core/learner/LearnerThread.java @@ -249,7 +249,6 @@ public void run() { LOGGER.warn("Something in the LearnerThread went wrong:", e); currentStep.setErrorText(e.getMessage()); learnerResultDAO.saveStep(result, currentStep); - sul.post(); } LOGGER.trace("LearnThread.run() - exit"); diff --git a/main/src/main/java/de/learnlib/alex/core/learner/connectors/Connector.java b/main/src/main/java/de/learnlib/alex/core/learner/connectors/Connector.java index efb49e347..20bdd27b7 100644 --- a/main/src/main/java/de/learnlib/alex/core/learner/connectors/Connector.java +++ b/main/src/main/java/de/learnlib/alex/core/learner/connectors/Connector.java @@ -29,7 +29,7 @@ public interface Connector { /** * Dispose the connector. - * This method will be called after the learning and allows to do necessary clean ups. + * This method will be called after the learning and allow to do necessary clean ups. * After this method is called, the connector should not work anymore. */ void dispose(); diff --git a/main/src/main/java/de/learnlib/alex/core/learner/connectors/ConnectorContextHandler.java b/main/src/main/java/de/learnlib/alex/core/learner/connectors/ConnectorContextHandler.java index 796982641..f01810a5d 100644 --- a/main/src/main/java/de/learnlib/alex/core/learner/connectors/ConnectorContextHandler.java +++ b/main/src/main/java/de/learnlib/alex/core/learner/connectors/ConnectorContextHandler.java @@ -84,7 +84,7 @@ private void executeResetSymbol() throws LearnerException { @Override public void disposeContext(ConnectorManager connector) { - connectors.dispose(); + connectors.forEach(Connector::dispose); } } diff --git a/main/src/main/java/de/learnlib/alex/core/learner/connectors/ConnectorManager.java b/main/src/main/java/de/learnlib/alex/core/learner/connectors/ConnectorManager.java index 181e64e5a..76507700e 100644 --- a/main/src/main/java/de/learnlib/alex/core/learner/connectors/ConnectorManager.java +++ b/main/src/main/java/de/learnlib/alex/core/learner/connectors/ConnectorManager.java @@ -37,6 +37,13 @@ public ConnectorManager() { this.connectors = new HashMap<>(); } + /** + * Removes all connectors. + */ + public void reset() { + this.connectors.clear(); + } + /** * Adds a new connector to the manager. * @@ -62,14 +69,4 @@ public T getConnector(Class type) { public Iterator iterator() { return connectors.values().iterator(); } - - /** - * Disposes all connectors and clear the list of managed connectors. - * This method is idempotent. - */ - public void dispose() { - connectors.forEach((t, u) -> u.dispose()); - connectors.clear(); - } - } diff --git a/main/src/test/java/de/learnlib/alex/core/learner/LearnerTest.java b/main/src/test/java/de/learnlib/alex/core/learner/LearnerTest.java index e55fabef8..642ff93b3 100644 --- a/main/src/test/java/de/learnlib/alex/core/learner/LearnerTest.java +++ b/main/src/test/java/de/learnlib/alex/core/learner/LearnerTest.java @@ -30,7 +30,6 @@ import de.learnlib.alex.core.learner.connectors.ConnectorManager; import de.learnlib.alex.core.learner.connectors.WebBrowser; import de.learnlib.alex.exceptions.NotFoundException; -import de.learnlib.mapper.ContextExecutableInputSUL; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; @@ -47,7 +46,6 @@ import static org.mockito.BDDMockito.given; import static org.mockito.Matchers.any; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; @RunWith(MockitoJUnitRunner.class) public class LearnerTest { @@ -144,24 +142,17 @@ public void shouldOnlyStartTheThreadOnce() throws NotFoundException { public void shouldReadTheCorrectOutputOfSomeSymbols() { Symbol resetSymbol = mock(Symbol.class); given(resetSymbol.execute(any(ConnectorManager.class))).willReturn(ExecuteResult.OK); - // List symbols = new LinkedList<>(); for (int i = 0; i < SYMBOL_AMOUNT; i++) { Symbol symbol = mock(Symbol.class); given(symbol.execute(any(ConnectorManager.class))).willReturn(ExecuteResult.OK); symbols.add(symbol); } - // - ConnectorContextHandler contextHandler = mock(ConnectorContextHandler.class); - ConnectorManager connectorManager = mock(ConnectorManager.class); - given(contextHandler.createContext()).willReturn(connectorManager); - given(contextHandlerFactory.createContext(project, WebBrowser.HTMLUNITDRIVER)).willReturn(contextHandler); List outputs = learner.readOutputs(user, project, resetSymbol, symbols); assertEquals(symbols.size(), outputs.size()); assertTrue("at least one output was not OK", outputs.stream().allMatch(output -> output.equals("OK"))); - verify(connectorManager).dispose(); } } diff --git a/main/src/test/java/de/learnlib/alex/core/learner/connectors/ConnectorManagerTest.java b/main/src/test/java/de/learnlib/alex/core/learner/connectors/ConnectorManagerTest.java deleted file mode 100644 index 54bc03410..000000000 --- a/main/src/test/java/de/learnlib/alex/core/learner/connectors/ConnectorManagerTest.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2016 TU Dortmund - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package de.learnlib.alex.core.learner.connectors; - -import org.junit.Test; - -import static org.junit.Assert.assertFalse; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; - - -public class ConnectorManagerTest { - - @Test - public void shouldDisposeAllConnectors() { - Connector connector1 = mock(Connector.class); - Connector connector2 = mock(Connector.class); - // - ConnectorManager manager = new ConnectorManager(); - manager.addConnector(VariableStoreConnector.class, connector1); - manager.addConnector(CounterStoreConnector.class, connector2); - - manager.dispose(); - - verify(connector1).dispose(); - verify(connector2).dispose(); - assertFalse(manager.iterator().hasNext()); // i.e. the Manager is empty - } - -} \ No newline at end of file From 2c704b015555a770fcdc24523bfeaa738b345954 Mon Sep 17 00:00:00 2001 From: Alexander Bainczyk Date: Sun, 5 Jun 2016 11:17:59 +0200 Subject: [PATCH 32/44] fixed that multiple results could not be selected for comparison --- main/src/main/webapp/src/js/components/views/resultsView.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main/src/main/webapp/src/js/components/views/resultsView.js b/main/src/main/webapp/src/js/components/views/resultsView.js index d871fbacf..0ce0a657b 100644 --- a/main/src/main/webapp/src/js/components/views/resultsView.js +++ b/main/src/main/webapp/src/js/components/views/resultsView.js @@ -107,7 +107,7 @@ class ResultsView { */ openSelectedResults() { if (this.selectedResults.length > 0) { - const testNos = _.pluck(this.selectedResults, 'testNo'); + const testNos = this.selectedResults.map(r => r.testNo); this.$state.go('resultsCompare', {testNos: testNos.join(',')}); } } From 2393dd4e216bcb5254b0c662be0aa1000fbc4608 Mon Sep 17 00:00:00 2001 From: Alexander Bainczyk Date: Thu, 9 Jun 2016 13:25:52 +0200 Subject: [PATCH 33/44] added changelog file with information for v1.1 --- CHANGELOG.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 000000000..218c53742 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,19 @@ +# ALEX v1.1 + +## Bug Fixes + +* Allow symbol groups to be edited via the frontend again +* Properly close connectors after finished learning + +## Features + +* New action: Execute JavaScript +* GoTo action and Call action support Basic HTTP authentication +* Export and import projects +* New REST endpoint: rest/users/batch/{ids} to delete multiple users at once +* Additional visual enhancements + +## Other + +* Updated frontend and backend dependencies +* Removed requirement to have grunt and grunt-cli installed globally \ No newline at end of file From 4b1056f896715c6f40335243d4486851b12bfba3 Mon Sep 17 00:00:00 2001 From: Alexander Bainczyk Date: Thu, 9 Jun 2016 15:46:45 +0200 Subject: [PATCH 34/44] fixed that when finished refining a model, the newest hypothesis was not displayed, but the first --- main/src/main/webapp/src/js/components/learnResultPanel.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/main/src/main/webapp/src/js/components/learnResultPanel.js b/main/src/main/webapp/src/js/components/learnResultPanel.js index b8d08f58c..3b10fd7a1 100644 --- a/main/src/main/webapp/src/js/components/learnResultPanel.js +++ b/main/src/main/webapp/src/js/components/learnResultPanel.js @@ -87,6 +87,10 @@ class LearnResultPanel { $element.children()[0].style.left = ((100 / from) * (index)) + '%'; }); + $scope.$watch(() => this.result, () => { + if (this.result) this.pointer = this.result.steps.length - 1; + }); + // wait for hypothesis layout settings to change EventBus.on(events.HYPOTHESIS_LAYOUT_UPDATED, (evt, data) => { this.layoutSettings = data.settings; From aeb0de086e8d04f6cf035659c64bddf6f3c75285 Mon Sep 17 00:00:00 2001 From: Alexander Schieweck Date: Mon, 30 May 2016 13:03:43 +0200 Subject: [PATCH 35/44] fixed that the Learning Context, including the Web Driver, was sometimes not properly closed. This time for real! I promise! --- .../learnlib/alex/core/learner/Learner.java | 5 ++- .../alex/core/learner/LearnerThread.java | 1 + .../core/learner/connectors/Connector.java | 2 +- .../connectors/ConnectorContextHandler.java | 2 +- .../learner/connectors/ConnectorManager.java | 16 +++---- .../alex/core/learner/LearnerTest.java | 9 ++++ .../connectors/ConnectorManagerTest.java | 42 +++++++++++++++++++ 7 files changed, 67 insertions(+), 10 deletions(-) create mode 100644 main/src/test/java/de/learnlib/alex/core/learner/connectors/ConnectorManagerTest.java diff --git a/main/src/main/java/de/learnlib/alex/core/learner/Learner.java b/main/src/main/java/de/learnlib/alex/core/learner/Learner.java index fed3f101a..ac5f2cec1 100644 --- a/main/src/main/java/de/learnlib/alex/core/learner/Learner.java +++ b/main/src/main/java/de/learnlib/alex/core/learner/Learner.java @@ -452,8 +452,11 @@ public List readOutputs(User user, Project project, Symbol resetSymbol, ConnectorManager connectors = contextHandler.createContext(); try { - return symbols.stream().map(s -> s.execute(connectors).toString()).collect(Collectors.toList()); + List output = symbols.stream().map(s -> s.execute(connectors).toString()).collect(Collectors.toList()); + connectors.dispose(); + return output; } catch (Exception e) { + connectors.dispose(); throw new LearnerException("Could not read the outputs", e); } } diff --git a/main/src/main/java/de/learnlib/alex/core/learner/LearnerThread.java b/main/src/main/java/de/learnlib/alex/core/learner/LearnerThread.java index a7e1b3c72..e7c9af740 100644 --- a/main/src/main/java/de/learnlib/alex/core/learner/LearnerThread.java +++ b/main/src/main/java/de/learnlib/alex/core/learner/LearnerThread.java @@ -249,6 +249,7 @@ public void run() { LOGGER.warn("Something in the LearnerThread went wrong:", e); currentStep.setErrorText(e.getMessage()); learnerResultDAO.saveStep(result, currentStep); + sul.post(); } LOGGER.trace("LearnThread.run() - exit"); diff --git a/main/src/main/java/de/learnlib/alex/core/learner/connectors/Connector.java b/main/src/main/java/de/learnlib/alex/core/learner/connectors/Connector.java index 20bdd27b7..efb49e347 100644 --- a/main/src/main/java/de/learnlib/alex/core/learner/connectors/Connector.java +++ b/main/src/main/java/de/learnlib/alex/core/learner/connectors/Connector.java @@ -29,7 +29,7 @@ public interface Connector { /** * Dispose the connector. - * This method will be called after the learning and allow to do necessary clean ups. + * This method will be called after the learning and allows to do necessary clean ups. * After this method is called, the connector should not work anymore. */ void dispose(); diff --git a/main/src/main/java/de/learnlib/alex/core/learner/connectors/ConnectorContextHandler.java b/main/src/main/java/de/learnlib/alex/core/learner/connectors/ConnectorContextHandler.java index f01810a5d..796982641 100644 --- a/main/src/main/java/de/learnlib/alex/core/learner/connectors/ConnectorContextHandler.java +++ b/main/src/main/java/de/learnlib/alex/core/learner/connectors/ConnectorContextHandler.java @@ -84,7 +84,7 @@ private void executeResetSymbol() throws LearnerException { @Override public void disposeContext(ConnectorManager connector) { - connectors.forEach(Connector::dispose); + connectors.dispose(); } } diff --git a/main/src/main/java/de/learnlib/alex/core/learner/connectors/ConnectorManager.java b/main/src/main/java/de/learnlib/alex/core/learner/connectors/ConnectorManager.java index 76507700e..8a33fdbe9 100644 --- a/main/src/main/java/de/learnlib/alex/core/learner/connectors/ConnectorManager.java +++ b/main/src/main/java/de/learnlib/alex/core/learner/connectors/ConnectorManager.java @@ -37,13 +37,6 @@ public ConnectorManager() { this.connectors = new HashMap<>(); } - /** - * Removes all connectors. - */ - public void reset() { - this.connectors.clear(); - } - /** * Adds a new connector to the manager. * @@ -69,4 +62,13 @@ public T getConnector(Class type) { public Iterator iterator() { return connectors.values().iterator(); } + + /** + * Disposes all connectors and clear the list of managed connectors. + * This method is idempotent. + */ + public void dispose() { + connectors.forEach((t, u) -> u.dispose()); + } + } diff --git a/main/src/test/java/de/learnlib/alex/core/learner/LearnerTest.java b/main/src/test/java/de/learnlib/alex/core/learner/LearnerTest.java index 642ff93b3..e55fabef8 100644 --- a/main/src/test/java/de/learnlib/alex/core/learner/LearnerTest.java +++ b/main/src/test/java/de/learnlib/alex/core/learner/LearnerTest.java @@ -30,6 +30,7 @@ import de.learnlib.alex.core.learner.connectors.ConnectorManager; import de.learnlib.alex.core.learner.connectors.WebBrowser; import de.learnlib.alex.exceptions.NotFoundException; +import de.learnlib.mapper.ContextExecutableInputSUL; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; @@ -46,6 +47,7 @@ import static org.mockito.BDDMockito.given; import static org.mockito.Matchers.any; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; @RunWith(MockitoJUnitRunner.class) public class LearnerTest { @@ -142,17 +144,24 @@ public void shouldOnlyStartTheThreadOnce() throws NotFoundException { public void shouldReadTheCorrectOutputOfSomeSymbols() { Symbol resetSymbol = mock(Symbol.class); given(resetSymbol.execute(any(ConnectorManager.class))).willReturn(ExecuteResult.OK); + // List symbols = new LinkedList<>(); for (int i = 0; i < SYMBOL_AMOUNT; i++) { Symbol symbol = mock(Symbol.class); given(symbol.execute(any(ConnectorManager.class))).willReturn(ExecuteResult.OK); symbols.add(symbol); } + // + ConnectorContextHandler contextHandler = mock(ConnectorContextHandler.class); + ConnectorManager connectorManager = mock(ConnectorManager.class); + given(contextHandler.createContext()).willReturn(connectorManager); + given(contextHandlerFactory.createContext(project, WebBrowser.HTMLUNITDRIVER)).willReturn(contextHandler); List outputs = learner.readOutputs(user, project, resetSymbol, symbols); assertEquals(symbols.size(), outputs.size()); assertTrue("at least one output was not OK", outputs.stream().allMatch(output -> output.equals("OK"))); + verify(connectorManager).dispose(); } } diff --git a/main/src/test/java/de/learnlib/alex/core/learner/connectors/ConnectorManagerTest.java b/main/src/test/java/de/learnlib/alex/core/learner/connectors/ConnectorManagerTest.java new file mode 100644 index 000000000..9d209ea5a --- /dev/null +++ b/main/src/test/java/de/learnlib/alex/core/learner/connectors/ConnectorManagerTest.java @@ -0,0 +1,42 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package de.learnlib.alex.core.learner.connectors; + +import org.junit.Test; + +import static org.junit.Assert.assertFalse; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + + +public class ConnectorManagerTest { + + @Test + public void shouldDisposeAllConnectors() { + Connector connector1 = mock(Connector.class); + Connector connector2 = mock(Connector.class); + // + ConnectorManager manager = new ConnectorManager(); + manager.addConnector(VariableStoreConnector.class, connector1); + manager.addConnector(CounterStoreConnector.class, connector2); + + manager.dispose(); + + verify(connector1).dispose(); + verify(connector2).dispose(); + } + +} \ No newline at end of file From a2223f02b4049f6ec377b164100a0817ab489a83 Mon Sep 17 00:00:00 2001 From: Alexander Schieweck Date: Thu, 9 Jun 2016 21:31:30 +0200 Subject: [PATCH 36/44] fixed the counting of the issued MQs. --- .../alex/core/learner/LearnerThread.java | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/main/src/main/java/de/learnlib/alex/core/learner/LearnerThread.java b/main/src/main/java/de/learnlib/alex/core/learner/LearnerThread.java index e7c9af740..9ad0a5ff4 100644 --- a/main/src/main/java/de/learnlib/alex/core/learner/LearnerThread.java +++ b/main/src/main/java/de/learnlib/alex/core/learner/LearnerThread.java @@ -148,8 +148,8 @@ public LearnerThread(LearnerResultDAO learnerResultDAO, LearnerResult result, Co ceiSUL = new ContextExecutableInputSUL<>(context); SUL mappedSUL = Mappers.apply(symbolMapper, ceiSUL); this.cachedSUL = SULCaches.createCache(this.sigma, mappedSUL); - resetCounterSUL = new ResetCounterSUL<>("reset counter", this.cachedSUL); - symbolCounterSUL = new SymbolCounterSUL<>("symbol counter", resetCounterSUL); + this.resetCounterSUL = new ResetCounterSUL<>("resets", this.cachedSUL); + this.symbolCounterSUL = new SymbolCounterSUL<>("symbols used", resetCounterSUL); this.sul = symbolCounterSUL; this.mqOracle = new SULOracle<>(sul); @@ -180,8 +180,12 @@ public LearnerThread(LearnerResultDAO learnerResultDAO, LearnerResult result, SU result.setSigma(AlphabetProxy.createFrom(sigma)); this.cachedSUL = existingSUL; - resetCounterSUL = new ResetCounterSUL<>("reset counter", this.cachedSUL); - symbolCounterSUL = new SymbolCounterSUL<>("symbol counter", resetCounterSUL); + this.resetCounterSUL = new ResetCounterSUL<>("resets", this.cachedSUL); + this.resetCounterSUL.getStatisticalData().increment(result.getStatistics().getMqsUsed()); + + this.symbolCounterSUL = new SymbolCounterSUL<>("symbols used", resetCounterSUL); + this.symbolCounterSUL.getStatisticalData().increment(result.getStatistics().getSymbolsUsed()); + this.sul = symbolCounterSUL; this.mqOracle = new SULOracle<>(sul); @@ -360,9 +364,9 @@ private void rememberMetaData() throws NotFoundException { + "(start: " + startTime + ", end: " + currentTime + ")."); long mqUsedDiff = resetCounterSUL.getStatisticalData().getCount() - statistics.getMqsUsed(); - statistics.setMqsUsed(Math.abs(mqUsedDiff)); + statistics.setMqsUsed(mqUsedDiff); long symbolUsedDiff = symbolCounterSUL.getStatisticalData().getCount() - statistics.getSymbolsUsed(); - statistics.setSymbolsUsed(Math.abs(symbolUsedDiff)); + statistics.setSymbolsUsed(symbolUsedDiff); // algorithm information LearnAlgorithms algorithm = result.getAlgorithm(); From c15f1e5eda7f4669d03955931a4eb53040896af3 Mon Sep 17 00:00:00 2001 From: Alexander Schieweck Date: Thu, 9 Jun 2016 23:13:43 +0200 Subject: [PATCH 37/44] the Frontend is now included in the .war again (...except our fancy fonts, but who reads anyway?) --- main/pom.xml | 33 +++++++++------------------------ 1 file changed, 9 insertions(+), 24 deletions(-) diff --git a/main/pom.xml b/main/pom.xml index 0497d074c..3b919e622 100644 --- a/main/pom.xml +++ b/main/pom.xml @@ -246,8 +246,8 @@ src/main/webapp **/node_modules/** - **/tests/** - **/src/** + **/webapp/tests/** + **/webapp/src/** package.json gruntfile.js @@ -279,31 +279,16 @@ ${war-plugin.version} false - - - src/main/webapp - - **/node_modules/** - **/tests/** - **/src/** - package.json - gruntfile.js - - - **/node_modules/font-awesome/fonts/** - - - - **/node_modules/** - **/tests/** - **/src/** - package.json + **/node_modules/**, + **/tests/**, + **/src/**, + package.json, gruntfile.js - - **/node_modules/font-awesome/fonts/** - + + + From 84840e2064cb28f2de86d70f9dd530e2ce957c20 Mon Sep 17 00:00:00 2001 From: Alexander Schieweck Date: Thu, 9 Jun 2016 23:29:26 +0200 Subject: [PATCH 38/44] small hack to include the awesome font into the war file. --- main/pom.xml | 3 --- main/src/main/webapp/src/scss/_font-awesome.scss | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/main/pom.xml b/main/pom.xml index 3b919e622..70fb08e6a 100644 --- a/main/pom.xml +++ b/main/pom.xml @@ -286,9 +286,6 @@ package.json, gruntfile.js - - - diff --git a/main/src/main/webapp/src/scss/_font-awesome.scss b/main/src/main/webapp/src/scss/_font-awesome.scss index e1bb4766a..4877f3abd 100644 --- a/main/src/main/webapp/src/scss/_font-awesome.scss +++ b/main/src/main/webapp/src/scss/_font-awesome.scss @@ -1,4 +1,4 @@ -$fa-font-path: "../node_modules/font-awesome/fonts" !default; +$fa-font-path: "../fonts" !default; @import "../../node_modules/font-awesome/scss/variables"; @import "../../node_modules/font-awesome/scss/mixins"; From 828bf0383524818a7eaa9c7160e7eb4d2997bcec Mon Sep 17 00:00:00 2001 From: Alexander Schieweck Date: Thu, 9 Jun 2016 23:31:23 +0200 Subject: [PATCH 39/44] added additional file for the small hack to include the awesome font into the war file. --- main/src/main/webapp/assets/fonts | 1 + 1 file changed, 1 insertion(+) create mode 120000 main/src/main/webapp/assets/fonts diff --git a/main/src/main/webapp/assets/fonts b/main/src/main/webapp/assets/fonts new file mode 120000 index 000000000..442164142 --- /dev/null +++ b/main/src/main/webapp/assets/fonts @@ -0,0 +1 @@ +../node_modules/font-awesome/fonts \ No newline at end of file From 50ae823187ea3229888f5ce0ce27499f53ccc53a Mon Sep 17 00:00:00 2001 From: Alexander Schieweck Date: Fri, 10 Jun 2016 10:24:38 +0200 Subject: [PATCH 40/44] link to generate a complete chart for one result now works again. --- main/src/main/webapp/src/html/pages/statistics.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main/src/main/webapp/src/html/pages/statistics.html b/main/src/main/webapp/src/html/pages/statistics.html index 2bafcf8d5..de56a11e6 100644 --- a/main/src/main/webapp/src/html/pages/statistics.html +++ b/main/src/main/webapp/src/html/pages/statistics.html @@ -18,7 +18,7 @@
  • - + Complete Result
  • From 789750baca702fee125e8c9658cf2911a7f0753e Mon Sep 17 00:00:00 2001 From: Alexander Schieweck Date: Fri, 10 Jun 2016 10:25:45 +0200 Subject: [PATCH 41/44] fixed that learner threads sometimes did not terminated properly. --- .../java/de/learnlib/alex/core/learner/LearnerThread.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/main/src/main/java/de/learnlib/alex/core/learner/LearnerThread.java b/main/src/main/java/de/learnlib/alex/core/learner/LearnerThread.java index 9ad0a5ff4..af8480bcd 100644 --- a/main/src/main/java/de/learnlib/alex/core/learner/LearnerThread.java +++ b/main/src/main/java/de/learnlib/alex/core/learner/LearnerThread.java @@ -254,10 +254,11 @@ public void run() { currentStep.setErrorText(e.getMessage()); learnerResultDAO.saveStep(result, currentStep); sul.post(); - } + } finally { + LOGGER.trace("LearnThread.run() - exit"); + finished = true; - LOGGER.trace("LearnThread.run() - exit"); - finished = true; + } } /** From c73b8b84f4ff722d664f69a423ed6d329f98370a Mon Sep 17 00:00:00 2001 From: Alexander Schieweck Date: Fri, 10 Jun 2016 10:59:34 +0200 Subject: [PATCH 42/44] now with error messages if Selenium fails to create a WebDriver, e.g. it could not find the right browser. --- .../alex/core/learner/LearnerThread.java | 8 +++++++- .../alex/core/learner/connectors/Connector.java | 2 +- .../connectors/ConnectorContextHandler.java | 8 +++++++- .../alex/core/learner/connectors/WebBrowser.java | 16 ++++------------ .../learner/connectors/WebSiteConnector.java | 2 +- 5 files changed, 20 insertions(+), 16 deletions(-) diff --git a/main/src/main/java/de/learnlib/alex/core/learner/LearnerThread.java b/main/src/main/java/de/learnlib/alex/core/learner/LearnerThread.java index af8480bcd..9e5e04702 100644 --- a/main/src/main/java/de/learnlib/alex/core/learner/LearnerThread.java +++ b/main/src/main/java/de/learnlib/alex/core/learner/LearnerThread.java @@ -251,8 +251,14 @@ public void run() { learn(); } catch (Exception e) { LOGGER.warn("Something in the LearnerThread went wrong:", e); - currentStep.setErrorText(e.getMessage()); + + String message = e.getMessage(); + if (message == null) { + message = e.getClass().getName(); + } + currentStep.setErrorText(message); learnerResultDAO.saveStep(result, currentStep); + sul.post(); } finally { LOGGER.trace("LearnThread.run() - exit"); diff --git a/main/src/main/java/de/learnlib/alex/core/learner/connectors/Connector.java b/main/src/main/java/de/learnlib/alex/core/learner/connectors/Connector.java index efb49e347..105e8d5f5 100644 --- a/main/src/main/java/de/learnlib/alex/core/learner/connectors/Connector.java +++ b/main/src/main/java/de/learnlib/alex/core/learner/connectors/Connector.java @@ -25,7 +25,7 @@ public interface Connector { * Method called during the reset of the SUL. * Set the connector back to init. state. */ - void reset(); + void reset() throws Exception; /** * Dispose the connector. diff --git a/main/src/main/java/de/learnlib/alex/core/learner/connectors/ConnectorContextHandler.java b/main/src/main/java/de/learnlib/alex/core/learner/connectors/ConnectorContextHandler.java index 796982641..5c2f39c1a 100644 --- a/main/src/main/java/de/learnlib/alex/core/learner/connectors/ConnectorContextHandler.java +++ b/main/src/main/java/de/learnlib/alex/core/learner/connectors/ConnectorContextHandler.java @@ -61,7 +61,13 @@ public void setResetSymbol(Symbol resetSymbol) { @Override public ConnectorManager createContext() throws LearnerException { - connectors.forEach(Connector::reset); + for (Connector connector : connectors) { + try { + connector.reset(); + } catch (Exception e) { + throw new LearnerException(e.getMessage(), e); + } + } executeResetSymbol(); diff --git a/main/src/main/java/de/learnlib/alex/core/learner/connectors/WebBrowser.java b/main/src/main/java/de/learnlib/alex/core/learner/connectors/WebBrowser.java index a494332c1..ab6c5ddc4 100644 --- a/main/src/main/java/de/learnlib/alex/core/learner/connectors/WebBrowser.java +++ b/main/src/main/java/de/learnlib/alex/core/learner/connectors/WebBrowser.java @@ -71,7 +71,7 @@ public String toString() { * * @return An instance of a web driver. */ - public WebDriver getWebDriver() { + public WebDriver getWebDriver() throws Exception { try { if (this == HTMLUNITDRIVER) { HtmlUnitDriver driver = (HtmlUnitDriver) webDriverClass.getConstructor(BrowserVersion.class) @@ -81,18 +81,10 @@ public WebDriver getWebDriver() { } else { return (WebDriver) webDriverClass.getConstructor().newInstance(); } - // todo: logging and error handling - } catch (InstantiationException e) { - e.printStackTrace(); - } catch (IllegalAccessException e) { - e.printStackTrace(); - } catch (InvocationTargetException e) { - e.printStackTrace(); - } catch (NoSuchMethodException e) { - e.printStackTrace(); + } catch (Exception e) { + LOGGER.warn("Could not create a WebDriver.", e); + throw e; } - - return null; } /** diff --git a/main/src/main/java/de/learnlib/alex/core/learner/connectors/WebSiteConnector.java b/main/src/main/java/de/learnlib/alex/core/learner/connectors/WebSiteConnector.java index df1e1f482..bba747dd9 100644 --- a/main/src/main/java/de/learnlib/alex/core/learner/connectors/WebSiteConnector.java +++ b/main/src/main/java/de/learnlib/alex/core/learner/connectors/WebSiteConnector.java @@ -72,7 +72,7 @@ public WebSiteConnector(String baseUrl, WebBrowser browser) { * Try to clear all data from the browser, including Cookies, local storage & session storage. */ @Override - public void reset() { + public void reset() throws Exception { this.driver = browser.getWebDriver(); this.driver.manage().timeouts().implicitlyWait(IMPLICITLY_WAIT_TIME, TimeUnit.SECONDS); this.driver.manage().timeouts().pageLoadTimeout(PAGE_LOAD_TIMEOUT_TIME, TimeUnit.SECONDS); From 407ab186cbefff3018d3e315d67529c412feb689 Mon Sep 17 00:00:00 2001 From: Alexander Schieweck Date: Fri, 10 Jun 2016 11:48:12 +0200 Subject: [PATCH 43/44] prepared for the next release. --- main/pom.xml | 10 +++++----- .../connectors/ConnectorContextHandlerTest.java | 2 +- pom.xml | 14 +++++++------- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/main/pom.xml b/main/pom.xml index 70fb08e6a..6a4d366f3 100644 --- a/main/pom.xml +++ b/main/pom.xml @@ -20,8 +20,8 @@ - 1.3.3.RELEASE - 2.5 + 1.3.5.RELEASE + 2.6.1 de.learnlib.alex.App @@ -60,7 +60,7 @@ org.springframework spring-context - 4.2.5.RELEASE + 4.2.6.RELEASE @@ -92,7 +92,7 @@ org.hsqldb hsqldb - 2.3.3 + 2.3.4 @@ -119,7 +119,7 @@ org.jsoup jsoup - 1.8.3 + 1.9.2 diff --git a/main/src/test/java/de/learnlib/alex/core/learner/connectors/ConnectorContextHandlerTest.java b/main/src/test/java/de/learnlib/alex/core/learner/connectors/ConnectorContextHandlerTest.java index 47f932c14..ea325dfbd 100644 --- a/main/src/test/java/de/learnlib/alex/core/learner/connectors/ConnectorContextHandlerTest.java +++ b/main/src/test/java/de/learnlib/alex/core/learner/connectors/ConnectorContextHandlerTest.java @@ -46,7 +46,7 @@ public void setUp() { } @Test - public void shouldCreateTheContextCorrectly() { + public void shouldCreateTheContextCorrectly() throws Exception { given(resetSymbol.execute(any(ConnectorManager.class))).willReturn(ExecuteResult.OK); Connector connector1 = mock(VariableStoreConnector.class); handler.addConnector(connector1); diff --git a/pom.xml b/pom.xml index 1a6debe15..40838623b 100644 --- a/pom.xml +++ b/pom.xml @@ -99,28 +99,28 @@ 3.1 2.10 - 2.6 + 3.0.0 2.10.3 5.6.6 2.19 - 2.19 + 2.19.1 2.6 2.17 2.7 - 3.0.2 - 3.4 + 3.0.3 + 3.6 2.2 - 2.22.2 + 2.23.1 5.2.4.Final 0.12.0 0.6.0 2.53.0 9.3.5.v20151012 - 0.5.0 - 1.2.4 + 0.5.1 + 1.2.5 From 6e3500d88f9740e6971b2b9fc3c85032683ebd45 Mon Sep 17 00:00:00 2001 From: Alexander Schieweck Date: Fri, 10 Jun 2016 11:52:09 +0200 Subject: [PATCH 44/44] incremented version number. --- api/pom.xml | 2 +- main/pom.xml | 2 +- main/src/main/webapp/package.json | 2 +- pom.xml | 2 +- processors/pom.xml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/api/pom.xml b/api/pom.xml index f95150d99..e9201bb86 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -7,7 +7,7 @@ de.learnlib.alex alex-parent - 1.1-SNAPSHOT + 1.1 ../pom.xml diff --git a/main/pom.xml b/main/pom.xml index 6a4d366f3..c71e51865 100644 --- a/main/pom.xml +++ b/main/pom.xml @@ -7,7 +7,7 @@ de.learnlib.alex alex-parent - 1.1-SNAPSHOT + 1.1 ../pom.xml diff --git a/main/src/main/webapp/package.json b/main/src/main/webapp/package.json index 957dce2f6..1b20319c6 100644 --- a/main/src/main/webapp/package.json +++ b/main/src/main/webapp/package.json @@ -1,6 +1,6 @@ { "name": "ALEX", - "version": "1.0.0", + "version": "1.1.0", "description": "An interface for Active Automata Learning", "repository": { "type": "git", diff --git a/pom.xml b/pom.xml index 40838623b..8054bf255 100644 --- a/pom.xml +++ b/pom.xml @@ -22,7 +22,7 @@ de.learnlib.alex alex-parent - 1.1-SNAPSHOT + 1.1 pom Automata Learning Experience (ALEX) diff --git a/processors/pom.xml b/processors/pom.xml index 138f64e3a..b0a6de978 100644 --- a/processors/pom.xml +++ b/processors/pom.xml @@ -7,7 +7,7 @@ de.learnlib.alex alex-parent - 1.1-SNAPSHOT + 1.1 ../pom.xml