Browse Source

Added age chart. Refactored code.

master
Christian P. MOMON 2 years ago
parent
commit
de605e2a55
  1. 28
      src/org/april/agirstatool/cli/SQLUtils.java
  2. 447
      src/org/april/agirstatool/core/AgirStatool.java
  3. 181
      src/org/april/agirstatool/core/AgirStatoolUtils.java
  4. 166
      src/org/april/agirstatool/core/Issue.java
  5. 129
      src/org/april/agirstatool/core/Issues.java
  6. 7
      src/org/april/agirstatool/core/Project.java
  7. 131
      src/org/april/agirstatool/core/Stat.java
  8. 54
      src/org/april/agirstatool/core/pages/CreatedClosedCountChartView.java
  9. 52
      src/org/april/agirstatool/core/pages/CreatedClosedDiffChartView.java
  10. 198
      src/org/april/agirstatool/core/pages/IssueAgeChartView.java
  11. 4
      src/org/april/agirstatool/core/pages/ProjectPage.java
  12. 92
      src/org/april/agirstatool/core/pages/issueAgeChartView.xhtml
  13. 23
      src/org/april/agirstatool/core/pages/project.xhtml

28
src/org/april/agirstatool/cli/SQLUtils.java

@ -24,6 +24,8 @@ import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import org.apache.commons.lang3.StringUtils;
import org.april.agirstatool.core.AgirStatoolException;
@ -241,4 +243,30 @@ public final class SQLUtils
//
return result;
}
/**
* To date time.
*
* @param source
* the source
* @return the local date time
*/
public static LocalDateTime toLocalDateTime(final java.sql.Timestamp source)
{
LocalDateTime result;
if (source == null)
{
result = null;
}
else
{
long seconds = source.getTime() / 1000;
long nanos = (source.getTime() - seconds * 1000) * 1000000;
result = LocalDateTime.ofEpochSecond(seconds, (int) nanos, ZoneOffset.UTC);
}
//
return result;
}
}

447
src/org/april/agirstatool/core/AgirStatool.java

@ -238,6 +238,262 @@ public class AgirStatool
}
}
/**
* Fetch issue open closed dates.
*
* @param project
* the project
* @return the date count map
* @throws AgirStatoolException
* the agir statool exception
*/
public Issues fetchIssue(final Project project) throws AgirStatoolException
{
Issues result;
result = new Issues();
//
PreparedStatement statement = null;
ResultSet resultSet = null;
try
{
StringList subSql = new StringList();
if (project.getType() == Project.Type.CONSOLIDATED)
{
subSql.append("select ");
subSql.append(" id ");
subSql.append("from ");
subSql.append(" projects as childProject ");
subSql.append("where ");
subSql.append(" (childProject.id=" + project.getId() + " or childProject.parent_id=" + project.getId() + ")");
subSql.append(" and childProject.status=1 and childProject.is_public=1");
}
else if (project.getType() == Project.Type.ROOT)
{
subSql.append("select ");
subSql.append(" id ");
subSql.append("from ");
subSql.append(" projects as childProject ");
subSql.append("where ");
subSql.append(" childProject.status=1 and childProject.is_public=1");
}
else
{
subSql.append(project.getId());
}
StringList sql = new StringList();
sql.append("SELECT");
sql.append(" id, ");
sql.append(" project_id, ");
sql.append(" created_on, ");
sql.append(" closed_on ");
sql.append("FROM ");
sql.append(" issues ");
sql.append("WHERE ");
sql.append(" project_id in (" + subSql.toString() + ") ");
// System.out.println(sql.toStringSeparatedBy("\n"));
this.connection.setAutoCommit(true);
statement = this.connection.prepareStatement(sql.toString());
resultSet = statement.executeQuery();
while (resultSet.next())
{
int id = resultSet.getInt(1);
int projectId = resultSet.getInt(2);
LocalDateTime createdOn = SQLUtils.toLocalDateTime(resultSet.getTimestamp(3));
LocalDateTime closedOn = SQLUtils.toLocalDateTime(resultSet.getTimestamp(4));
Issue issue = new Issue(id, projectId, createdOn, closedOn);
result.add(issue);
}
}
catch (SQLException exception)
{
throw new AgirStatoolException("Error fetching day closed count: " + exception.getMessage(), exception);
}
finally
{
SQLUtils.closeQuietly(statement, resultSet);
}
//
return result;
}
/**
* List projects extended.
*
* @return the projects
* @throws AgirStatoolException
* the agir statool exception
*/
public Projects fetchProjectsWithoutSubStats() throws AgirStatoolException
{
Projects result;
result = fetchProjectsWithStats(Project.Type.ALONE);
//
return result;
}
/**
* List projects with stats.
*
* @param type
* the type
* @return the projects
* @throws AgirStatoolException
* the agir statool exception
*/
public Projects fetchProjectsWithStats(final Project.Type type) throws AgirStatoolException
{
Projects result;
result = new Projects();
//
PreparedStatement statement = null;
ResultSet resultSet = null;
try
{
StringList subSql = new StringList();
if (type == Project.Type.CONSOLIDATED)
{
subSql.append("select ");
subSql.append(" id ");
subSql.append("from ");
subSql.append(" projects as childProject ");
subSql.append("where ");
subSql.append(" (childProject.id=currentProject.id or childProject.parent_id=currentProject.id)");
subSql.append(" and childProject.status=1 and childProject.is_public=1");
}
else
{
subSql.append("currentProject.id");
}
StringList sql = new StringList();
sql.append("SELECT");
sql.append(" id,");
sql.append(" identifier,");
sql.append(" name,");
sql.append(" parent_id,");
sql.append(" (select count(*) from projects as subproject where subproject.parent_id=currentProject.id and subproject.status = 1 and subproject.is_public = 1) as child_count, ");
sql.append(" updated_on,");
sql.append(" (select count(*) from issues where issues.project_id in (" + subSql.toString() + ")) as issue_count,");
sql.append(" (select count(*) from issues where issues.project_id in (" + subSql.toString() + ") and issues.status_id= 1) as new_issue_count,");
sql.append(" (select count(*) from issues where issues.project_id in (" + subSql.toString() + ") and issues.status_id= 2) as ongoing_issue_count,");
sql.append(" (select count(*) from issues where issues.project_id in (" + subSql.toString() + ") and issues.status_id= 3) as resolved_issue_count,");
sql.append(" (select count(*) from issues where issues.project_id in (" + subSql.toString() + ") and issues.status_id= 5) as closed_issue_count,");
sql.append(" (select count(*) from issues where issues.project_id in (" + subSql.toString() + ") and issues.status_id= 6) as rejected_issue_count,");
sql.append(" (select count(*) from issues where issues.project_id in (" + subSql.toString() + ") and issues.status_id= 7) as confirmed_issue_count,");
sql.append(" (select count(*) from issues where issues.project_id in (" + subSql.toString() + ") and issues.status_id=15) as maybe_issue_count,");
sql.append(" (select count(*) from issues where issues.project_id in (" + subSql.toString() + ") and issues.status_id=16) as waiting_issue_count, ");
sql.append(" (select count(*) from issues where issues.project_id in (" + subSql.toString() + ") and issues.assigned_to_id is null) as unassigned_issue_count,");
sql.append(
" (select count(*) from issues where issues.project_id in (" + subSql.toString() + ") and issues.status_id= 1 and issues.assigned_to_id is null) as unassigned_new_issue_count,");
sql.append(" (select count(*) from issues where issues.project_id in (" + subSql.toString()
+ ") and issues.status_id= 2 and issues.assigned_to_id is null) as unassigned_ongoing_issue_count,");
sql.append(" (select count(*) from issues where issues.project_id in (" + subSql.toString()
+ ") and issues.status_id= 3 and issues.assigned_to_id is null) as unassigned_resolved_issue_count,");
sql.append(" (select count(*) from issues where issues.project_id in (" + subSql.toString()
+ ") and issues.status_id= 5 and issues.assigned_to_id is null) as unassigned_closed_issue_count,");
sql.append(" (select count(*) from issues where issues.project_id in (" + subSql.toString()
+ ") and issues.status_id= 6 and issues.assigned_to_id is null) as unassigned_rejected_issue_count,");
sql.append(" (select count(*) from issues where issues.project_id in (" + subSql.toString()
+ ") and issues.status_id= 7 and issues.assigned_to_id is null) as unassigned_confirmed_issue_count,");
sql.append(" (select count(*) from issues where issues.project_id in (" + subSql.toString()
+ ") and issues.status_id=15 and issues.assigned_to_id is null) as unassigned_maybe_issue_count,");
sql.append(" (select count(*) from issues where issues.project_id in (" + subSql.toString()
+ ") and issues.status_id=16 and issues.assigned_to_id is null) as unassigned_waiting_issue_count, ");
sql.append(" (select min(created_on) from issues where issues.project_id in (" + subSql.toString() + ")) as first_issue_create, ");
sql.append(" (select max(updated_on) from issues where issues.project_id in (" + subSql.toString() + ")) as last_issue_update ");
sql.append("FROM ");
sql.append(" projects as currentProject ");
sql.append("WHERE ");
sql.append(" currentProject.status=1 and currentProject.is_public=1;");
// System.out.println(sql.toStringSeparatedBy("\n"));
this.connection.setAutoCommit(true);
statement = this.connection.prepareStatement(sql.toString());
resultSet = statement.executeQuery();
while (resultSet.next())
{
long id = resultSet.getLong(1);
String identifier = resultSet.getString(2);
String name = resultSet.getString(3);
Long parentId = SQLUtils.getNullableLong(resultSet, 4);
Project project = new Project(id, identifier, name, parentId);
project.setChildCount(resultSet.getLong(5));
project.setLastUpdate(SQLUtils.toLocalDateTime(resultSet.getTimestamp(6)));
project.issueStats().setCount(resultSet.getLong(7));
project.issueStats().setNewCount(resultSet.getLong(8));
project.issueStats().setOngoingCount(resultSet.getLong(9));
project.issueStats().setResolvedCount(resultSet.getLong(10));
project.issueStats().setClosedCount(resultSet.getLong(11));
project.issueStats().setRejectedCount(resultSet.getLong(12));
project.issueStats().setConfirmedCount(resultSet.getLong(13));
project.issueStats().setMaybeCount(resultSet.getLong(14));
project.issueStats().setWaitingCount(resultSet.getLong(15));
project.issueStats().setUnassignedCount(resultSet.getLong(16));
project.issueStats().setUnassignedNewCount(resultSet.getLong(17));
project.issueStats().setUnassignedOngoingCount(resultSet.getLong(18));
project.issueStats().setUnassignedResolvedCount(resultSet.getLong(19));
project.issueStats().setUnassignedClosedCount(resultSet.getLong(20));
project.issueStats().setUnassignedRejectedCount(resultSet.getLong(21));
project.issueStats().setUnassignedConfirmedCount(resultSet.getLong(22));
project.issueStats().setUnassignedMaybeCount(resultSet.getLong(23));
project.issueStats().setUnassignedWaitingCount(resultSet.getLong(24));
project.issueStats().setFirstCreate(SQLUtils.toLocalDateTime(resultSet.getTimestamp(25)));
project.issueStats().setLastUpdate(SQLUtils.toLocalDateTime(resultSet.getTimestamp(26)));
result.add(project);
}
}
catch (SQLException exception)
{
throw new AgirStatoolException("Error reading projects extended: " + exception.getMessage(), exception);
}
finally
{
SQLUtils.closeQuietly(statement, resultSet);
}
//
return result;
}
/**
* List projects consolidated.
*
* @return the projects
* @throws AgirStatoolException
* the agir statool exception
*/
public Projects fetchProjectsWithSubStats() throws AgirStatoolException
{
Projects result;
result = fetchProjectsWithStats(Project.Type.CONSOLIDATED);
//
return result;
}
/**
* Fetch week concluded count.
*
@ -247,7 +503,7 @@ public class AgirStatool
* @throws AgirStatoolException
* the agir statool exception
*/
public DateCountMap fetchWeekConcludedCount(final Project project) throws AgirStatoolException
public DateCountMap fetchWeekClosedOnCount(final Project project) throws AgirStatoolException
{
DateCountMap result;
@ -458,7 +714,7 @@ public class AgirStatool
result = new Project(id, identifier, name, parentId);
result.setChildCount(resultSet.getLong(5));
result.setLastUpdate(AgirStatoolUtils.toLocaleDateTime(resultSet.getTimestamp(6)));
result.setLastUpdate(SQLUtils.toLocalDateTime(resultSet.getTimestamp(6)));
result.issueStats().setCount(resultSet.getLong(7));
result.issueStats().setNewCount(resultSet.getLong(8));
result.issueStats().setOngoingCount(resultSet.getLong(9));
@ -468,8 +724,8 @@ public class AgirStatool
result.issueStats().setConfirmedCount(resultSet.getLong(13));
result.issueStats().setMaybeCount(resultSet.getLong(14));
result.issueStats().setWaitingCount(resultSet.getLong(15));
result.issueStats().setFirstCreate(AgirStatoolUtils.toLocaleDateTime(resultSet.getTimestamp(16)));
result.issueStats().setLastUpdate(AgirStatoolUtils.toLocaleDateTime(resultSet.getTimestamp(17)));
result.issueStats().setFirstCreate(SQLUtils.toLocalDateTime(resultSet.getTimestamp(16)));
result.issueStats().setLastUpdate(SQLUtils.toLocalDateTime(resultSet.getTimestamp(17)));
}
}
catch (SQLException exception)
@ -587,7 +843,7 @@ public class AgirStatool
Project project = new Project(id, identifier, name, parentId);
project.setChildCount(resultSet.getLong(5));
project.setLastUpdate(AgirStatoolUtils.toLocaleDateTime(resultSet.getTimestamp(6)));
project.setLastUpdate(SQLUtils.toLocalDateTime(resultSet.getTimestamp(6)));
result.add(project);
}
@ -638,13 +894,17 @@ public class AgirStatool
// Fill created and concluded issues history.
{
{
Issues issues = fetchIssue(result);
result.issues().addAll(issues);
}
{
DateCountMap map = fetchWeekCreatedCount(result);
DateCountList counts = AgirStatoolUtils.normalizedWeekCountList(map, result.issueStats().getFirstCreate().toLocalDate());
result.issueStats().setWeekCreatedIssueCounts(counts);
}
{
DateCountMap map = fetchWeekConcludedCount(result);
DateCountMap map = fetchWeekClosedOnCount(result);
DateCountList counts = AgirStatoolUtils.normalizedWeekCountList(map, result.issueStats().getFirstCreate().toLocalDate());
result.issueStats().setWeekConcludedIssueCounts(counts);
}
@ -654,13 +914,17 @@ public class AgirStatool
logger.debug("Fetching Created/Closed history for " + project.getName());
if (project.hasIssue())
{
{
Issues issues = fetchIssue(project);
project.issues().addAll(issues);
}
{
DateCountMap map = fetchWeekCreatedCount(project);
DateCountList counts = AgirStatoolUtils.normalizedWeekCountList(map, project.issueStats().getFirstCreate().toLocalDate());
project.issueStats().setWeekCreatedIssueCounts(counts);
}
{
DateCountMap map = fetchWeekConcludedCount(project);
DateCountMap map = fetchWeekClosedOnCount(project);
DateCountList counts = AgirStatoolUtils.normalizedWeekCountList(map, project.issueStats().getFirstCreate().toLocalDate());
project.issueStats().setWeekConcludedIssueCounts(counts);
}
@ -687,175 +951,6 @@ public class AgirStatool
return result;
}
/**
* List projects extended.
*
* @return the projects
* @throws AgirStatoolException
* the agir statool exception
*/
public Projects fetchProjectsWithoutSubStats() throws AgirStatoolException
{
Projects result;
result = fetchProjectsWithStats(Project.Type.ALONE);
//
return result;
}
/**
* List projects with stats.
*
* @param type
* the type
* @return the projects
* @throws AgirStatoolException
* the agir statool exception
*/
public Projects fetchProjectsWithStats(final Project.Type type) throws AgirStatoolException
{
Projects result;
result = new Projects();
//
PreparedStatement statement = null;
ResultSet resultSet = null;
try
{
StringList subSql = new StringList();
if (type == Project.Type.CONSOLIDATED)
{
subSql.append("select ");
subSql.append(" id ");
subSql.append("from ");
subSql.append(" projects as childProject ");
subSql.append("where ");
subSql.append(" (childProject.id=currentProject.id or childProject.parent_id=currentProject.id)");
subSql.append(" and childProject.status=1 and childProject.is_public=1");
}
else
{
subSql.append("currentProject.id");
}
StringList sql = new StringList();
sql.append("SELECT");
sql.append(" id,");
sql.append(" identifier,");
sql.append(" name,");
sql.append(" parent_id,");
sql.append(" (select count(*) from projects as subproject where subproject.parent_id=currentProject.id and subproject.status = 1 and subproject.is_public = 1) as child_count, ");
sql.append(" updated_on,");
sql.append(" (select count(*) from issues where issues.project_id in (" + subSql.toString() + ")) as issue_count,");
sql.append(" (select count(*) from issues where issues.project_id in (" + subSql.toString() + ") and issues.status_id= 1) as new_issue_count,");
sql.append(" (select count(*) from issues where issues.project_id in (" + subSql.toString() + ") and issues.status_id= 2) as ongoing_issue_count,");
sql.append(" (select count(*) from issues where issues.project_id in (" + subSql.toString() + ") and issues.status_id= 3) as resolved_issue_count,");
sql.append(" (select count(*) from issues where issues.project_id in (" + subSql.toString() + ") and issues.status_id= 5) as closed_issue_count,");
sql.append(" (select count(*) from issues where issues.project_id in (" + subSql.toString() + ") and issues.status_id= 6) as rejected_issue_count,");
sql.append(" (select count(*) from issues where issues.project_id in (" + subSql.toString() + ") and issues.status_id= 7) as confirmed_issue_count,");
sql.append(" (select count(*) from issues where issues.project_id in (" + subSql.toString() + ") and issues.status_id=15) as maybe_issue_count,");
sql.append(" (select count(*) from issues where issues.project_id in (" + subSql.toString() + ") and issues.status_id=16) as waiting_issue_count, ");
sql.append(" (select count(*) from issues where issues.project_id in (" + subSql.toString() + ") and issues.assigned_to_id is null) as unassigned_issue_count,");
sql.append(
" (select count(*) from issues where issues.project_id in (" + subSql.toString() + ") and issues.status_id= 1 and issues.assigned_to_id is null) as unassigned_new_issue_count,");
sql.append(" (select count(*) from issues where issues.project_id in (" + subSql.toString()
+ ") and issues.status_id= 2 and issues.assigned_to_id is null) as unassigned_ongoing_issue_count,");
sql.append(" (select count(*) from issues where issues.project_id in (" + subSql.toString()
+ ") and issues.status_id= 3 and issues.assigned_to_id is null) as unassigned_resolved_issue_count,");
sql.append(" (select count(*) from issues where issues.project_id in (" + subSql.toString()
+ ") and issues.status_id= 5 and issues.assigned_to_id is null) as unassigned_closed_issue_count,");
sql.append(" (select count(*) from issues where issues.project_id in (" + subSql.toString()
+ ") and issues.status_id= 6 and issues.assigned_to_id is null) as unassigned_rejected_issue_count,");
sql.append(" (select count(*) from issues where issues.project_id in (" + subSql.toString()
+ ") and issues.status_id= 7 and issues.assigned_to_id is null) as unassigned_confirmed_issue_count,");
sql.append(" (select count(*) from issues where issues.project_id in (" + subSql.toString()
+ ") and issues.status_id=15 and issues.assigned_to_id is null) as unassigned_maybe_issue_count,");
sql.append(" (select count(*) from issues where issues.project_id in (" + subSql.toString()
+ ") and issues.status_id=16 and issues.assigned_to_id is null) as unassigned_waiting_issue_count, ");
sql.append(" (select min(created_on) from issues where issues.project_id in (" + subSql.toString() + ")) as first_issue_create, ");
sql.append(" (select max(updated_on) from issues where issues.project_id in (" + subSql.toString() + ")) as last_issue_update ");
sql.append("FROM ");
sql.append(" projects as currentProject ");
sql.append("WHERE ");
sql.append(" currentProject.status=1 and currentProject.is_public=1;");
// System.out.println(sql.toStringSeparatedBy("\n"));
this.connection.setAutoCommit(true);
statement = this.connection.prepareStatement(sql.toString());
resultSet = statement.executeQuery();
while (resultSet.next())
{
long id = resultSet.getLong(1);
String identifier = resultSet.getString(2);
String name = resultSet.getString(3);
Long parentId = SQLUtils.getNullableLong(resultSet, 4);
Project project = new Project(id, identifier, name, parentId);
project.setChildCount(resultSet.getLong(5));
project.setLastUpdate(AgirStatoolUtils.toLocaleDateTime(resultSet.getTimestamp(6)));
project.issueStats().setCount(resultSet.getLong(7));
project.issueStats().setNewCount(resultSet.getLong(8));
project.issueStats().setOngoingCount(resultSet.getLong(9));
project.issueStats().setResolvedCount(resultSet.getLong(10));
project.issueStats().setClosedCount(resultSet.getLong(11));
project.issueStats().setRejectedCount(resultSet.getLong(12));
project.issueStats().setConfirmedCount(resultSet.getLong(13));
project.issueStats().setMaybeCount(resultSet.getLong(14));
project.issueStats().setWaitingCount(resultSet.getLong(15));
project.issueStats().setUnassignedCount(resultSet.getLong(16));
project.issueStats().setUnassignedNewCount(resultSet.getLong(17));
project.issueStats().setUnassignedOngoingCount(resultSet.getLong(18));
project.issueStats().setUnassignedResolvedCount(resultSet.getLong(19));
project.issueStats().setUnassignedClosedCount(resultSet.getLong(20));
project.issueStats().setUnassignedRejectedCount(resultSet.getLong(21));
project.issueStats().setUnassignedConfirmedCount(resultSet.getLong(22));
project.issueStats().setUnassignedMaybeCount(resultSet.getLong(23));
project.issueStats().setUnassignedWaitingCount(resultSet.getLong(24));
project.issueStats().setFirstCreate(AgirStatoolUtils.toLocaleDateTime(resultSet.getTimestamp(25)));
project.issueStats().setLastUpdate(AgirStatoolUtils.toLocaleDateTime(resultSet.getTimestamp(26)));
result.add(project);
}
}
catch (SQLException exception)
{
throw new AgirStatoolException("Error reading projects extended: " + exception.getMessage(), exception);
}
finally
{
SQLUtils.closeQuietly(statement, resultSet);
}
//
return result;
}
/**
* List projects consolidated.
*
* @return the projects
* @throws AgirStatoolException
* the agir statool exception
*/
public Projects fetchProjectsWithSubStats() throws AgirStatoolException
{
Projects result;
result = fetchProjectsWithStats(Project.Type.CONSOLIDATED);
//
return result;
}
public void putTouchFile()
{

181
src/org/april/agirstatool/core/AgirStatoolUtils.java

@ -20,7 +20,6 @@ package org.april.agirstatool.core;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.Locale;
@ -47,6 +46,162 @@ public class AgirStatoolUtils
public static final DateTimeFormatter PATTERN_SHORTDATE = DateTimeFormatter.ofPattern("dd/MM/yyyy", Locale.FRANCE);
public static final DateTimeFormatter PATTERN_LONGDATE = DateTimeFormatter.ofPattern("dd/MM/yyyy hh':'mm", Locale.FRANCE);
/**
* Builds the week labels.
*
* @param start
* the start
* @return the string list
*/
public static StringList buildWeekLabels(final LocalDate start)
{
StringList result;
result = buildWeekLabels(start, LocalDate.now());
//
return result;
}
/**
* Builds the week labels.
*
* @param start
* the start
* @param end
* the end
* @return the string list
*/
public static StringList buildWeekLabels(final LocalDate start, final LocalDate end)
{
StringList result;
result = new StringList();
if (start != null)
{
LocalDate normalizedEnd = AgirStatoolUtils.normaliseWeekDate(end);
LocalDate date = AgirStatoolUtils.normaliseWeekDate(start);
while (date.isBefore(normalizedEnd) || date.isEqual(normalizedEnd))
{
String label = date.format(DateTimeFormatter.ofPattern("yyyy-MMM", Locale.FRANCE));
result.add(label);
date = date.plusWeeks(1);
}
}
//
return result;
}
/**
* Builds the week max ages.
*
* @param project
* the project
* @param start
* the start
* @param end
* the end
* @return the string list
*/
public static StringList buildWeekMaxAges(final Project project, final LocalDate start, final LocalDate end)
{
StringList result;
result = new StringList();
if (start != null)
{
LocalDate date = AgirStatoolUtils.normaliseWeekDate(start);
LocalDate normalizedEnd = AgirStatoolUtils.normaliseWeekDate(end);
while (date.isBefore(normalizedEnd) || date.isEqual(normalizedEnd))
{
Stat stat = project.issues().extractActivedAt(date).computeStat(date);
result.add(String.valueOf(stat.getMax()));
//
date = date.plusWeeks(1);
}
}
//
return result;
}
/**
* Builds the week mean ages.
*
* @param project
* the project
* @param start
* the start
* @param end
* the end
* @return the string list
*/
public static StringList buildWeekMeanAges(final Project project, final LocalDate start, final LocalDate end)
{
StringList result;
result = new StringList();
if (start != null)
{
LocalDate date = AgirStatoolUtils.normaliseWeekDate(start);
LocalDate normalizedEnd = AgirStatoolUtils.normaliseWeekDate(end);
while (date.isBefore(normalizedEnd) || date.isEqual(normalizedEnd))
{
Stat stat = project.issues().extractActivedAt(date).computeStat(date);
result.add(String.format(Locale.ENGLISH, "%.2f", stat.getMean()));
//
date = date.plusWeeks(1);
}
}
//
return result;
}
/**
* Builds the week min ages.
*
* @param project
* the project
* @param start
* the start
* @param end
* the end
* @return the string list
*/
public static StringList buildWeekMinAges(final Project project, final LocalDate start, final LocalDate end)
{
StringList result;
result = new StringList();
if (start != null)
{
LocalDate date = AgirStatoolUtils.normaliseWeekDate(start);
LocalDate normalizedEnd = AgirStatoolUtils.normaliseWeekDate(end);
while (date.isBefore(normalizedEnd) || date.isEqual(normalizedEnd))
{
Stat stat = project.issues().extractActivedAt(date).computeStat(date);
result.add(String.valueOf(stat.getMin()));
//
date = date.plusWeeks(1);
}
}
//
return result;
}
/**
* Normalise week date.
*
@ -376,30 +531,6 @@ public class AgirStatoolUtils
return result;
}
/**
* To date time.
*
* @param source
* the source
* @return the local date time
*/
public static LocalDateTime toLocaleDateTime(final java.sql.Timestamp source)
{
LocalDateTime result;
if (source == null)
{
result = null;
}
else
{
result = LocalDateTime.ofEpochSecond(source.getTime() / 1000, 0, ZoneOffset.UTC);
}
//
return result;
}
/**
* To year week.
*

166
src/org/april/agirstatool/core/Issue.java

@ -0,0 +1,166 @@
/*
* Copyright (C) 2020 Christian Pierre MOMON <christian.momon@devinsy.fr>
*
* This file is part of AgirStatool, simple key value database.
*
* AgirStatool is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* AgirStatool is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with AgirStatool. If not, see <http://www.gnu.org/licenses/>.
*/
package org.april.agirstatool.core;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneOffset;
/**
* The Class Issue.
*/
public class Issue
{
private int id;
private Integer projectId;
private LocalDateTime createdOn;
private LocalDateTime closedOn;
/**
* Instantiates a new issue.
*
* @param id
* the id
* @param createdOn
* the created on
*/
public Issue(final int id, final Integer projectId, final LocalDateTime createdOn)
{
this(id, projectId, createdOn, null);
}
/**
* Instantiates a new issue.
*
* @param id
* the id
* @param createdOn
* the created on
* @param closedOn
* the closed on
*/
public Issue(final int id, final Integer projectId, final LocalDateTime createdOn, final LocalDateTime closedOn)
{
this.id = id;
this.projectId = projectId;
this.createdOn = createdOn;
this.closedOn = closedOn;
}
/**
* Age in days.
*
* @return the int
*/
public int getAgeInDays()
{
int result;
long start = this.createdOn.toEpochSecond(ZoneOffset.UTC);
long end;
if (this.closedOn == null)
{
end = LocalDateTime.now().toEpochSecond(ZoneOffset.UTC);
}
else
{
end = this.closedOn.toEpochSecond(ZoneOffset.UTC);
}
result = (int) ((end - start) / (24 * 60 * 60));
//
return result;
}
/**
* Gets the age in days.
*
* @param date
* the date
* @return the age in days
*/
public int getAgeInDays(final LocalDate date)
{
int result;
if (date == null)
{
result = 0;
}
else if ((this.closedOn != null) && (this.closedOn.isBefore(LocalDateTime.of(date, LocalTime.MAX))))
{
long start = this.createdOn.toEpochSecond(ZoneOffset.UTC);
long end = this.closedOn.toEpochSecond(ZoneOffset.UTC);
result = (int) ((end - start) / (24 * 60 * 60));
}
else
{
long start = this.createdOn.toEpochSecond(ZoneOffset.UTC);
long end = LocalDateTime.of(date, LocalTime.MAX).toEpochSecond(ZoneOffset.UTC);
result = (int) ((end - start) / (24 * 60 * 60));
}
//
return result;
}
public LocalDateTime getClosedOn()
{
return this.closedOn;
}
public LocalDateTime getCreatedOn()
{
return this.createdOn;
}
public int getId()
{
return this.id;
}
public Integer getProjectId()
{
return this.projectId;
}
public void setClosedOn(final LocalDateTime closedOn)
{
this.closedOn = closedOn;
}
public void setCreatedOn(final LocalDateTime createdOn)
{
this.createdOn = createdOn;
}
public void setId(final int id)
{
this.id = id;
}
public void setProjectId(final Integer projectId)
{
this.projectId = projectId;
}
}

129
src/org/april/agirstatool/core/Issues.java

@ -0,0 +1,129 @@
/*
* Copyright (C) 2020 Christian Pierre MOMON <christian.momon@devinsy.fr>
*
* This file is part of AgirStatool, simple key value database.
*
* AgirStatool is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* AgirStatool is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with AgirStatool. If not, see <http://www.gnu.org/licenses/>.
*/
package org.april.agirstatool.core;
import java.time.LocalDate;
import java.util.ArrayList;
/**
* The Class IssueList.
*/
public class Issues extends ArrayList<Issue>
{
private static final long serialVersionUID = -6241164269103539500L;
/**
* Instantiates a new issue list.
*/
public Issues()
{
super();
}
/**
* Instantiates a new issue list.
*
* @param capacity
* the capacity
*/
public Issues(final int capacity)
{
super(capacity);
}
/**
* Compute stat.
*
* @param date
* the date
* @return the stat
*/
public Stat computeStat()
{
Stat result;
result = new Stat();
for (Issue issue : this)
{
int age = issue.getAgeInDays();
result.addValue(age);
}
//
return result;
}
/**
* Compute stat.
*
* @param date
* the date
* @return the stat
*/
public Stat computeStat(final LocalDate date)
{
Stat result;
result = new Stat();
for (Issue issue : this)
{
int age = issue.getAgeInDays(date);
result.addValue(age);
}
//
return result;
}
/**
* Extract aticved at.
*
* @param date
* the date
* @return the issue list
*/
public Issues extractActivedAt(final LocalDate date)
{
Issues result;
result = new Issues();
for (Issue issue : this)
{
LocalDate createdOn = issue.getCreatedOn().toLocalDate();
LocalDate closedOn;
if (issue.getClosedOn() == null)
{
closedOn = null;
}
else
{
closedOn = issue.getClosedOn().toLocalDate();
}
if ((!createdOn.isAfter(date)) && ((issue.getClosedOn() == null) || (!closedOn.isBefore(date))))
{
result.add(issue);
}
}
//
return result;
}
}

7
src/org/april/agirstatool/core/Project.java

@ -41,6 +41,7 @@ public class Project
private Projects subProjects;
private IssueStats stats;
private LocalDateTime lastUpdate;
private Issues issues;
/**
* Instantiates a new project.
@ -64,6 +65,7 @@ public class Project
this.subProjects = new Projects();
this.childCount = 0;
this.stats = new IssueStats();
this.issues = new Issues();
}
public long getChildCount()
@ -171,6 +173,11 @@ public class Project
return result;
}
public Issues issues()
{
return this.issues;
}
public IssueStats issueStats()
{
return this.stats;

131
src/org/april/agirstatool/core/Stat.java

@ -0,0 +1,131 @@
/*
* Copyright (C) 2020 Christian Pierre MOMON <christian.momon@devinsy.fr>
*
* This file is part of AgirStatool, simple key value database.
*
* AgirStatool is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* AgirStatool is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with AgirStatool. If not, see <http://www.gnu.org/licenses/>.
*/
package org.april.agirstatool.core;
/**
* The Class Stat.
*/
public class Stat
{
int count;
double total;
double min;
double max;
/**
* Instantiates a new stat.
*/
public Stat()
{
this.count = 0;
this.total = 0;
this.min = 0;
this.max = 0;
}
/**
* Adds the value.
*
* @param value
* the value
*/
public void addValue(final double value)
{
if (this.count == 0)
{
this.total = value;
this.min = value;
this.max = value;
}
else
{
//
if (value < this.min)
{
this.min = value;
}
//
this.total += value;
//
if (value > this.max)
{
this.max = value;
}
}
this.count += 1;
}
public int getCount()
{
return this.count;
}
public double getMax()
{
return this.max;
}
/**
* Gets the mean.
*
* @return the mean
*/
public double getMean()
{
double result;
result = this.total / this.count;
//
return result;
}
public double getMin()
{
return this.min;
}
public double getTotal()
{
return this.total;
}
public void setCount(final int count)
{
this.count = count;
}
public void setMax(final double max)
{
this.max = max;
}
public void setMin(final double min)
{
this.min = min;
}
public void setTotal(final double total)
{
this.total = total;
}
}

54
src/org/april/agirstatool/core/pages/CreatedClosedCountChartView.java

@ -20,8 +20,6 @@ package org.april.agirstatool.core.pages;
import java.io.IOException;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Locale;
import org.apache.commons.codec.digest.DigestUtils;
import org.april.agirstatool.charts.DateCountList;
@ -70,7 +68,7 @@ public class CreatedClosedCountChartView
code = code.replaceAll("myChart", "myChart_" + DigestUtils.md5Hex(title + "lineChart"));
StringList labels = buildWeekLabels(start, end);
StringList labels = AgirStatoolUtils.buildWeekLabels(start, end);
code = code.replaceAll("labels: \\[.*\\]", "labels: " + AgirStatoolUtils.toJSonStrings(labels));
DateCountList dates = project.issueStats().getWeekCreatedIssueCounts();
@ -123,7 +121,7 @@ public class CreatedClosedCountChartView
code = code.replaceAll("myChart", "myChart_" + DigestUtils.md5Hex(title + "lineBar"));
StringList labels = buildWeekLabels(project.issueStats().getFirstCreate().toLocalDate());
StringList labels = AgirStatoolUtils.buildWeekLabels(project.issueStats().getFirstCreate().toLocalDate());
code = code.replaceAll("labels: \\[.*\\]", "labels: " + AgirStatoolUtils.toJSonStrings(labels));
StringList values = project.issueStats().getWeekCreatedIssueCounts().toValueList();
@ -192,54 +190,6 @@ public class CreatedClosedCountChartView
return result;
}
/**
* Builds the week labels.
*
* @param start
* the start
* @return the string list
*/
private static StringList buildWeekLabels(final LocalDate start)
{
StringList result;
result = buildWeekLabels(start, LocalDate.now());
//
return result;
}
/**
* Builds the week labels.
*
* @param start
* the start
* @param end
* the end
* @return the string list
*/
private static StringList buildWeekLabels(final LocalDate start, final LocalDate end)
{
StringList result;
result = new StringList();
if (start != null)
{
LocalDate normalizedEnd = AgirStatoolUtils.normaliseWeekDate(end);
LocalDate date = AgirStatoolUtils.normaliseWeekDate(start);
while (date.isBefore(normalizedEnd) || date.isEqual(normalizedEnd))
{
String label = date.format(DateTimeFormatter.ofPattern("yyyy-MMM", Locale.FRANCE));
result.add(label);
date = date.plusWeeks(1);
}
}
//
return result;
}
/**
* Builds the year.
*

52
src/org/april/agirstatool/core/pages/CreatedClosedDiffChartView.java

@ -20,8 +20,6 @@ package org.april.agirstatool.core.pages;
import java.io.IOException;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Locale;
import org.apache.commons.codec.digest.DigestUtils;
import org.april.agirstatool.charts.DateCount;
@ -71,7 +69,7 @@ public class CreatedClosedDiffChartView
code = code.replaceAll("myChart", "myChart_" + DigestUtils.md5Hex(title + "line2Chart"));
StringList labels = buildWeekLabels(start, end);
StringList labels = AgirStatoolUtils.buildWeekLabels(start, end);
code = code.replaceAll("labels: \\[.*\\]", "labels: " + AgirStatoolUtils.toJSonStrings(labels));
DateCountList createdDates = project.issueStats().getWeekCreatedIssueCounts();
@ -177,54 +175,6 @@ public class CreatedClosedDiffChartView
return result;
}
/**
* Builds the week labels.
*
* @param start
* the start
* @return the string list
*/
private static StringList buildWeekLabels(final LocalDate start)
{
StringList result;
result = buildWeekLabels(start, LocalDate.now());
//
return result;
}
/**
* Builds the week labels.
*
* @param start
* the start
* @param end
* the end
* @return the string list
*/
private static StringList buildWeekLabels(final LocalDate start, final LocalDate end)
{
StringList result;
result = new StringList();
if (start != null)
{
LocalDate normalizedEnd = AgirStatoolUtils.normaliseWeekDate(end);
LocalDate date = AgirStatoolUtils.normaliseWeekDate(start);
while (date.isBefore(normalizedEnd) || date.isEqual(normalizedEnd))
{
String label = date.format(DateTimeFormatter.ofPattern("yyyy-MMM", Locale.FRANCE));
result.add(label);
date = date.plusWeeks(1);
}
}
//
return result;
}
/**
* Builds the year.
*

198
src/org/april/agirstatool/core/pages/IssueAgeChartView.java

@ -0,0 +1,198 @@
/*
* Copyright (C) 2020 Christian Pierre MOMON <christian.momon@devinsy.fr>
*
* This file is part of AgirStatool, simple key value database.
*
* AgirStatool is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* AgirStatool is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with AgirStatool. If not, see <http://www.gnu.org/licenses/>.
*/
package org.april.agirstatool.core.pages;
import java.io.IOException;
import java.time.LocalDate;
import org.apache.commons.codec.digest.DigestUtils;
import org.april.agirstatool.core.AgirStatool;
import org.april.agirstatool.core.AgirStatoolException;
import org.april.agirstatool.core.AgirStatoolUtils;
import org.april.agirstatool.core.Project;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import fr.devinsy.strings.StringList;
import fr.devinsy.xidyn.utils.XidynUtils;
/**
* The Class projectsRawPageBuilder.
*/
public class IssueAgeChartView
{
private static Logger logger = LoggerFactory.getLogger(IssueAgeChartView.class);
/**
* Builds the.
*
* @param title
* the title
* @param project
* the project
* @param start
* the start
* @param end
* the end
* @return the string
* @throws AgirStatoolException
* the agir statool exception
*/
public static String build(final String title, final Project project, final LocalDate start, final LocalDate end) throws AgirStatoolException
{
String result;
try
{
if (project.hasIssue())
{
String source = XidynUtils.load(AgirStatool.class.getResource("/org/april/agirstatool/core/pages/issueAgeChartView.xhtml"));
String code = XidynUtils.extractBodyContent(source);
code = code.replaceAll("myChart", "myChart_" + DigestUtils.md5Hex(title + "ageStatsChart"));
StringList labels = AgirStatoolUtils.buildWeekLabels(start, end);
code = code.replaceAll("labels: \\[.*\\]", "labels: " + AgirStatoolUtils.toJSonStrings(labels));
StringList values = AgirStatoolUtils.buildWeekMinAges(project, start, end);
code = code.replaceAll("data: \\[.*\\]", "data: " + AgirStatoolUtils.toJSonNumbers(values));
values = AgirStatoolUtils.buildWeekMeanAges(project, start, end);
code = code.replaceAll("data : \\[.*\\]", "data: " + AgirStatoolUtils.toJSonNumbers(values));
values = AgirStatoolUtils.buildWeekMaxAges(project, start, end);
code = code.replaceAll("data : \\[.*\\]", "data: " + AgirStatoolUtils.toJSonNumbers(values));
result = code.toString();
}
else
{
result = "No issue.";
}
}
catch (IOException exception)
{
throw new AgirStatoolException("Error building ProjectsRaw view: " + exception.getMessage(), exception);
}
//
return result;
}
/**
* Builds the full.
*
* @param title
* the title
* @param project
* the project
* @return the string
* @throws AgirStatoolException
* the agir statool exception
*/
public static String buildFull(final String title, final Project project) throws AgirStatoolException
{
String result;
logger.debug("Building created/concluded chart view…");
if (project.hasIssue())
{
result = build(title, project, project.issueStats().getFirstCreate().toLocalDate(), LocalDate.now());
}
else
{
result = "No issue.";
}
//
return result;
}
/**
* Builds the last months.
*
* @param title
* the title
* @param project
* the project
* @param monthCount
* the month count
* @return the string
* @throws AgirStatoolException
* the agir statool exception
*/
public static String buildLastMonths(final String title, final Project project, final int monthCount) throws AgirStatoolException
{
String result;
result = build(title, project, LocalDate.now().minusMonths(monthCount), LocalDate.now());
//
return result;
}
/**
* Builds the previous year.
*
* @param title
* the title