Revize 2964b75c
Přidáno uživatelem Ondřej Váně před asi 4 roky(ů)
pom.xml | ||
---|---|---|
20 | 20 |
<dependencies> |
21 | 21 |
<dependency> |
22 | 22 |
<groupId>org.springframework.boot</groupId> |
23 |
<artifactId>spring-boot-starter-web</artifactId>
|
|
23 |
<artifactId>spring-boot-starter</artifactId> |
|
24 | 24 |
</dependency> |
25 | 25 |
|
26 | 26 |
<dependency> |
27 | 27 |
<groupId>org.springframework.boot</groupId> |
28 |
<artifactId>spring-boot-starter-tomcat</artifactId>
|
|
29 |
<scope>provided</scope>
|
|
28 |
<artifactId>spring-boot-starter-test</artifactId>
|
|
29 |
<scope>test</scope>
|
|
30 | 30 |
</dependency> |
31 | 31 |
<dependency> |
32 | 32 |
<groupId>org.springframework.boot</groupId> |
33 |
<artifactId>spring-boot-starter-test</artifactId> |
|
34 |
<scope>test</scope> |
|
33 |
<artifactId>spring-boot-starter-web</artifactId> |
|
34 |
</dependency> |
|
35 |
<dependency> |
|
36 |
<groupId>mysql</groupId> |
|
37 |
<artifactId>mysql-connector-java</artifactId> |
|
38 |
<scope>runtime</scope> |
|
39 |
</dependency> |
|
40 |
<dependency> |
|
41 |
<groupId>org.springframework.boot</groupId> |
|
42 |
<artifactId>spring-boot-starter-data-jpa</artifactId> |
|
43 |
</dependency> |
|
44 |
<dependency> |
|
45 |
<groupId>org.springframework.boot</groupId> |
|
46 |
<artifactId>spring-boot-starter-web</artifactId> |
|
47 |
</dependency> |
|
48 |
<dependency> |
|
49 |
<groupId>org.springframework.boot</groupId> |
|
50 |
<artifactId>spring-boot-starter-thymeleaf</artifactId> |
|
35 | 51 |
</dependency> |
36 | 52 |
</dependencies> |
37 | 53 |
|
... | ... | |
42 | 58 |
<artifactId>spring-boot-maven-plugin</artifactId> |
43 | 59 |
</plugin> |
44 | 60 |
</plugins> |
61 |
<resources> |
|
62 |
<resource> |
|
63 |
<directory>src/main/java/resources</directory> |
|
64 |
<includes> |
|
65 |
<include>queries/*.sql</include> |
|
66 |
</includes> |
|
67 |
</resource> |
|
68 |
</resources> |
|
45 | 69 |
</build> |
46 | 70 |
|
47 | 71 |
</project> |
src/main/java/cz/zcu/fav/kiv/antipatterndetectionapp/Utils.java | ||
---|---|---|
1 |
package cz.zcu.fav.kiv.antipatterndetectionapp; |
|
2 |
|
|
3 |
import java.io.BufferedReader; |
|
4 |
import java.io.IOException; |
|
5 |
import java.io.InputStream; |
|
6 |
import java.io.InputStreamReader; |
|
7 |
import java.sql.Date; |
|
8 |
import java.time.LocalDate; |
|
9 |
import java.time.temporal.ChronoUnit; |
|
10 |
import java.util.ArrayList; |
|
11 |
import java.util.List; |
|
12 |
|
|
13 |
public class Utils { |
|
14 |
|
|
15 |
public static String bindValues(String query, List<String> parameters) { |
|
16 |
for (String parameter : parameters) { |
|
17 |
query = query.replace("?", parameter); |
|
18 |
} |
|
19 |
return query; |
|
20 |
} |
|
21 |
|
|
22 |
public static List<String> loadQueryFromFile(String fileName) { |
|
23 |
List<String> queries = new ArrayList<>(); |
|
24 |
InputStream is = Utils.class.getClassLoader().getResourceAsStream(fileName); |
|
25 |
BufferedReader reader = new BufferedReader(new InputStreamReader(is)); |
|
26 |
try { |
|
27 |
while (reader.ready()) { |
|
28 |
|
|
29 |
String line = reader.readLine(); |
|
30 |
if (line.startsWith("select") || line.startsWith("set")) { |
|
31 |
queries.add(line); |
|
32 |
} |
|
33 |
|
|
34 |
} |
|
35 |
} catch (IOException e) { |
|
36 |
e.printStackTrace(); |
|
37 |
} |
|
38 |
return queries; |
|
39 |
} |
|
40 |
|
|
41 |
public static long daysBetween(Date firstDate, Date secondDate) { |
|
42 |
//24-May-2017, change this to your desired Start Date |
|
43 |
LocalDate dateBefore = firstDate.toLocalDate(); |
|
44 |
//29-July-2017, change this to your desired End Date |
|
45 |
LocalDate dateAfter = secondDate.toLocalDate(); |
|
46 |
return ChronoUnit.DAYS.between(dateBefore, dateAfter); |
|
47 |
} |
|
48 |
} |
src/main/java/cz/zcu/fav/kiv/antipatterndetectionapp/controller/AppController.java | ||
---|---|---|
1 |
package cz.zcu.fav.kiv.antipatterndetectionapp.controller; |
|
2 |
|
|
3 |
|
|
4 |
import cz.zcu.fav.kiv.antipatterndetectionapp.detecting.AntiPatternManager; |
|
5 |
import cz.zcu.fav.kiv.antipatterndetectionapp.detecting.AntiPatternsEnum; |
|
6 |
import cz.zcu.fav.kiv.antipatterndetectionapp.model.AntiPattern; |
|
7 |
import cz.zcu.fav.kiv.antipatterndetectionapp.model.Project; |
|
8 |
import cz.zcu.fav.kiv.antipatterndetectionapp.model.Query; |
|
9 |
import cz.zcu.fav.kiv.antipatterndetectionapp.repository.ProjectRepository; |
|
10 |
import org.springframework.beans.factory.annotation.Autowired; |
|
11 |
import org.springframework.stereotype.Controller; |
|
12 |
import org.springframework.ui.Model; |
|
13 |
import org.springframework.web.bind.annotation.*; |
|
14 |
|
|
15 |
import java.util.ArrayList; |
|
16 |
import java.util.List; |
|
17 |
import java.util.Optional; |
|
18 |
|
|
19 |
@Controller |
|
20 |
public class AppController { |
|
21 |
|
|
22 |
@Autowired |
|
23 |
private ProjectRepository projectRepository; |
|
24 |
|
|
25 |
@Autowired |
|
26 |
private AntiPatternManager antiPatternManager; |
|
27 |
|
|
28 |
@GetMapping("/") |
|
29 |
public String index(Model model) { |
|
30 |
model.addAttribute("query", createQuery()); |
|
31 |
return "index"; |
|
32 |
} |
|
33 |
|
|
34 |
@GetMapping("/projects/{id}") |
|
35 |
public String getProjectById(@PathVariable Long id, Model model) { |
|
36 |
Optional<Project> foundProject = projectRepository.findById(id); |
|
37 |
if (foundProject.isPresent()) { |
|
38 |
Project project = foundProject.get(); |
|
39 |
model.addAttribute("project", project); |
|
40 |
} |
|
41 |
return "project"; |
|
42 |
} |
|
43 |
|
|
44 |
@GetMapping("/anti-patterns") |
|
45 |
public @ResponseBody |
|
46 |
List<AntiPattern> getAllAntiPatterns() { |
|
47 |
List<AntiPattern> antiPatterns = new ArrayList<>(); |
|
48 |
for (AntiPatternsEnum antiPatternEnum : AntiPatternsEnum.values()) { |
|
49 |
AntiPattern antiPattern = new AntiPattern(antiPatternEnum.id, antiPatternEnum.printName, antiPatternEnum.name, antiPatternEnum.description); |
|
50 |
antiPatterns.add(antiPattern); |
|
51 |
} |
|
52 |
return antiPatterns; |
|
53 |
} |
|
54 |
|
|
55 |
@GetMapping("/anti-patterns/{id}") |
|
56 |
public String getAntiPatternById(@PathVariable Long id, Model model) { |
|
57 |
for (AntiPatternsEnum antiPatternEnum : AntiPatternsEnum.values()) { |
|
58 |
if (antiPatternEnum.id.equals(id)) { |
|
59 |
model.addAttribute("antiPattern", new AntiPattern(antiPatternEnum.id, antiPatternEnum.printName, antiPatternEnum.name, antiPatternEnum.description)); |
|
60 |
} |
|
61 |
} |
|
62 |
return "anti-pattern"; |
|
63 |
} |
|
64 |
|
|
65 |
@PostMapping("/analyze") |
|
66 |
public String analyze(Model model, |
|
67 |
@RequestParam(value = "selectedProjects", required = false) String[] selectedProjects, |
|
68 |
@RequestParam(value = "selectedAntiPatterns", required = false) String[] selectedAntiPatterns |
|
69 |
) { |
|
70 |
|
|
71 |
if (selectedProjects == null) { |
|
72 |
model.addAttribute("errorMessage", "No project selected." + |
|
73 |
" Select at least one project."); |
|
74 |
model.addAttribute("query", createQuery()); |
|
75 |
return "index"; |
|
76 |
} |
|
77 |
|
|
78 |
if (selectedAntiPatterns == null) { |
|
79 |
model.addAttribute("errorMessage", "No anti-pattern selected." + |
|
80 |
" Select at least one anti-pattern."); |
|
81 |
model.addAttribute("query", createQuery()); |
|
82 |
return "index"; |
|
83 |
} |
|
84 |
|
|
85 |
model.addAttribute("queryResults", antiPatternManager.analyze(createQueryToAnalyze(selectedProjects, selectedAntiPatterns))); |
|
86 |
|
|
87 |
return "result"; |
|
88 |
} |
|
89 |
|
|
90 |
@GetMapping("/about") |
|
91 |
public String about() { |
|
92 |
return "about"; |
|
93 |
} |
|
94 |
|
|
95 |
private Query createQuery() { |
|
96 |
List<AntiPattern> antiPatterns = new ArrayList<>(); |
|
97 |
for (AntiPatternsEnum antiPatternEnum : AntiPatternsEnum.values()) { |
|
98 |
AntiPattern antiPattern = new AntiPattern(antiPatternEnum.id, antiPatternEnum.printName, antiPatternEnum.name, antiPatternEnum.description); |
|
99 |
antiPatterns.add(antiPattern); |
|
100 |
} |
|
101 |
List<Project> projects = new ArrayList<>(); |
|
102 |
for (Project project : projectRepository.findAll()) { |
|
103 |
projects.add(project); |
|
104 |
} |
|
105 |
|
|
106 |
return new Query(projects, antiPatterns); |
|
107 |
} |
|
108 |
|
|
109 |
private Query createQueryToAnalyze(String[] selectedProjects, String[] selectedAntiPatterns) { |
|
110 |
List<Project> projects = new ArrayList<>(); |
|
111 |
for (String selectedProject : selectedProjects) { |
|
112 |
Optional<Project> project = projectRepository.findById(Long.parseLong(selectedProject)); |
|
113 |
project.ifPresent(projects::add); |
|
114 |
} |
|
115 |
|
|
116 |
List<AntiPattern> antiPatterns = new ArrayList<>(); |
|
117 |
for (String selectedAntiPattern : selectedAntiPatterns) { |
|
118 |
for (AntiPatternsEnum antiPatternEnum : AntiPatternsEnum.values()) { |
|
119 |
if (antiPatternEnum.id.equals(Long.parseLong(selectedAntiPattern))) { |
|
120 |
antiPatterns.add(new AntiPattern(antiPatternEnum.id, antiPatternEnum.printName, antiPatternEnum.name, antiPatternEnum.description)); |
|
121 |
} |
|
122 |
} |
|
123 |
} |
|
124 |
|
|
125 |
return new Query(projects, antiPatterns); |
|
126 |
} |
|
127 |
} |
src/main/java/cz/zcu/fav/kiv/antipatterndetectionapp/detecting/AntiPatternManager.java | ||
---|---|---|
1 |
package cz.zcu.fav.kiv.antipatterndetectionapp.detecting; |
|
2 |
|
|
3 |
|
|
4 |
import cz.zcu.fav.kiv.antipatterndetectionapp.model.Query; |
|
5 |
import cz.zcu.fav.kiv.antipatterndetectionapp.model.QueryResult; |
|
6 |
|
|
7 |
import java.util.List; |
|
8 |
|
|
9 |
public interface AntiPatternManager { |
|
10 |
List<QueryResult> analyze(Query query); |
|
11 |
} |
src/main/java/cz/zcu/fav/kiv/antipatterndetectionapp/detecting/AntiPatternManagerImpl.java | ||
---|---|---|
1 |
package cz.zcu.fav.kiv.antipatterndetectionapp.detecting; |
|
2 |
|
|
3 |
|
|
4 |
import cz.zcu.fav.kiv.antipatterndetectionapp.detecting.detectors.*; |
|
5 |
import cz.zcu.fav.kiv.antipatterndetectionapp.model.*; |
|
6 |
import org.springframework.stereotype.Service; |
|
7 |
|
|
8 |
import java.util.ArrayList; |
|
9 |
import java.util.List; |
|
10 |
|
|
11 |
@Service |
|
12 |
public class AntiPatternManagerImpl implements AntiPatternManager { |
|
13 |
|
|
14 |
public List<QueryResult> analyze(Query query) { |
|
15 |
DatabaseConnection databaseConnection = new DatabaseConnection(); |
|
16 |
|
|
17 |
List<QueryResult> queryResults = new ArrayList<>(); |
|
18 |
|
|
19 |
|
|
20 |
for (Project project : query.getProjects()) { |
|
21 |
QueryResult queryResult = new QueryResult(); |
|
22 |
queryResult.setProject(project); |
|
23 |
List<QueryResultItem> queryResultItems = new ArrayList<>(); |
|
24 |
for (AntiPattern antiPattern : query.getAntiPatterns()) { |
|
25 |
QueryResultItem queryResultItem = new QueryResultItem(); |
|
26 |
queryResultItem.setAntiPattern(antiPattern); |
|
27 |
queryResultItem.setDetected(getAntiPatternService(antiPattern).analyze(project, databaseConnection)); |
|
28 |
queryResultItems.add(queryResultItem); |
|
29 |
} |
|
30 |
queryResult.setQueryResultItems(queryResultItems); |
|
31 |
queryResults.add(queryResult); |
|
32 |
|
|
33 |
} |
|
34 |
|
|
35 |
databaseConnection.closeConnection(); |
|
36 |
|
|
37 |
return queryResults; |
|
38 |
} |
|
39 |
|
|
40 |
private AntiPatternDetector getAntiPatternService(AntiPattern antiPattern) { |
|
41 |
switch (antiPattern.getName()) { |
|
42 |
case "TooLongSprint": |
|
43 |
return new TooLongSprintDetectorDetector(); |
|
44 |
case "BusinessAsUsual": |
|
45 |
return new BusinessAsUsualDetector(); |
|
46 |
case "VaryingSprintLength": |
|
47 |
return new VaryingSprintLengthDetector(); |
|
48 |
case "IndifferentSpecialist": |
|
49 |
return new IndifferentSpecialistDetector(); |
|
50 |
case "LongOrNonExistentFeedbackLoops": |
|
51 |
return new LongOrNonExistentFeedbackLoopsDetector(); |
|
52 |
case "RoadToNowhere": |
|
53 |
return new RoadToNowhereDetector(); |
|
54 |
case "SpecifyNothing": |
|
55 |
return new SpecifyNothingDetector(); |
|
56 |
} |
|
57 |
return null; |
|
58 |
} |
|
59 |
} |
src/main/java/cz/zcu/fav/kiv/antipatterndetectionapp/detecting/AntiPatternsEnum.java | ||
---|---|---|
1 |
package cz.zcu.fav.kiv.antipatterndetectionapp.detecting; |
|
2 |
|
|
3 |
public enum AntiPatternsEnum { |
|
4 |
|
|
5 |
TooLongSprint(1L, |
|
6 |
"TooLongSprint", |
|
7 |
"Too Long Sprint - DONE", |
|
8 |
"Iterations too long. (ideal iteration length is about 1-2 weeks, " + |
|
9 |
"maximum 3 weeks). It could also be detected here if the length " + |
|
10 |
"of the iteration does not change often (It can change at the " + |
|
11 |
"beginning and at the end of the project, but it should not " + |
|
12 |
"change in the already started project)."), |
|
13 |
|
|
14 |
VaryingSprintLength(2L, |
|
15 |
"VaryingSprintLength", |
|
16 |
"Varying Sprint Length - DONE", |
|
17 |
"The length of the sprint changes very often. " + |
|
18 |
"It is clear that iterations will be different " + |
|
19 |
"lengths at the beginning and end of the project, " + |
|
20 |
"but the length of the sprint should not change " + |
|
21 |
"during the project."), |
|
22 |
|
|
23 |
RoadToNowhere(3L, |
|
24 |
"RoadToNowhere", |
|
25 |
"Road To Nowhere - DONE", |
|
26 |
"The project is not sufficiently planned and therefore " + |
|
27 |
"takes place on an ad hoc basis with an uncertain " + |
|
28 |
"outcome and deadline. There is no project plan in the project."), |
|
29 |
|
|
30 |
SpecifyNothing(4L, |
|
31 |
"SpecifyNothing", |
|
32 |
"Specify Nothing - DONE", |
|
33 |
"The specification is not done intentionally. Programmers are " + |
|
34 |
"expected to work better without written specifications."), |
|
35 |
|
|
36 |
BusinessAsUsual(5L, |
|
37 |
"BusinessAsUsual", |
|
38 |
"Business As Usual - DONE", |
|
39 |
"Absence of a retrospective after individual " + |
|
40 |
"iterations or after the completion project."), |
|
41 |
|
|
42 |
IndifferentSpecialist(6L, |
|
43 |
"IndifferentSpecialist", |
|
44 |
"Indifferent specialist", |
|
45 |
"A team member who is a specialist in just one thing and does not want to learn new things. " + |
|
46 |
"He refuses to learn new things outside of his specialization. It often disparages other " + |
|
47 |
"\"technologies\". They reduce the seriousness of things that do not specialize in his specialization."), |
|
48 |
|
|
49 |
LongOrNonExistentFeedbackLoops(7L, |
|
50 |
"LongOrNonExistentFeedbackLoops", |
|
51 |
"Long Or Non-Existent Feedback Loops", |
|
52 |
"Long spacings between customer feedback or no feedback. The customer " + |
|
53 |
"enters the project and sees the final result. In the end, the customer " + |
|
54 |
"may not get what he really wanted. With long intervals of feedback, " + |
|
55 |
"some misunderstood functionality can be created and we have to spend " + |
|
56 |
"a lot of effort and time to redo it. "); |
|
57 |
|
|
58 |
|
|
59 |
|
|
60 |
|
|
61 |
public final Long id; |
|
62 |
public final String name; |
|
63 |
public final String printName; |
|
64 |
public final String description; |
|
65 |
|
|
66 |
AntiPatternsEnum(Long id, String name, String printName, String description) { |
|
67 |
this.id = id; |
|
68 |
this.name = name; |
|
69 |
this.printName = printName; |
|
70 |
this.description = description; |
|
71 |
} |
|
72 |
} |
src/main/java/cz/zcu/fav/kiv/antipatterndetectionapp/detecting/DatabaseConnection.java | ||
---|---|---|
1 |
package cz.zcu.fav.kiv.antipatterndetectionapp.detecting; |
|
2 |
|
|
3 |
import java.sql.Connection; |
|
4 |
import java.sql.DriverManager; |
|
5 |
import java.sql.SQLException; |
|
6 |
|
|
7 |
public class DatabaseConnection { |
|
8 |
|
|
9 |
private static final String connectionUrl = "jdbc:mysql://localhost:3306/ppicha"; |
|
10 |
private static final String user = "root"; |
|
11 |
private static final String password = ""; |
|
12 |
|
|
13 |
private Connection databaseConnection; |
|
14 |
|
|
15 |
public DatabaseConnection() { |
|
16 |
this.databaseConnection = createConnection(); |
|
17 |
} |
|
18 |
|
|
19 |
private Connection createConnection() { |
|
20 |
Connection conn = null; |
|
21 |
try { |
|
22 |
conn = DriverManager.getConnection(connectionUrl, user, password); |
|
23 |
|
|
24 |
} catch (SQLException e) { |
|
25 |
e.printStackTrace(); |
|
26 |
} |
|
27 |
return conn; |
|
28 |
} |
|
29 |
|
|
30 |
public void closeConnection() { |
|
31 |
try { |
|
32 |
this.databaseConnection.close(); |
|
33 |
} catch (SQLException e) { |
|
34 |
e.printStackTrace(); |
|
35 |
} |
|
36 |
} |
|
37 |
|
|
38 |
public Connection getDatabaseConnection() { |
|
39 |
return databaseConnection; |
|
40 |
} |
|
41 |
} |
src/main/java/cz/zcu/fav/kiv/antipatterndetectionapp/detecting/detectors/AntiPatternDetector.java | ||
---|---|---|
1 |
package cz.zcu.fav.kiv.antipatterndetectionapp.detecting.detectors; |
|
2 |
|
|
3 |
|
|
4 |
import cz.zcu.fav.kiv.antipatterndetectionapp.Utils; |
|
5 |
import cz.zcu.fav.kiv.antipatterndetectionapp.detecting.DatabaseConnection; |
|
6 |
import cz.zcu.fav.kiv.antipatterndetectionapp.model.Project; |
|
7 |
|
|
8 |
import java.sql.ResultSet; |
|
9 |
import java.sql.SQLException; |
|
10 |
import java.sql.Statement; |
|
11 |
import java.util.Arrays; |
|
12 |
import java.util.List; |
|
13 |
|
|
14 |
|
|
15 |
public abstract class AntiPatternDetector { |
|
16 |
public abstract boolean analyze(Project analyzedProject, DatabaseConnection databaseConnection); |
|
17 |
|
|
18 |
ResultSet executeQuery(Project project, String queryFileName, DatabaseConnection databaseConnection){ |
|
19 |
Statement stmt; |
|
20 |
ResultSet resultSet = null; |
|
21 |
try { |
|
22 |
stmt = databaseConnection.getDatabaseConnection().createStatement(); |
|
23 |
|
|
24 |
List<String> queries = Utils.loadQueryFromFile(queryFileName); |
|
25 |
ResultSet rs = null; |
|
26 |
for (String query : queries) { |
|
27 |
if(queries.indexOf(query) != queries.size()-1){ |
|
28 |
if(query.contains("?")) |
|
29 |
query = Utils.bindValues(query, Arrays.asList(project.getId().toString())); |
|
30 |
stmt.executeQuery(query); |
|
31 |
} else { |
|
32 |
resultSet = stmt.executeQuery(query); |
|
33 |
} |
|
34 |
|
|
35 |
} |
|
36 |
} catch (SQLException e) { |
|
37 |
e.printStackTrace(); |
|
38 |
} |
|
39 |
return resultSet; |
|
40 |
} |
|
41 |
} |
src/main/java/cz/zcu/fav/kiv/antipatterndetectionapp/detecting/detectors/BusinessAsUsualDetector.java | ||
---|---|---|
1 |
package cz.zcu.fav.kiv.antipatterndetectionapp.detecting.detectors; |
|
2 |
|
|
3 |
|
|
4 |
import cz.zcu.fav.kiv.antipatterndetectionapp.detecting.DatabaseConnection; |
|
5 |
import cz.zcu.fav.kiv.antipatterndetectionapp.model.Project; |
|
6 |
|
|
7 |
import java.sql.ResultSet; |
|
8 |
import java.sql.SQLException; |
|
9 |
|
|
10 |
public class BusinessAsUsualDetector extends AntiPatternDetector { |
|
11 |
@Override |
|
12 |
public boolean analyze(Project analyzedProject, DatabaseConnection databaseConnection) { |
|
13 |
|
|
14 |
int numberOfIterations = 0; |
|
15 |
int numberOfWikipageWithRetr = 0; |
|
16 |
int numberOfRestrospectiveActivities = 0; |
|
17 |
|
|
18 |
try { |
|
19 |
ResultSet rs = super.executeQuery(analyzedProject, "./queries/business_as_usual.sql", databaseConnection); |
|
20 |
if (rs != null) { |
|
21 |
while (rs.next()) { |
|
22 |
numberOfIterations = rs.getInt("numberOfIterations"); |
|
23 |
numberOfWikipageWithRetr = rs.getInt("numberOfWikipageWithRetr"); |
|
24 |
numberOfRestrospectiveActivities = rs.getInt("numberOfRestrospectiveActivities"); |
|
25 |
System.out.println("Project Id: " + analyzedProject.getId()); |
|
26 |
System.out.println("numberOfWikipageWithRetr:" + numberOfWikipageWithRetr); |
|
27 |
System.out.println("numberOfRestrospectiveActivities:" + numberOfRestrospectiveActivities); |
|
28 |
|
|
29 |
} |
|
30 |
} |
|
31 |
|
|
32 |
} catch (SQLException e) { |
|
33 |
e.printStackTrace(); |
|
34 |
} |
|
35 |
if((numberOfWikipageWithRetr > 0) && (numberOfRestrospectiveActivities > 0)){ |
|
36 |
return false; |
|
37 |
} else { |
|
38 |
return true; |
|
39 |
} |
|
40 |
} |
|
41 |
} |
src/main/java/cz/zcu/fav/kiv/antipatterndetectionapp/detecting/detectors/IndifferentSpecialistDetector.java | ||
---|---|---|
1 |
package cz.zcu.fav.kiv.antipatterndetectionapp.detecting.detectors; |
|
2 |
|
|
3 |
|
|
4 |
import cz.zcu.fav.kiv.antipatterndetectionapp.detecting.DatabaseConnection; |
|
5 |
import cz.zcu.fav.kiv.antipatterndetectionapp.model.Project; |
|
6 |
|
|
7 |
public class IndifferentSpecialistDetector extends AntiPatternDetector { |
|
8 |
@Override |
|
9 |
public boolean analyze(Project analyzedProject, DatabaseConnection databaseConnection) { |
|
10 |
return false; |
|
11 |
} |
|
12 |
} |
src/main/java/cz/zcu/fav/kiv/antipatterndetectionapp/detecting/detectors/LongOrNonExistentFeedbackLoopsDetector.java | ||
---|---|---|
1 |
package cz.zcu.fav.kiv.antipatterndetectionapp.detecting.detectors; |
|
2 |
|
|
3 |
|
|
4 |
import cz.zcu.fav.kiv.antipatterndetectionapp.detecting.DatabaseConnection; |
|
5 |
import cz.zcu.fav.kiv.antipatterndetectionapp.model.Project; |
|
6 |
|
|
7 |
public class LongOrNonExistentFeedbackLoopsDetector extends AntiPatternDetector { |
|
8 |
|
|
9 |
@Override |
|
10 |
public boolean analyze(Project analyzedProject, DatabaseConnection databaseConnection) { |
|
11 |
/* |
|
12 |
|
|
13 |
int counter = 0; |
|
14 |
long daysTolerance = 3; |
|
15 |
boolean isFirstIteration = true; |
|
16 |
int numberOfIterations = 0; |
|
17 |
double averageLengthOfIteration = 0; |
|
18 |
Date firstIterationDueDate = null; |
|
19 |
Date secondIterationDueDate = null; |
|
20 |
|
|
21 |
try { |
|
22 |
ResultSet rs = super.executeQuery(analyzedProject, "./queries/long_or_non_existent_feedback_loops.sql", databaseConnection); |
|
23 |
if (rs != null) { |
|
24 |
|
|
25 |
|
|
26 |
while (rs.next()) { |
|
27 |
if (isFirstIteration) { |
|
28 |
numberOfIterations = rs.getInt("numberOfIterations"); |
|
29 |
averageLengthOfIteration = rs.getDouble("averageIterationLength"); |
|
30 |
firstIterationDueDate = rs.getDate("dueDate"); |
|
31 |
isFirstIteration = false; |
|
32 |
continue; |
|
33 |
} |
|
34 |
|
|
35 |
secondIterationDueDate = rs.getDate("dueDate"); |
|
36 |
long numberOfDatesBetween = Utils.daysBetween(firstIterationDueDate, secondIterationDueDate); |
|
37 |
firstIterationDueDate = secondIterationDueDate; |
|
38 |
if (Math.abs(numberOfDatesBetween - averageLengthOfIteration) <= daysTolerance) { |
|
39 |
counter++; |
|
40 |
} |
|
41 |
} |
|
42 |
} |
|
43 |
|
|
44 |
} catch (SQLException e) { |
|
45 |
e.printStackTrace(); |
|
46 |
} |
|
47 |
|
|
48 |
return counter != numberOfIterations;*/ |
|
49 |
return false; |
|
50 |
} |
|
51 |
} |
src/main/java/cz/zcu/fav/kiv/antipatterndetectionapp/detecting/detectors/RoadToNowhereDetector.java | ||
---|---|---|
1 |
package cz.zcu.fav.kiv.antipatterndetectionapp.detecting.detectors; |
|
2 |
|
|
3 |
|
|
4 |
|
|
5 |
import cz.zcu.fav.kiv.antipatterndetectionapp.detecting.DatabaseConnection; |
|
6 |
import cz.zcu.fav.kiv.antipatterndetectionapp.model.Project; |
|
7 |
|
|
8 |
import java.sql.ResultSet; |
|
9 |
import java.sql.SQLException; |
|
10 |
|
|
11 |
public class RoadToNowhereDetector extends AntiPatternDetector { |
|
12 |
@Override |
|
13 |
public boolean analyze(Project analyzedProject, DatabaseConnection databaseConnection) { |
|
14 |
|
|
15 |
int numberOfIssuesForProjectPlan = 0; |
|
16 |
int numberOfWikiPagesForProjectPlan = 0; |
|
17 |
|
|
18 |
try { |
|
19 |
ResultSet rs = super.executeQuery(analyzedProject, "./queries/road_to_nowhere.sql", databaseConnection); |
|
20 |
if (rs != null) { |
|
21 |
while (rs.next()) { |
|
22 |
numberOfIssuesForProjectPlan = rs.getInt("numberOfIssuesForProjectPlan"); |
|
23 |
numberOfWikiPagesForProjectPlan = rs.getInt("numberOfWikiPagesForProjectPlan"); |
|
24 |
} |
|
25 |
} |
|
26 |
|
|
27 |
} catch (SQLException e) { |
|
28 |
e.printStackTrace(); |
|
29 |
} |
|
30 |
return numberOfIssuesForProjectPlan <= 0 && numberOfWikiPagesForProjectPlan <= 0; |
|
31 |
} |
|
32 |
} |
src/main/java/cz/zcu/fav/kiv/antipatterndetectionapp/detecting/detectors/SpecifyNothingDetector.java | ||
---|---|---|
1 |
package cz.zcu.fav.kiv.antipatterndetectionapp.detecting.detectors; |
|
2 |
|
|
3 |
|
|
4 |
|
|
5 |
import cz.zcu.fav.kiv.antipatterndetectionapp.detecting.DatabaseConnection; |
|
6 |
import cz.zcu.fav.kiv.antipatterndetectionapp.model.Project; |
|
7 |
|
|
8 |
import java.sql.ResultSet; |
|
9 |
import java.sql.SQLException; |
|
10 |
|
|
11 |
public class SpecifyNothingDetector extends AntiPatternDetector { |
|
12 |
|
|
13 |
@Override |
|
14 |
public boolean analyze(Project analyzedProject, DatabaseConnection databaseConnection) { |
|
15 |
|
|
16 |
/* Settings */ |
|
17 |
int minimumNumberOfWikiPages = 1; |
|
18 |
int minimumNumberOfActivities = 1; |
|
19 |
double minimumAverageLengthOfIssueDescription = 200; |
|
20 |
|
|
21 |
/* Init values */ |
|
22 |
int numberOfWikiPages = 0; |
|
23 |
int numberOfActivitiesForSpecification = 0; |
|
24 |
double averageLengthOfIssueDescription = 0; |
|
25 |
|
|
26 |
try { |
|
27 |
ResultSet rs = super.executeQuery(analyzedProject, "./queries/specify_nothing.sql", databaseConnection); |
|
28 |
if (rs != null) { |
|
29 |
while (rs.next()) { |
|
30 |
numberOfWikiPages = rs.getInt("numberOfWikiPages"); |
|
31 |
numberOfActivitiesForSpecification = rs.getInt("numberOfActivitiesForSpecification"); |
|
32 |
averageLengthOfIssueDescription = rs.getDouble("averageLengthOfIssueDescription"); |
|
33 |
} |
|
34 |
} |
|
35 |
|
|
36 |
} catch (SQLException e) { |
|
37 |
e.printStackTrace(); |
|
38 |
} |
|
39 |
|
|
40 |
return numberOfWikiPages < minimumNumberOfWikiPages |
|
41 |
&& numberOfActivitiesForSpecification < minimumNumberOfActivities |
|
42 |
&& !(averageLengthOfIssueDescription >= minimumAverageLengthOfIssueDescription); |
|
43 |
} |
|
44 |
} |
src/main/java/cz/zcu/fav/kiv/antipatterndetectionapp/detecting/detectors/TooLongSprintDetectorDetector.java | ||
---|---|---|
1 |
package cz.zcu.fav.kiv.antipatterndetectionapp.detecting.detectors; |
|
2 |
|
|
3 |
|
|
4 |
import cz.zcu.fav.kiv.antipatterndetectionapp.detecting.DatabaseConnection; |
|
5 |
import cz.zcu.fav.kiv.antipatterndetectionapp.model.Project; |
|
6 |
|
|
7 |
import java.sql.ResultSet; |
|
8 |
import java.sql.SQLException; |
|
9 |
|
|
10 |
public class TooLongSprintDetectorDetector extends AntiPatternDetector { |
|
11 |
|
|
12 |
|
|
13 |
@Override |
|
14 |
public boolean analyze(Project analyzedProject, DatabaseConnection databaseConnection) { |
|
15 |
|
|
16 |
int counter = 0; |
|
17 |
int maximumNumberOfTooLongIterations = 2; |
|
18 |
|
|
19 |
try { |
|
20 |
ResultSet rs = super.executeQuery(analyzedProject, "./queries/too_long_sprint.sql", databaseConnection); |
|
21 |
if (rs != null) { |
|
22 |
while (rs.next()) { |
|
23 |
boolean isTooLongSprint = rs.getBoolean("isTooLongSprint"); |
|
24 |
if(isTooLongSprint) { |
|
25 |
counter++; |
|
26 |
} |
|
27 |
} |
|
28 |
} |
|
29 |
|
|
30 |
} catch (SQLException e) { |
|
31 |
e.printStackTrace(); |
|
32 |
} |
|
33 |
return counter >= maximumNumberOfTooLongIterations; |
|
34 |
} |
|
35 |
} |
src/main/java/cz/zcu/fav/kiv/antipatterndetectionapp/detecting/detectors/VaryingSprintLengthDetector.java | ||
---|---|---|
1 |
package cz.zcu.fav.kiv.antipatterndetectionapp.detecting.detectors; |
|
2 |
|
|
3 |
|
|
4 |
|
|
5 |
import cz.zcu.fav.kiv.antipatterndetectionapp.detecting.DatabaseConnection; |
|
6 |
import cz.zcu.fav.kiv.antipatterndetectionapp.model.Project; |
|
7 |
|
|
8 |
import java.sql.ResultSet; |
|
9 |
import java.sql.SQLException; |
|
10 |
|
|
11 |
public class VaryingSprintLengthDetector extends AntiPatternDetector { |
|
12 |
@Override |
|
13 |
public boolean analyze(Project analyzedProject, DatabaseConnection databaseConnection) { |
|
14 |
|
|
15 |
// Settings |
|
16 |
// Maximum difference between two iterations |
|
17 |
int maximumDaysDifference = 5; |
|
18 |
// How many times the length of the iteration can change |
|
19 |
int maximumIterationChange = 2; |
|
20 |
|
|
21 |
int counter = 0; |
|
22 |
|
|
23 |
try { |
|
24 |
ResultSet rs = super.executeQuery(analyzedProject, "./queries/varying_sprint_length.sql", databaseConnection); |
|
25 |
if (rs != null) { |
|
26 |
int firstIterationLength = Integer.MIN_VALUE; |
|
27 |
int secondIterationLength; |
|
28 |
while (rs.next()) { |
|
29 |
int iterationLength = rs.getInt("iterationLength"); |
|
30 |
if (firstIterationLength == Integer.MIN_VALUE) { |
|
31 |
firstIterationLength = iterationLength; |
|
32 |
continue; |
|
33 |
} else { |
|
34 |
secondIterationLength = iterationLength; |
|
35 |
} |
|
36 |
|
|
37 |
if (Math.abs(firstIterationLength - secondIterationLength) >= maximumDaysDifference) { |
|
38 |
counter = counter + 1; |
|
39 |
} |
|
40 |
firstIterationLength = secondIterationLength; |
|
41 |
} |
|
42 |
} |
|
43 |
|
|
44 |
} catch (SQLException e) { |
|
45 |
e.printStackTrace(); |
|
46 |
} |
|
47 |
return counter >= maximumIterationChange; |
|
48 |
} |
|
49 |
} |
src/main/java/cz/zcu/fav/kiv/antipatterndetectionapp/model/AntiPattern.java | ||
---|---|---|
1 |
package cz.zcu.fav.kiv.antipatterndetectionapp.model; |
|
2 |
|
|
3 |
public class AntiPattern { |
|
4 |
|
|
5 |
private Long id; |
|
6 |
private String printName; |
|
7 |
private String name; |
|
8 |
private String description; |
|
9 |
|
|
10 |
public AntiPattern(Long id, String printName, String name, String description) { |
|
11 |
this.id = id; |
|
12 |
this.printName = printName; |
|
13 |
this.name = name; |
|
14 |
this.description = description; |
|
15 |
} |
|
16 |
|
|
17 |
public Long getId() { |
|
18 |
return id; |
|
19 |
} |
|
20 |
|
|
21 |
public void setId(Long id) { |
|
22 |
this.id = id; |
|
23 |
} |
|
24 |
|
|
25 |
public String getPrintName() { |
|
26 |
return printName; |
|
27 |
} |
|
28 |
|
|
29 |
public void setPrintName(String printName) { |
|
30 |
this.printName = printName; |
|
31 |
} |
|
32 |
|
|
33 |
public String getName() { |
|
34 |
return name; |
|
35 |
} |
|
36 |
|
|
37 |
public void setName(String name) { |
|
38 |
this.name = name; |
|
39 |
} |
|
40 |
|
|
41 |
public String getDescription() { |
|
42 |
return description; |
|
43 |
} |
|
44 |
|
|
45 |
public void setDescription(String description) { |
|
46 |
this.description = description; |
|
47 |
} |
|
48 |
|
|
49 |
@Override |
|
50 |
public String toString() { |
|
51 |
return "AntiPattern{" + |
|
52 |
"id=" + id + |
|
53 |
", printName='" + printName + '\'' + |
|
54 |
", name='" + name + '\'' + |
|
55 |
", description='" + description + '\'' + |
|
56 |
'}'; |
|
57 |
} |
|
58 |
} |
src/main/java/cz/zcu/fav/kiv/antipatterndetectionapp/model/Project.java | ||
---|---|---|
1 |
package cz.zcu.fav.kiv.antipatterndetectionapp.model; |
|
2 |
|
|
3 |
import javax.persistence.Entity; |
|
4 |
import javax.persistence.GeneratedValue; |
|
5 |
import javax.persistence.GenerationType; |
|
6 |
import javax.persistence.Id; |
|
7 |
|
|
8 |
@Entity |
|
9 |
public class Project { |
|
10 |
|
|
11 |
@Id |
|
12 |
@GeneratedValue(strategy = GenerationType.AUTO) |
|
13 |
private Long id; |
|
14 |
private String name; |
|
15 |
private String description; |
|
16 |
|
|
17 |
public Project() { |
|
18 |
} |
|
19 |
|
|
20 |
public Project(String name, String description) { |
|
21 |
this.name = name; |
|
22 |
this.description = description; |
|
23 |
} |
|
24 |
|
|
25 |
public Long getId() { |
|
26 |
return id; |
|
27 |
} |
|
28 |
|
|
29 |
public void setId(Long id) { |
|
30 |
this.id = id; |
|
31 |
} |
|
32 |
|
|
33 |
public String getName() { |
|
34 |
return name; |
|
35 |
} |
|
36 |
|
|
37 |
public void setName(String name) { |
|
38 |
this.name = name; |
|
39 |
} |
|
40 |
|
|
41 |
public String getDescription() { |
|
42 |
return description; |
|
43 |
} |
|
44 |
|
|
45 |
public void setDescription(String description) { |
|
46 |
this.description = description; |
|
47 |
} |
|
48 |
|
|
49 |
@Override |
|
50 |
public String toString() { |
|
51 |
return "Project{" + |
|
52 |
"id=" + id + |
|
53 |
", name='" + name + '\'' + |
|
54 |
", description='" + description + '\'' + |
|
55 |
'}'; |
|
56 |
} |
|
57 |
} |
src/main/java/cz/zcu/fav/kiv/antipatterndetectionapp/model/Query.java | ||
---|---|---|
1 |
package cz.zcu.fav.kiv.antipatterndetectionapp.model; |
|
2 |
|
|
3 |
import java.util.List; |
|
4 |
|
|
5 |
public class Query { |
|
6 |
private List<Project> projects; |
|
7 |
private List<AntiPattern> antiPatterns; |
|
8 |
|
|
9 |
public Query(List<Project> projects, List<AntiPattern> antiPatterns) { |
|
10 |
this.projects = projects; |
|
11 |
this.antiPatterns = antiPatterns; |
|
12 |
} |
|
13 |
|
|
14 |
public List<Project> getProjects() { |
|
15 |
return projects; |
|
16 |
} |
|
17 |
|
|
18 |
public void setProjects(List<Project> projects) { |
|
19 |
this.projects = projects; |
|
20 |
} |
|
21 |
|
|
22 |
public List<AntiPattern> getAntiPatterns() { |
|
23 |
return antiPatterns; |
|
24 |
} |
|
25 |
|
|
26 |
public void setAntiPatterns(List<AntiPattern> antiPatterns) { |
|
27 |
this.antiPatterns = antiPatterns; |
|
28 |
} |
|
29 |
} |
src/main/java/cz/zcu/fav/kiv/antipatterndetectionapp/model/QueryResult.java | ||
---|---|---|
1 |
package cz.zcu.fav.kiv.antipatterndetectionapp.model; |
|
2 |
|
|
3 |
import java.util.List; |
|
4 |
|
|
5 |
public class QueryResult { |
|
6 |
private Project project; |
|
7 |
private List<QueryResultItem> queryResultItems; |
|
8 |
|
|
9 |
public Project getProject() { |
|
10 |
return project; |
|
11 |
} |
|
12 |
|
|
13 |
public void setProject(Project project) { |
|
14 |
this.project = project; |
|
15 |
} |
|
16 |
|
|
17 |
public List<QueryResultItem> getQueryResultItems() { |
|
18 |
return queryResultItems; |
|
19 |
} |
|
20 |
|
|
21 |
public void setQueryResultItems(List<QueryResultItem> queryResultItems) { |
|
22 |
this.queryResultItems = queryResultItems; |
|
23 |
} |
|
24 |
|
|
25 |
public QueryResult() { |
|
26 |
} |
|
27 |
|
|
28 |
public QueryResult(Project project, List<QueryResultItem> queryResultItems) { |
|
29 |
this.project = project; |
|
30 |
this.queryResultItems = queryResultItems; |
|
31 |
} |
|
32 |
} |
src/main/java/cz/zcu/fav/kiv/antipatterndetectionapp/model/QueryResultItem.java | ||
---|---|---|
1 |
package cz.zcu.fav.kiv.antipatterndetectionapp.model; |
|
2 |
|
|
3 |
public class QueryResultItem { |
|
4 |
private AntiPattern antiPattern; |
|
5 |
private boolean isDetected; |
|
6 |
|
|
7 |
public QueryResultItem() { |
|
8 |
} |
|
9 |
|
|
10 |
public QueryResultItem(AntiPattern antiPattern, boolean isDetected) { |
|
11 |
this.antiPattern = antiPattern; |
|
12 |
this.isDetected = isDetected; |
|
13 |
} |
|
14 |
|
|
15 |
public AntiPattern getAntiPattern() { |
|
16 |
return antiPattern; |
|
17 |
} |
|
18 |
|
|
19 |
public void setAntiPattern(AntiPattern antiPattern) { |
|
20 |
this.antiPattern = antiPattern; |
|
21 |
} |
|
22 |
|
|
23 |
public boolean isDetected() { |
|
24 |
return isDetected; |
|
25 |
} |
|
26 |
|
|
27 |
public void setDetected(boolean detected) { |
|
28 |
isDetected = detected; |
|
29 |
} |
|
30 |
} |
src/main/java/cz/zcu/fav/kiv/antipatterndetectionapp/repository/ProjectRepository.java | ||
---|---|---|
1 |
package cz.zcu.fav.kiv.antipatterndetectionapp.repository; |
|
2 |
|
|
3 |
import cz.zcu.fav.kiv.antipatterndetectionapp.model.Project; |
|
4 |
import org.springframework.data.repository.CrudRepository; |
|
5 |
|
|
6 |
public interface ProjectRepository extends CrudRepository<Project, Long> { |
|
7 |
} |
src/main/resources/application.properties | ||
---|---|---|
1 |
|
|
1 |
spring.datasource.url=jdbc:mysql://localhost:3306/ppicha |
|
2 |
spring.datasource.username=root |
|
3 |
spring.datasource.password= |
src/main/resources/queries/business_as_usual.sql | ||
---|---|---|
1 |
/* |
|
2 |
Anti-pattern name: Business as usual (No sprint retrospective) |
|
3 |
|
|
4 |
Description: Absence of a retrospective after individual |
|
5 |
iterations or after the completion project. |
|
6 |
|
|
7 |
Detection: There will be no activities in the project |
|
8 |
that would indicate that a retrospective is |
|
9 |
taking place (issue with the name of the |
|
10 |
retrospective, issue on which all team members |
|
11 |
log, issue that is repeated periodically, |
|
12 |
issue to which no commit is bound, issue which |
|
13 |
will be marked as administration or something like that). |
|
14 |
There will be no notes in the wiki or other tool called |
|
15 |
retrospectives (%retr%). |
|
16 |
*/ |
|
17 |
|
|
18 |
/* Init global variables */ |
|
19 |
set @projectId = ?; |
|
20 |
/* Retrospective substring */ |
|
21 |
set @restrospectiveSubstring = '%retr%'; |
|
22 |
/* Number of developers in project */ |
|
23 |
set @numberOfPeople = (select count(DISTINCT assigneeId) from workunitview where projectId = @projectId and assigneeName != 'unknown'); |
|
24 |
/* Number of iterations for given project */ |
|
25 |
set @numberOfIterations = (select COUNT(*) from iteration where superProjectId = @projectId); |
|
26 |
/* Number of wikipages with substring retr */ |
|
27 |
set @numberOfWikipageWithRetr = (select count(*) from artifactview where projectId = @projectId AND artifactClass like 'WIKIPAGE' AND (name like @restrospectiveSubstring OR description like @restrospectiveSubstring)); |
|
28 |
/* Number of issues with retr root ends same day like iteration and all members of team are logging time on this issue */ |
|
29 |
set @numberOfRestrospectiveActivities = (select COUNT(distinct activityEndDate) from (select workunitview.id, workunitview.activityEndDate from workunitview INNER JOIN fieldchangeview on workunitview.id = fieldchangeview.itemId where workunitview.projectId = @projectId AND fieldchangeview.changeName LIKE 'LOGTIME' AND (abs(datediff(workunitview.activityEndDate, workunitview.iterationEndDate) = 0)) AND (workunitview.name like @restrospectiveSubstring OR workunitview.description LIKE @restrospectiveSubstring) GROUP by workunitview.id HAVING COUNT(DISTINCT fieldchangeview.authorId) = @numberOfPeople) as test); |
|
30 |
/* Show all statistics */ |
|
31 |
select @projectId as `projectId`, @numberOfPeople as `numberOfPeople`, @numberOfIterations as `numberOfIterations`, @numberOfWikipageWithRetr as `numberOfWikipageWithRetr`, @numberOfRestrospectiveActivities as `numberOfRestrospectiveActivities`; |
src/main/resources/queries/long_or_non_existent_feedback_loops.sql | ||
---|---|---|
1 |
/* |
|
2 |
Anti-pattern name: Long Or Non-Existant Feedback Loops (No Customer feedback) |
|
3 |
|
|
4 |
Description: Long spacings between customer feedback or no feedback. The customer |
|
5 |
enters the project and sees the final result. In the end, the customer |
|
6 |
may not get what he really wanted. With long intervals of feedback, |
|
7 |
some misunderstood functionality can be created and we have to spend |
|
8 |
a lot of effort and time to redo it. |
|
9 |
|
|
10 |
|
|
11 |
Detection: How to choose what is the optimal spacing between feedbacks? In ASWI, |
|
12 |
it was mostly after each iteration, ie 2-3 weeks apart. Check if there |
|
13 |
is an activity that is repeated periodically, all team members or |
|
14 |
leaders log time on it (essentially a similar issue as in the anti-Business |
|
15 |
as usual model). Search for an activity named "DEMO", "CUSTOMER", etc. |
|
16 |
Search for some records from the demo in the wiki. Similar to Business as usual. |
|
17 |
|
|
18 |
POZNÁMKY: |
|
19 |
1) naléz aktivity, které mohou odpovídat schůzce se zákazníkem |
|
20 |
2) nalezené aktivity zgrupovat podle dueDate |
|
21 |
3) následně udělat časový rozdíl těchto aktivit (Python) |
|
22 |
4) a porovnat s průměrnou délkou iterace |
|
23 |
|
|
24 |
|
|
25 |
|
|
26 |
|
|
27 |
*/ |
|
28 |
|
|
29 |
/* Init project id */ |
|
30 |
set @projectId = ?; |
|
31 |
/* Number of iterations for project */ |
|
32 |
set @numberOfIterations = (SELECT count(id) FROM `iteration` WHERE superProjectId = @projectId); |
|
33 |
/* Average iteration length */ |
|
34 |
set @averageIterationLength = (SELECT AVG(abs(datediff(iteration.endDate, iteration.startDate))) FROM `iteration` WHERE superProjectId = @projectId); |
|
35 |
/* Look for issues that should indicate customer demo or customer feedback */ |
|
36 |
select workunitview.duedate as `dueDate`, @numberOfIterations as `numberOfIterations`, @averageIterationLength as `averageIterationLength` FROM `workunitview` where workunitview.projectId = @projectId and (workunitview.name like '%demo%' or workunitview.description like '%demo%' or workunitview.name like '%zákazník%' or workunitview.description like '%zákazník%' or workunitview.name like '%cust%' or workunitview.description like '%cust%' or workunitview.name like '%zadavatel%' or workunitview.description like '%zadavatel%' or workunitview.name like '%předvedení%' or workunitview.description like '%předvedení%' or workunitview.name like '%presentation%' or workunitview.description like '%presentation%') group by workunitview.duedate; |
|
37 |
|
|
38 |
|
|
39 |
|
src/main/resources/queries/road_to_nowhere.sql | ||
---|---|---|
1 |
/* |
|
2 |
Anti-pattern name: Road To Nowhere |
|
3 |
|
|
4 |
Description: The project is not sufficiently planned and therefore |
|
5 |
takes place on an ad hoc basis with an uncertain |
|
6 |
outcome and deadline. There is no project plan in the project. |
|
7 |
|
|
8 |
Detection: There is no activity in ALM that would indicate the creation |
|
9 |
of a project plan. There will be no document in the wiki |
|
10 |
called the "Project Plan". Project plan should be created in first or |
|
11 |
second iteration. Also could be detected with field change view. If is |
|
12 |
a lot of changes on issues in the beginning of the iteration so then could |
|
13 |
indicate some planning. |
|
14 |
*/ |
|
15 |
set @projectId = ?; |
|
16 |
set @firstIterationStartDate = (select startDate from iteration where superProjectId = @projectId ORDER BY startDate LIMIT 1 offset 0); |
|
17 |
set @secondIterationStartDate = (select startDate from iteration where superProjectId = @projectId ORDER BY startDate LIMIT 1 offset 1); |
|
18 |
set @numberOfIssuesForProjectPlan = (SELECT count(*) from workunitview where projectId = @projectId and (workunitview.name like '%plán%projektu%' or workunitview.description like '%plán%projektu%' or workunitview.name like '%project%plan%' or workunitview.description like '%project%plan%' or workunitview.name like '%plan%project%' or workunitview.description like '%plan%project%' or workunitview.name like '%proje%plán%' or workunitview.description like '%proje%plán%') AND (iterationStartDate = @firstIterationStartDate OR iterationStartDate = @secondIterationStartDate)); |
|
19 |
set @numberOfWikiPagesForProjectPlan = (SELECT count(*) from artifactview where projectId = @projectId AND artifactClass like 'WIKIPAGE' AND (artifactview.name like '%plán%projektu%' or artifactview.description like '%plán%projektu%' or artifactview.name like '%project%plan%' or artifactview.description like '%project%plan%' or artifactview.name like '%plan%project%' or artifactview.description like '%plan%project%' or artifactview.name like '%proje%plán%' or artifactview.description like '%proje%plán%')); |
|
20 |
select @projectId as `projectId`, @numberOfIssuesForProjectPlan as `numberOfIssuesForProjectPlan`, @numberOfWikiPagesForProjectPlan as `numberOfWikiPagesForProjectPlan`; |
src/main/resources/queries/specify_nothing.sql | ||
---|---|---|
1 |
/* |
|
2 |
Anti-pattern name: Specify nothing |
|
3 |
|
|
4 |
Description: The specification is not done intentionally. Programmers are |
|
5 |
expected to work better without written specifications. |
|
6 |
|
|
7 |
Detection: No specification artifact. There is no issue that will have something |
|
8 |
like "DSP, SPECIFICATIONS, ETC." in the title. Initially, meetings |
|
9 |
with the customer should be more frequent to clarify the project framework. |
|
10 |
No entry in the wiki with the project specification. |
|
11 |
*/ |
|
12 |
|
|
13 |
/* Init project id */ |
|
14 |
set @projectId = ?; |
|
15 |
/* Find number of wikipages with some project specification */ |
|
16 |
set @numberOfWikiPages = (select count(name) from artifactview where projectId = @projectId and (name like '%DSP%' or name like '%specifikace%' or name like '%specification%' or description like '%DSP%' or description like '%specifikace%' or description like '%specification%')); |
|
17 |
/* Find activities for creating DSP or project specification */ |
|
18 |
set @numberOfActivitiesForSpecification = (SELECT count(id) from workunitview where projectId = @projectId and (name like '%DSP%' or name like '%specifikace%' or name like '%specification%' or description like '%DSP%' or description like '%specifikace%' or description like '%specification%')); |
|
19 |
/* Count average length of issues description */ |
|
20 |
set @averageLengthOfIssueDescription = (select AVG(CHAR_LENGTH(workunitview.description)) from workunitview where workunitview.projectId = @projectId); |
|
21 |
/* Show all statistics */ |
|
22 |
select @projectId as `projectId`, @numberOfWikiPages as `numberOfWikiPages`, @numberOfActivitiesForSpecification as `numberOfActivitiesForSpecification`, @averageLengthOfIssueDescription as `averageLengthOfIssueDescription`; |
src/main/resources/queries/too_long_sprint.sql | ||
---|---|---|
1 |
/* |
|
2 |
Anti-pattern name: Too Long Sprint |
|
3 |
|
|
4 |
Description: Iterations too long. (ideal iteration length is about 1-2 weeks, |
|
5 |
maximum 3 weeks). It could also be detected here if the length |
|
6 |
of the iteration does not change often (It can change at the |
|
7 |
beginning and at the end of the project, but it should not |
|
8 |
change in the already started project). |
|
9 |
|
|
10 |
Detection: Detect the beginning and end of the iteration and what is |
|
11 |
the interval between these time points. We should exclude |
|
12 |
the initial and final iterations, as they could skew the result. |
|
13 |
*/ |
|
14 |
|
|
15 |
/* Init project id */ |
|
16 |
set @projectId = ?; |
|
17 |
/* Maximum iteration length in days */ |
|
18 |
set @maxSprintLength = 20; |
|
19 |
/* Exclude first and last iteration? */ |
|
20 |
set @excludeFirstAndLastIteration = true; |
|
21 |
/* Id of first iteration */ |
|
22 |
set @idOfFirstIteration = (select id from iteration where iteration.superProjectId = @projectId order by startDate limit 1); |
|
23 |
/* Id of last iteration */ |
|
24 |
set @idOfLastIteration = (select id from iteration where iteration.superProjectId = @projectId order by startDate desc limit 1); |
|
25 |
/* Select all too long iterations */ |
|
26 |
select datediff(iteration.endDate, iteration.startDate) as `iterationLength`, if(datediff(iteration.endDate, iteration.startDate) > @maxSprintLength, true, false) as `isTooLongSprint`, iteration.startDate as `iterationStartDate` from iteration where iteration.superProjectId = @projectId and iteration.id != if(@excludeFirstAndLastIteration = true, @idOfFirstIteration, -1) and iteration.id != if(@excludeFirstAndLastIteration = true, @idOfLastIteration, -1) order by iteration.startDate; |
src/main/resources/queries/varying_sprint_length.sql | ||
---|---|---|
1 |
/* |
|
2 |
Anti-pattern name: Varying Sprint Length |
|
3 |
|
|
4 |
Description: The length of the sprint changes very often. |
|
5 |
It is clear that iterations will be different |
|
6 |
lengths at the beginning and end of the project, |
|
7 |
but the length of the sprint should not change |
|
8 |
during the project. |
|
9 |
|
|
10 |
|
|
11 |
Detection: Detect sprint lengths throughout the project |
|
12 |
and see if they are too different. Possibility to |
|
13 |
eliminate the first and last sprint. It could be |
|
14 |
otherwise long. Detection would be similar to |
|
15 |
Too Long sprint anti-pattern. |
|
16 |
*/ |
|
17 |
|
|
18 |
/* Init project id */ |
|
19 |
set @projectId = ?; |
|
20 |
/* Exclude first and last iteration? */ |
|
21 |
set @excludeFirstAndLastIteration = true; |
|
22 |
/* Id of first iteration */ |
|
23 |
set @idOfFirstIteration = (select id from iteration where iteration.superProjectId = @projectId order by startDate limit 1); |
|
24 |
/* Id of last iteration */ |
|
25 |
set @idOfLastIteration = (select id from iteration where iteration.superProjectId = @projectId order by startDate desc limit 1); |
|
26 |
/* Select all iterations with their length */ |
|
27 |
select datediff(endDate, startDate) as `iterationLength` from iteration where iteration.superProjectId = @projectId and iteration.id != if(@excludeFirstAndLastIteration = true, @idOfFirstIteration, -1) and iteration.id != if(@excludeFirstAndLastIteration = true, @idOfLastIteration, -1) order by iteration.startDate; |
|
28 |
|
src/main/resources/templates/about.html | ||
---|---|---|
1 |
<!DOCTYPE HTML> |
|
2 |
<html xmlns:th="http://www.thymeleaf.org"> |
|
3 |
<head> |
|
4 |
<title>Anti Pattern Detector - About</title> |
|
5 |
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> |
|
6 |
<meta name="viewport" content="width=device-width, initial-scale=1"> |
|
7 |
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css"> |
|
8 |
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script> |
|
9 |
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js"></script> |
|
10 |
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script> |
|
11 |
|
|
12 |
</head> |
|
13 |
<body> |
|
14 |
<!-- Navigation bar imported --> |
|
15 |
<div th:replace="fragments/navbar :: navBar"></div> |
|
16 |
<!-- ./Navigation bar imported --> |
|
17 |
<div class="container"> |
|
18 |
<h1>About</h1> |
|
19 |
<p> |
|
20 |
Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean placerat. Vivamus luctus egestas leo. Aliquam ante. Praesent in mauris eu tortor porttitor accumsan. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Fusce consectetuer risus a nunc. Proin in tellus sit amet nibh dignissim sagittis. Fusce wisi. Maecenas lorem. Integer malesuada. |
|
21 |
|
|
22 |
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Integer imperdiet lectus quis justo. Aenean vel massa quis mauris vehicula lacinia. Integer in sapien. Maecenas fermentum, sem in pharetra pellentesque, velit turpis volutpat ante, in pharetra metus odio a lectus. Phasellus enim erat, vestibulum vel, aliquam a, posuere eu, velit. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Nulla non lectus sed nisl molestie malesuada. Nulla est. Cras elementum. Quisque tincidunt scelerisque libero. Nullam rhoncus aliquam metus. Vivamus luctus egestas leo. Aliquam erat volutpat. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Nullam dapibus fermentum ipsum. Etiam quis quam. |
|
23 |
|
|
24 |
Nullam feugiat, turpis at pulvinar vulputate, erat libero tristique tellus, nec bibendum odio risus sit amet ante. Phasellus enim erat, vestibulum vel, aliquam a, posuere eu, velit. Quisque tincidunt scelerisque libero. Mauris dolor felis, sagittis at, luctus sed, aliquam non, tellus. Nullam sit amet magna in magna gravida vehicula. Phasellus faucibus molestie nisl. In enim a arcu imperdiet malesuada. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nullam lectus justo, vulputate eget mollis sed, tempor sed magna. Praesent id justo in neque elementum ultrices. In enim a arcu imperdiet malesuada. Aliquam ornare wisi eu metus. Morbi imperdiet, mauris ac auctor dictum, nisl ligula egestas nulla, et sollicitudin sem purus in lacus. Vestibulum erat nulla, ullamcorper nec, rutrum non, nonummy ac, erat. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Fusce tellus odio, dapibus id fermentum quis, suscipit id erat. Mauris metus. Nulla non lectus sed nisl molestie malesuada. Et harum quidem rerum facilis est et expedita distinctio. Ut tempus purus at lorem. |
|
25 |
|
|
26 |
Phasellus enim erat, vestibulum vel, aliquam a, posuere eu, velit. Pellentesque sapien. Morbi leo mi, nonummy eget tristique non, rhoncus non leo. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos hymenaeos. Duis ante orci, molestie vitae vehicula venenatis, tincidunt ac pede. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum fermentum tortor id mi. Nullam sit amet magna in magna gravida vehicula. Aliquam id dolor. Donec quis nibh at felis congue commodo. |
|
27 |
|
|
28 |
Curabitur ligula sapien, pulvinar a vestibulum quis, facilisis vel sapien. Aenean fermentum risus id tortor. Etiam sapien elit, consequat eget, tristique non, venenatis quis, ante. Aenean id metus id velit ullamcorper pulvinar. Nunc auctor. Maecenas aliquet accumsan leo. Sed convallis magna eu sem. Mauris tincidunt sem sed arcu. Fusce consectetuer risus a nunc. Nulla turpis magna, cursus sit amet, suscipit a, interdum id, felis. Fusce tellus. |
|
29 |
</p> |
|
30 |
|
|
31 |
</body> |
|
32 |
</html> |
src/main/resources/templates/anti-pattern.html | ||
---|---|---|
1 |
<!DOCTYPE html> |
|
2 |
<html xmlns:th="http://www.thymeleaf.org"> |
|
3 |
<head> |
|
4 |
<meta charset="UTF-8"> |
|
5 |
<title>Anti Pattern Detector - Anti Pattern details</title> |
|
6 |
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> |
|
7 |
<meta name="viewport" content="width=device-width, initial-scale=1"> |
|
8 |
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css"> |
|
9 |
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script> |
|
10 |
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js"></script> |
|
11 |
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script> |
|
12 |
|
|
13 |
<style> |
|
14 |
.card { |
|
15 |
margin-top: 40px; |
|
16 |
margin-left: 5%; |
|
17 |
margin-right: 5%; |
|
18 |
|
|
19 |
} |
|
20 |
</style> |
|
21 |
</head> |
|
22 |
<body> |
|
23 |
<!-- Navigation bar imported --> |
|
24 |
<div th:replace="fragments/navbar :: navBar"></div> |
|
25 |
<!-- ./Navigation bar imported --> |
|
26 |
<div class="card"> |
|
27 |
<h5 class="card-header">Anti Pattern</h5> |
|
28 |
<div class="card-body"> |
|
29 |
<div class="form-group"> |
|
30 |
<label for="projectId">Anti Pattern Id:</label> |
|
31 |
<input disabled type="text" class="form-control" id="projectId" th:value="${antiPattern.id}"> |
|
32 |
</div> |
|
33 |
<div class="form-group"> |
|
34 |
<label for="projectId">Project id:</label> |
|
35 |
<input disabled type="text" class="form-control" id="projectName" th:value="${antiPattern.printName}"> |
|
36 |
</div> |
|
37 |
<div class="form-group"> |
|
38 |
<label for="projectId">Project id:</label> |
|
39 |
<textarea disabled class="form-control" id="projectDescription" rows="5" th:text="${antiPattern.description}"></textarea> |
|
40 |
</div> |
|
41 |
</div> |
|
42 |
</div> |
|
43 |
|
|
44 |
</body> |
|
45 |
</html> |
src/main/resources/templates/fragments/navbar.html | ||
---|---|---|
1 |
<div th:fragment="navBar" xmlns:th="http://www.w3.org/1999/xhtml"> |
|
2 |
<nav class="navbar navbar-expand-lg navbar-light bg-light"> |
|
3 |
<a class="navbar-brand">Anti Pattern Detector</a> |
|
4 |
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation"> |
|
5 |
<span class="navbar-toggler-icon"></span> |
|
6 |
</button> |
|
7 |
<div class="collapse navbar-collapse" id="navbarSupportedContent"> |
|
8 |
<ul class="navbar-nav mr-auto"> |
|
9 |
<li class="nav-item active"> |
|
10 |
<a class="nav-link" th:href="@{/}">Home</a> |
|
11 |
</li> |
|
12 |
<li class="nav-item active"> |
|
13 |
<a class="nav-link" th:href="@{/about}">About</a> |
|
14 |
</li> |
|
15 |
</div> |
|
16 |
</nav> |
|
17 |
</div> |
src/main/resources/templates/index.html | ||
---|---|---|
1 |
<!DOCTYPE HTML> |
|
2 |
<html xmlns:th="http://www.thymeleaf.org"> |
|
3 |
<head> |
|
4 |
<title>Anti Pattern Detector</title> |
|
5 |
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> |
|
6 |
<meta name="viewport" content="width=device-width, initial-scale=1"> |
|
7 |
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css"> |
|
8 |
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script> |
|
9 |
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js"></script> |
|
10 |
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script> |
|
11 |
|
|
12 |
<style> |
|
13 |
tbody tr:hover { |
|
14 |
background-color: #F8F9FA; |
|
15 |
} |
|
16 |
|
|
17 |
.analyze-button-container { |
|
18 |
margin-left: 40%; |
|
19 |
margin-right: 40%; |
|
20 |
margin-bottom: 40px; |
|
21 |
} |
|
22 |
</style> |
|
23 |
|
|
24 |
<script> |
|
25 |
function checkAllProjects(checkBox) { |
|
26 |
var projects = document.querySelectorAll('*[id^="project_"]'); |
|
27 |
var i; |
|
28 |
for (i = 0; i < projects.length; i++) { |
|
29 |
projects[i].checked = !!checkBox.checked; |
|
30 |
} |
|
31 |
} |
|
32 |
|
|
33 |
function checkAllAntiPatterns(checkBox) { |
|
34 |
var antiPatterns = document.querySelectorAll('*[id^="anti-pattern"]'); |
|
35 |
var i; |
|
36 |
for (i = 0; i < antiPatterns.length; i++) { |
|
37 |
antiPatterns[i].checked = !!checkBox.checked; |
|
38 |
} |
|
39 |
} |
|
40 |
</script> |
|
41 |
</head> |
|
42 |
<body> |
|
43 |
<!-- Navigation bar imported --> |
|
44 |
<div th:replace="fragments/navbar :: navBar"></div> |
|
45 |
<!-- ./Navigation bar imported --> |
|
46 |
<form action="#" th:action="@{/analyze}" th:object="${query}" method="post"> |
|
47 |
<div class="container"> |
|
48 |
<div class="row"> |
|
49 |
<div class="col"> |
|
50 |
<h1>Projects</h1> |
|
51 |
</div> |
|
52 |
<div class="col"> |
|
53 |
<h1>Anti Patterns</h1><br> |
|
54 |
</div> |
|
55 |
</div> |
|
56 |
|
|
57 |
<div class="row"> |
|
58 |
<div class="col"> |
|
59 |
<table class="table"> |
|
60 |
<thead> |
|
61 |
<tr> |
|
62 |
<th scope="col">#</th> |
|
63 |
<th scope="col">Project Name</th> |
|
64 |
<th scope="col">Detail</th> |
|
65 |
<th scope="col">Analyze?</th> |
|
66 |
</tr> |
|
67 |
</thead> |
|
68 |
<tbody> |
|
69 |
<tr th:each="project : ${query.projects}"> |
|
70 |
<td th:text="${project.id}"></td> |
|
71 |
<td th:text="${project.name}"></td> |
|
72 |
<td><a th:href="@{/projects/} + ${project.id}">Show</a></td> |
|
73 |
<td style="text-align: center"> |
|
74 |
<input checked type="checkbox" class="form-check-input" name="selectedProjects" th:value="${project.id}" th:id="@{project_} + ${project.id}"> |
|
75 |
</td> |
|
76 |
</tr> |
|
77 |
<tr> |
|
78 |
<td></td> |
|
79 |
<td></td> |
|
80 |
<td></td> |
|
81 |
<td> |
|
82 |
<input checked type="checkbox" class="form-check-input" id="select_all_projects" |
|
83 |
onclick="checkAllProjects(this)"> |
|
84 |
<label class="form-check-label" for="select_all_projects">Select All</label> |
|
85 |
</td> |
|
86 |
</tr> |
|
87 |
</tbody> |
|
88 |
</table> |
|
89 |
</div> |
|
90 |
<div class="col"> |
|
91 |
<table class="table"> |
|
92 |
<thead> |
|
93 |
<tr> |
|
94 |
<th scope="col">#</th> |
|
95 |
<th scope="col">Anti Pattern</th> |
|
96 |
<th scope="col">Detail</th> |
|
97 |
<th scope="col">Analyze?</th> |
|
98 |
</tr> |
|
99 |
</thead> |
|
100 |
<tbody> |
|
101 |
<tr th:each="antiPattern : ${query.antiPatterns}"> |
|
102 |
<td th:text="${antiPattern.id}"></td> |
|
103 |
<td th:text="${antiPattern.printName}"></td> |
|
104 |
<td><a th:href="@{/anti-patterns/} + ${antiPattern.id}">Show</a></td> |
|
105 |
<td style="text-align: center"> |
|
106 |
<input checked type="checkbox" class="form-check-input" name="selectedAntiPatterns" th:value="${antiPattern.id}" th:id="@{anti-pattern_} + ${antiPattern.id}"> |
|
107 |
</td> |
|
108 |
</tr> |
|
109 |
<tr> |
|
110 |
<td></td> |
|
111 |
<td></td> |
Také k dispozici: Unified diff
Migration project from local storage to git