commit 8808bbe4ff9c0c60da0bf68b09e966c5e975e21a
Author: Ondřej Váně <onry.vane@gmail.com>
Date:   Mon May 3 14:48:25 2021 +0200

    Implemented changable configuration by UI
    
    - values extracted only for too long sprint anti pattern

diff --git a/src/main/java/cz/zcu/fav/kiv/antipatterndetectionapp/controller/AppController.java b/src/main/java/cz/zcu/fav/kiv/antipatterndetectionapp/controller/AppController.java
index 50ace5e..f9262bb 100644
--- a/src/main/java/cz/zcu/fav/kiv/antipatterndetectionapp/controller/AppController.java
+++ b/src/main/java/cz/zcu/fav/kiv/antipatterndetectionapp/controller/AppController.java
@@ -82,4 +82,26 @@ public class AppController {
     public String about() {
         return "about";
     }
+
+    @GetMapping("/configuration")
+    public String configuration(Model model) {
+        model.addAttribute("antiPatterns", antiPatternService.antiPatternsToModel(antiPatternService.getAllAntiPatterns()));
+        return "configuration";
+    }
+
+    @PostMapping("/configuration")
+    public String configurationPost(Model model,
+                                    @RequestParam(value = "configValues", required = false) String[] configValues,
+                                    @RequestParam(value = "configNames", required = false) String[] configNames) {
+
+        if (antiPatternService.saveNewConfiguration(configNames, configValues)) {
+            model.addAttribute("successMessage", "All threshold values has been successfully saved.");
+        } else {
+            model.addAttribute("errorMessage", "One or more configuration values are not in correct format");
+        }
+
+        model.addAttribute("antiPatterns", antiPatternService.antiPatternsToModel(antiPatternService.getAllAntiPatterns()));
+
+        return "configuration";
+    }
 }
diff --git a/src/main/java/cz/zcu/fav/kiv/antipatterndetectionapp/detecting/detectors/TooLongSprintDetectorImpl.java b/src/main/java/cz/zcu/fav/kiv/antipatterndetectionapp/detecting/detectors/TooLongSprintDetectorImpl.java
index 75dd055..66dce73 100644
--- a/src/main/java/cz/zcu/fav/kiv/antipatterndetectionapp/detecting/detectors/TooLongSprintDetectorImpl.java
+++ b/src/main/java/cz/zcu/fav/kiv/antipatterndetectionapp/detecting/detectors/TooLongSprintDetectorImpl.java
@@ -1,14 +1,12 @@
 package cz.zcu.fav.kiv.antipatterndetectionapp.detecting.detectors;
 
 import cz.zcu.fav.kiv.antipatterndetectionapp.detecting.DatabaseConnection;
-import cz.zcu.fav.kiv.antipatterndetectionapp.model.AntiPattern;
-import cz.zcu.fav.kiv.antipatterndetectionapp.model.Project;
-import cz.zcu.fav.kiv.antipatterndetectionapp.model.QueryResultItem;
-import cz.zcu.fav.kiv.antipatterndetectionapp.model.ResultDetail;
+import cz.zcu.fav.kiv.antipatterndetectionapp.model.*;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
@@ -23,19 +21,21 @@ public class TooLongSprintDetectorImpl implements AntiPatternDetector {
                     "maximum 3 weeks). It could also be detected here if the length " +
                     "of the iteration does not change often (It can change at the " +
                     "beginning and at the end of the project, but it should not " +
-                    "change in the already started project).");
+                    "change in the already started project).",
+            new HashMap<>() {{
+                put("maxIterationLength", new Configuration<Integer>("maxIterationLength",
+                        "Max Iteration Length",
+                        "Maximum iteration length in days", 21));
+                put("maxNumberOfTooLongIterations", new Configuration<Integer>("maxNumberOfTooLongIterations",
+                        "Max number of foo long iterations",
+                        "Maximum number of too long iterations in project", 1));
+            }}
+    );
 
     private final String SQL_FILE_NAME = "too_long_sprint.sql";
     // sql queries loaded from sql file
     private List<String> sqlQueries;
 
-    /**
-     * SETTINGS
-     */
-    private static final int MAX_NUMBER_OF_TOO_LONG_ITERATIONS = 1;
-    private static final int MAX_ITERATION_LENGTH = 21;
-
-
     @Override
     public AntiPattern getAntiPatternModel() {
         return this.antiPattern;
@@ -51,10 +51,18 @@ public class TooLongSprintDetectorImpl implements AntiPatternDetector {
         this.sqlQueries = queries;
     }
 
+    private Integer getMaxIterationLength(){
+        return (Integer) this.antiPattern.getConfigurations().get("maxIterationLength").getValue();
+    }
+
+    private Integer getMaxNumberOfTooLongIterations(){
+        return (Integer) this.antiPattern.getConfigurations().get("maxNumberOfTooLongIterations").getValue();
+    }
+
     /**
      * Postup detekce:
      *      1) najít všechny iterace danného projektu
-     *      2) odebrat první a poslední iteraci( ty mohou být z důvodu nastartování projektu dlouhé)
+     *      2) odebrat první a poslední iteraci ( ty mohou být z důvodu nastartování projektu dlouhé)
      *      3) zjistit jejich délku (rozdíl mezi start date a end date)
      *      4) pokud iterace přesháne délku 21 dní (3 týdny), tak jsou označeny jako moc dlouhé
      *      5) pokud je nalezena jedna nebo více iterací jako dlouhé, tak je anti pattern detekován
@@ -66,6 +74,11 @@ public class TooLongSprintDetectorImpl implements AntiPatternDetector {
     @Override
     public QueryResultItem analyze(Project project, DatabaseConnection databaseConnection) {
 
+        // get configuration
+        int maxIterationLength = getMaxIterationLength();
+        int maxNumberOfTooLongIterations = getMaxNumberOfTooLongIterations();
+
+        // auxiliary variables
         int numberOfLongIterations = 0;
         int totalCountOfIteration = 0;
 
@@ -77,14 +90,14 @@ public class TooLongSprintDetectorImpl implements AntiPatternDetector {
             if (!iterationLengths.containsKey("iterationLength") || iterationLengths.get("iterationLength") == null)
                 continue;
             int iterationLength = (int) iterationLengths.get("iterationLength");
-            if (iterationLength > MAX_ITERATION_LENGTH) {
+            if (iterationLength > maxIterationLength) {
                 numberOfLongIterations++;
             }
         }
         List<ResultDetail> resultDetails = new ArrayList<>();
         resultDetails.add(new ResultDetail("Count of iterations without first and last", String.valueOf(totalCountOfIteration)));
         resultDetails.add(new ResultDetail("Number of too long iterations", String.valueOf(numberOfLongIterations)));
-        if (numberOfLongIterations >= MAX_NUMBER_OF_TOO_LONG_ITERATIONS) {
+        if (numberOfLongIterations >= maxNumberOfTooLongIterations) {
             resultDetails.add(new ResultDetail("Conclusion", "One or more iteration is too long"));
         } else {
             resultDetails.add(new ResultDetail("Conclusion", "All iterations in limit"));
@@ -93,6 +106,6 @@ public class TooLongSprintDetectorImpl implements AntiPatternDetector {
         LOGGER.info(this.antiPattern.getPrintName());
         LOGGER.info(resultDetails.toString());
 
-        return new QueryResultItem(this.antiPattern, numberOfLongIterations >= 1, resultDetails);
+        return new QueryResultItem(this.antiPattern, numberOfLongIterations >= maxNumberOfTooLongIterations, resultDetails);
     }
 }
diff --git a/src/main/java/cz/zcu/fav/kiv/antipatterndetectionapp/model/AntiPattern.java b/src/main/java/cz/zcu/fav/kiv/antipatterndetectionapp/model/AntiPattern.java
index 42513b6..f060ff9 100644
--- a/src/main/java/cz/zcu/fav/kiv/antipatterndetectionapp/model/AntiPattern.java
+++ b/src/main/java/cz/zcu/fav/kiv/antipatterndetectionapp/model/AntiPattern.java
@@ -1,11 +1,14 @@
 package cz.zcu.fav.kiv.antipatterndetectionapp.model;
 
+import java.util.Map;
+
 public class AntiPattern {
 
     private Long id;
     private String printName;
     private String name;
     private String description;
+    private Map<String, Configuration> configurations;
 
     public AntiPattern(Long id, String printName, String name, String description) {
         this.id = id;
@@ -14,6 +17,14 @@ public class AntiPattern {
         this.description = description;
     }
 
+    public AntiPattern(Long id, String printName, String name, String description, Map<String, Configuration> configurations) {
+        this.id = id;
+        this.printName = printName;
+        this.name = name;
+        this.description = description;
+        this.configurations = configurations;
+    }
+
     public Long getId() {
         return id;
     }
@@ -46,6 +57,14 @@ public class AntiPattern {
         this.description = description;
     }
 
+    public Map<String, Configuration> getConfigurations() {
+        return configurations;
+    }
+
+    public void setConfigurations(Map<String, Configuration> configurations) {
+        this.configurations = configurations;
+    }
+
     @Override
     public String toString() {
         return "AntiPattern{" +
diff --git a/src/main/java/cz/zcu/fav/kiv/antipatterndetectionapp/model/Configuration.java b/src/main/java/cz/zcu/fav/kiv/antipatterndetectionapp/model/Configuration.java
new file mode 100644
index 0000000..e4f835b
--- /dev/null
+++ b/src/main/java/cz/zcu/fav/kiv/antipatterndetectionapp/model/Configuration.java
@@ -0,0 +1,47 @@
+package cz.zcu.fav.kiv.antipatterndetectionapp.model;
+
+public class Configuration<T> {
+    private String name;
+    private String printName;
+    private String description;
+    private T value;
+
+    public Configuration(String name, String printName, String description, T value) {
+        this.name = name;
+        this.printName = printName;
+        this.description = description;
+        this.value = value;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getPrintName() {
+        return printName;
+    }
+
+    public void setPrintName(String printName) {
+        this.printName = printName;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+    public T getValue() {
+        return value;
+    }
+
+    public void setValue(T value) {
+        this.value = value;
+    }
+}
diff --git a/src/main/java/cz/zcu/fav/kiv/antipatterndetectionapp/service/AntiPatternService.java b/src/main/java/cz/zcu/fav/kiv/antipatterndetectionapp/service/AntiPatternService.java
index 1a36654..8cd16e0 100644
--- a/src/main/java/cz/zcu/fav/kiv/antipatterndetectionapp/service/AntiPatternService.java
+++ b/src/main/java/cz/zcu/fav/kiv/antipatterndetectionapp/service/AntiPatternService.java
@@ -17,5 +17,5 @@ public interface AntiPatternService {
 
     List<AntiPatternDetector> getAllAntiPatternsForGivenIds(Long[] ids);
 
-
+    boolean saveNewConfiguration(String[] configNames, String[] configValues);
 }
diff --git a/src/main/java/cz/zcu/fav/kiv/antipatterndetectionapp/service/AntiPatternServiceImpl.java b/src/main/java/cz/zcu/fav/kiv/antipatterndetectionapp/service/AntiPatternServiceImpl.java
index 2b230eb..55e4fc9 100644
--- a/src/main/java/cz/zcu/fav/kiv/antipatterndetectionapp/service/AntiPatternServiceImpl.java
+++ b/src/main/java/cz/zcu/fav/kiv/antipatterndetectionapp/service/AntiPatternServiceImpl.java
@@ -50,4 +50,32 @@ public class AntiPatternServiceImpl implements AntiPatternService {
         }
         return antiPatternDetectors;
     }
+
+    @Override
+    public boolean saveNewConfiguration(String[] configNames, String[] configValues) {
+        List<AntiPatternDetector> antiPatternDetectors = antiPatternRepository.getAllAntiPatterns();
+
+        for (AntiPatternDetector antiPatternDetector : antiPatternDetectors) {
+            // not every anti-pattern should have configuration
+            if (antiPatternDetector.getAntiPatternModel().getConfigurations() == null) {
+                continue;
+            }
+            for (int i = 0; i < configNames.length; i++) {
+                if (antiPatternDetector.getAntiPatternModel().getConfigurations().containsKey(configNames[i])) {
+
+                    if (antiPatternDetector.getAntiPatternModel().getConfigurations().get(configNames[i]).getValue().getClass() == Integer.class) {
+                        try {
+                            antiPatternDetector.getAntiPatternModel().getConfigurations().get(configNames[i]).setValue((Integer.parseInt(configValues[i])));
+                        } catch (NumberFormatException e) {
+                            return false;
+                        }
+
+                    }
+
+
+                }
+            }
+        }
+        return true;
+    }
 }
diff --git a/src/main/webapp/WEB-INF/templates/about.html b/src/main/webapp/WEB-INF/templates/about.html
index 722500b..0ab7e86 100644
--- a/src/main/webapp/WEB-INF/templates/about.html
+++ b/src/main/webapp/WEB-INF/templates/about.html
@@ -21,7 +21,7 @@
         <br>
         <strong>Author:</strong> Ondřej Váně
         <br>
-        <strong>E-mail:</strong> <a href="mailto:onry.vane@gmail.com">onry.vane@gmail.com</a>
+        <strong>E-mail:</strong> <a href="mailto:vaneo@students.zcu.cz">vaneo@students.zcu.cz</a>
     </p>
     <p>
         This application is used to detect presence of anti-patterns in project management tools data. Seven selected anti-patterns are implemented in this application.
diff --git a/src/main/webapp/WEB-INF/templates/configuration.html b/src/main/webapp/WEB-INF/templates/configuration.html
new file mode 100644
index 0000000..3e289f0
--- /dev/null
+++ b/src/main/webapp/WEB-INF/templates/configuration.html
@@ -0,0 +1,70 @@
+<!DOCTYPE HTML>
+<html xmlns:th="http://www.thymeleaf.org">
+<head>
+    <title>Anti Pattern Detector - Configuration</title>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+    <meta name="viewport" content="width=device-width, initial-scale=1">
+    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css">
+    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
+    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js"></script>
+    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
+
+    <style>
+        .analyze-button-container {
+            margin: 20px 40% 40px;
+        }
+    </style>
+
+</head>
+<body>
+<!-- Navigation bar imported -->
+<div th:replace="fragments/navbar :: navBar"></div>
+<!-- ./Navigation bar imported -->
+
+<!-- Page body -->
+<div class="container">
+    <h1>Configuration</h1>
+
+    <!-- Form for configuration values -->
+    <form action="#" th:action="@{/configuration}" th:object="${antiPatterns}" method="post">
+        <div th:each="antiPattern : ${antiPatterns}">
+            <h3 th:text="${antiPattern.printName}"></h3>
+            <div th:each="config : ${antiPattern.configurations}">
+                <div class="form-group row">
+                    <label th:text="${config.value.printName}" th:for="${config.value.name}"
+                           class="col-sm-5 col-form-label"></label>
+                    <div class="col-sm-5">
+                        <input th:value="${config.value.value}" type="number" class="form-control" th:id="${config.value.name}"
+                               name="configValues">
+                        <input th:value="${config.value.name}" style="display: none" class="form-control"
+                               name="configNames">
+                        <small th:text="${config.value.description}" th:value="${config.value.name}"
+                               class="form-text text-muted"></small>
+                    </div>
+                </div>
+
+            </div>
+        </div>
+        <!-- Container for show error message -->
+        <div class="container">
+            <div th:if="${errorMessage}" th:text="${errorMessage}" class="alert alert-danger" role="alert">
+            </div>
+        </div>
+        <!-- ./Container for show error message -->
+        <!-- Container for show success message -->
+        <div class="container">
+            <div th:if="${successMessage}" th:text="${successMessage}" class="alert alert-success" role="alert">
+            </div>
+        </div>
+        <!-- ./Container for show success message -->
+        <div class="analyze-button-container">
+            <button type="submit" class="btn btn-primary btn-lg btn-block">Save</button>
+        </div>
+    </form>
+    <!-- ./Form for configuration values -->
+</div>
+<!-- ./Page body -->
+
+</body>
+</html>
+
diff --git a/src/main/webapp/WEB-INF/templates/fragments/navbar.html b/src/main/webapp/WEB-INF/templates/fragments/navbar.html
index 8d44bdc..1f3cde8 100644
--- a/src/main/webapp/WEB-INF/templates/fragments/navbar.html
+++ b/src/main/webapp/WEB-INF/templates/fragments/navbar.html
@@ -9,6 +9,9 @@
                 <li class="nav-item active">
                     <a class="nav-link" th:href="@{/}">Home</a>
                 </li>
+                <li class="nav-item active">
+                    <a class="nav-link" th:href="@{/configuration}">Configuration</a>
+                </li>
                 <li class="nav-item active">
                     <a class="nav-link" th:href="@{/about}">About</a>
                 </li>
