Recode 'Task Completation Lead/Lag' using jqPlot
This commit is contained in:
parent
3d8b044b17
commit
8b86147642
4 changed files with 529 additions and 250 deletions
|
|
@ -33,6 +33,7 @@ import org.apache.commons.lang.StringUtils;
|
|||
import org.libreplan.business.orders.entities.Order;
|
||||
import org.libreplan.business.planner.entities.TaskStatusEnum;
|
||||
import org.libreplan.web.common.Util;
|
||||
import org.libreplan.web.dashboard.DashboardModel.Interval;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
|
@ -43,7 +44,6 @@ import org.zkoss.zul.Grid;
|
|||
import org.zkoss.zul.Label;
|
||||
import org.zkoss.zul.Window;
|
||||
|
||||
|
||||
/**
|
||||
* @author Nacho Barrientos <nacho@igalia.com>
|
||||
* @author Diego Pino García <dpino@igalia.com>
|
||||
|
|
@ -59,7 +59,7 @@ public class DashboardController extends GenericForwardComposer {
|
|||
private Window dashboardWindow;
|
||||
|
||||
private Grid gridTasksSummary;
|
||||
|
||||
|
||||
private Div projectDashboardChartsDiv;
|
||||
private Div projectDashboardNoTasksWarningDiv;
|
||||
|
||||
|
|
@ -69,246 +69,321 @@ public class DashboardController extends GenericForwardComposer {
|
|||
@Override
|
||||
public void doAfterCompose(org.zkoss.zk.ui.Component comp) throws Exception {
|
||||
super.doAfterCompose(comp);
|
||||
this.dashboardWindow = (Window)comp;
|
||||
this.dashboardWindow = (Window) comp;
|
||||
self.setAttribute("controller", this);
|
||||
Util.createBindingsFor(this.dashboardWindow);
|
||||
}
|
||||
|
||||
public void setCurrentOrder(Order order) {
|
||||
dashboardModel.setCurrentOrder(order);
|
||||
if (dashboardModel.tasksAvailable()) {
|
||||
showCharts();
|
||||
} else {
|
||||
hideCharts();
|
||||
}
|
||||
if (this.dashboardWindow != null) {
|
||||
renderGlobalProgress();
|
||||
renderTaskStatus();
|
||||
renderTasksSummary();
|
||||
}
|
||||
}
|
||||
|
||||
private void renderTasksSummary() {
|
||||
Map<TaskStatusEnum, Integer> taskStatus = dashboardModel.calculateTaskStatus();
|
||||
|
||||
taskStatus("lblTasksFinished", taskStatus.get(TaskStatusEnum.FINISHED));
|
||||
taskStatus("lblTasksBlocked", taskStatus.get(TaskStatusEnum.BLOCKED));
|
||||
taskStatus("lblTasksInProgress", taskStatus.get(TaskStatusEnum.IN_PROGRESS));
|
||||
taskStatus("lblTasksReadyToStart", taskStatus.get(TaskStatusEnum.READY_TO_START));
|
||||
}
|
||||
|
||||
private void taskStatus(String key, Integer value) {
|
||||
Label label = (Label) gridTasksSummary.getFellowIfAny(key);
|
||||
if (label != null) {
|
||||
label.setValue(String.format(_("%d tasks"), value));
|
||||
}
|
||||
}
|
||||
|
||||
private void renderTaskStatus() {
|
||||
TaskStatus taskStatus = TaskStatus.create();
|
||||
taskStatus.data(_("Finished"),
|
||||
dashboardModel.getPercentageOfFinishedTasks());
|
||||
taskStatus.data(_("In progress"),
|
||||
dashboardModel.getPercentageOfInProgressTasks());
|
||||
taskStatus.data(_("Ready to start"),
|
||||
dashboardModel.getPercentageOfReadyToStartTasks());
|
||||
taskStatus.data(_("Blocked"),
|
||||
dashboardModel.getPercentageOfBlockedTasks());
|
||||
taskStatus.render();
|
||||
}
|
||||
public void setCurrentOrder(Order order) {
|
||||
dashboardModel.setCurrentOrder(order);
|
||||
if (dashboardModel.tasksAvailable()) {
|
||||
showCharts();
|
||||
} else {
|
||||
hideCharts();
|
||||
}
|
||||
if (this.dashboardWindow != null) {
|
||||
renderGlobalProgress();
|
||||
renderTaskStatus();
|
||||
renderTaskCompletationLag();
|
||||
renderTasksSummary();
|
||||
}
|
||||
}
|
||||
|
||||
private void renderGlobalProgress() {
|
||||
GlobalProgress globalProgress = GlobalProgress.create();
|
||||
private void renderTaskCompletationLag() {
|
||||
Map<Interval, Integer> taskCompletationData = dashboardModel
|
||||
.calculateTaskCompletation();
|
||||
TaskCompletationLag taskCompletation = TaskCompletationLag.create();
|
||||
for (Interval each : taskCompletationData.keySet()) {
|
||||
Integer value = taskCompletationData.get(each);
|
||||
taskCompletation.data(each.toString(), value);
|
||||
}
|
||||
taskCompletation.render();
|
||||
}
|
||||
|
||||
private void renderTasksSummary() {
|
||||
Map<TaskStatusEnum, Integer> taskStatus = dashboardModel
|
||||
.calculateTaskStatus();
|
||||
|
||||
taskStatus("lblTasksFinished", taskStatus.get(TaskStatusEnum.FINISHED));
|
||||
taskStatus("lblTasksBlocked", taskStatus.get(TaskStatusEnum.BLOCKED));
|
||||
taskStatus("lblTasksInProgress",
|
||||
taskStatus.get(TaskStatusEnum.IN_PROGRESS));
|
||||
taskStatus("lblTasksReadyToStart",
|
||||
taskStatus.get(TaskStatusEnum.READY_TO_START));
|
||||
}
|
||||
|
||||
private void taskStatus(String key, Integer value) {
|
||||
Label label = (Label) gridTasksSummary.getFellowIfAny(key);
|
||||
if (label != null) {
|
||||
label.setValue(String.format(_("%d tasks"), value));
|
||||
}
|
||||
}
|
||||
|
||||
private void renderTaskStatus() {
|
||||
TaskStatus taskStatus = TaskStatus.create();
|
||||
taskStatus.data(_("Finished"),
|
||||
dashboardModel.getPercentageOfFinishedTasks());
|
||||
taskStatus.data(_("In progress"),
|
||||
dashboardModel.getPercentageOfInProgressTasks());
|
||||
taskStatus.data(_("Ready to start"),
|
||||
dashboardModel.getPercentageOfReadyToStartTasks());
|
||||
taskStatus.data(_("Blocked"),
|
||||
dashboardModel.getPercentageOfBlockedTasks());
|
||||
taskStatus.render();
|
||||
}
|
||||
|
||||
private void renderGlobalProgress() {
|
||||
GlobalProgress globalProgress = GlobalProgress.create();
|
||||
|
||||
// Current values
|
||||
globalProgress.current(GlobalProgress.CRITICAL_PATH_DURATION,
|
||||
dashboardModel.getCriticalPathProgressByDuration());
|
||||
globalProgress.current(GlobalProgress.CRITICAL_PATH_HOURS,
|
||||
dashboardModel.getCriticalPathProgressByNumHours());
|
||||
globalProgress.current(GlobalProgress.ALL_TASKS_HOURS,
|
||||
dashboardModel.getAdvancePercentageByHours());
|
||||
// Expected values
|
||||
globalProgress.expected(GlobalProgress.CRITICAL_PATH_DURATION,
|
||||
dashboardModel.getExpectedCriticalPathProgressByDuration());
|
||||
globalProgress.expected(GlobalProgress.CRITICAL_PATH_HOURS,
|
||||
dashboardModel.getExpectedCriticalPathProgressByNumHours());
|
||||
globalProgress.expected(GlobalProgress.ALL_TASKS_HOURS,
|
||||
dashboardModel.getExpectedAdvancePercentageByHours());
|
||||
|
||||
globalProgress.render();
|
||||
}
|
||||
|
||||
private void showCharts() {
|
||||
projectDashboardChartsDiv.setVisible(true);
|
||||
projectDashboardNoTasksWarningDiv.setVisible(false);
|
||||
}
|
||||
|
||||
// Current values
|
||||
globalProgress.current(GlobalProgress.CRITICAL_PATH_DURATION,
|
||||
dashboardModel.getCriticalPathProgressByDuration());
|
||||
globalProgress.current(GlobalProgress.CRITICAL_PATH_HOURS,
|
||||
dashboardModel.getCriticalPathProgressByNumHours());
|
||||
globalProgress.current(GlobalProgress.ALL_TASKS_HOURS,
|
||||
dashboardModel.getAdvancePercentageByHours());
|
||||
// Expected values
|
||||
globalProgress.expected(GlobalProgress.CRITICAL_PATH_DURATION,
|
||||
dashboardModel.getExpectedCriticalPathProgressByDuration());
|
||||
globalProgress.expected(GlobalProgress.CRITICAL_PATH_HOURS,
|
||||
dashboardModel.getExpectedCriticalPathProgressByNumHours());
|
||||
globalProgress.expected(GlobalProgress.ALL_TASKS_HOURS,
|
||||
dashboardModel.getExpectedAdvancePercentageByHours());
|
||||
|
||||
globalProgress.render();
|
||||
}
|
||||
|
||||
private void showCharts() {
|
||||
projectDashboardChartsDiv.setVisible(true);
|
||||
projectDashboardNoTasksWarningDiv.setVisible(false);
|
||||
}
|
||||
|
||||
private void hideCharts() {
|
||||
projectDashboardChartsDiv.setVisible(false);
|
||||
projectDashboardNoTasksWarningDiv.setVisible(true);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Diego Pino García <dpino@igalia.com>
|
||||
*
|
||||
*
|
||||
*/
|
||||
static class GlobalProgress {
|
||||
static class GlobalProgress {
|
||||
|
||||
public static final String ALL_TASKS_HOURS = _("All tasks (hours)");
|
||||
public static final String ALL_TASKS_HOURS = _("All tasks (hours)");
|
||||
|
||||
public static final String CRITICAL_PATH_HOURS = _("Critical path (hours)");
|
||||
public static final String CRITICAL_PATH_HOURS = _("Critical path (hours)");
|
||||
|
||||
public static final String CRITICAL_PATH_DURATION = _("Critical path (duration)");
|
||||
public static final String CRITICAL_PATH_DURATION = _("Critical path (duration)");
|
||||
|
||||
private Map<String, BigDecimal> current = new LinkedHashMap<String, BigDecimal>();
|
||||
private final Map<String, BigDecimal> current = new LinkedHashMap<String, BigDecimal>();
|
||||
|
||||
private Map<String, BigDecimal> expected = new LinkedHashMap<String, BigDecimal>();
|
||||
private final Map<String, BigDecimal> expected = new LinkedHashMap<String, BigDecimal>();
|
||||
|
||||
private static List<Series> series = new ArrayList<Series>() {
|
||||
{
|
||||
add(Series.create(_("Current"), "blue"));
|
||||
add(Series.create(_("Expected"), "red"));
|
||||
}
|
||||
};
|
||||
private static List<Series> series = new ArrayList<Series>() {
|
||||
{
|
||||
add(Series.create(_("Current"), "#33c"));
|
||||
add(Series.create(_("Expected"), "#c33"));
|
||||
}
|
||||
};
|
||||
|
||||
private GlobalProgress() {
|
||||
private GlobalProgress() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public void current(String key, BigDecimal value) {
|
||||
current.put(key, value);
|
||||
}
|
||||
public void current(String key, BigDecimal value) {
|
||||
current.put(key, value);
|
||||
}
|
||||
|
||||
public void expected(String key, BigDecimal value) {
|
||||
expected.put(key, value);
|
||||
}
|
||||
public void expected(String key, BigDecimal value) {
|
||||
expected.put(key, value);
|
||||
}
|
||||
|
||||
public static GlobalProgress create() {
|
||||
return new GlobalProgress();
|
||||
}
|
||||
public static GlobalProgress create() {
|
||||
return new GlobalProgress();
|
||||
}
|
||||
|
||||
public String getPercentages() {
|
||||
return String.format("'[%s, %s]'",
|
||||
jsonifyPercentages(current.values()),
|
||||
jsonifyPercentages(expected.values()));
|
||||
}
|
||||
public String getPercentages() {
|
||||
return String.format("'[%s, %s]'",
|
||||
jsonifyPercentages(current.values()),
|
||||
jsonifyPercentages(expected.values()));
|
||||
}
|
||||
|
||||
private String jsonifyPercentages(Collection<BigDecimal> array) {
|
||||
List<String> result = new ArrayList<String>();
|
||||
private String jsonifyPercentages(Collection<BigDecimal> array) {
|
||||
List<String> result = new ArrayList<String>();
|
||||
|
||||
int i = 1;
|
||||
for (BigDecimal each : array) {
|
||||
result.add(String.format("[%.2f, %d]", each.doubleValue(), i++));
|
||||
}
|
||||
return String.format("[%s]", StringUtils.join(result, ","));
|
||||
}
|
||||
int i = 1;
|
||||
for (BigDecimal each : array) {
|
||||
result.add(String.format("[%.2f, %d]", each.doubleValue(), i++));
|
||||
}
|
||||
return String.format("[%s]", StringUtils.join(result, ","));
|
||||
}
|
||||
|
||||
private String jsonify(Collection<?> list) {
|
||||
Collection<String> result = new ArrayList<String>();
|
||||
for (Object each : list) {
|
||||
if (each.getClass() == String.class) {
|
||||
result.add(String.format("\"%s\"", each.toString()));
|
||||
} else {
|
||||
result.add(String.format("%s", each.toString()));
|
||||
}
|
||||
}
|
||||
return String.format("'[%s]'", StringUtils.join(result, ','));
|
||||
}
|
||||
private String jsonify(Collection<?> list) {
|
||||
Collection<String> result = new ArrayList<String>();
|
||||
for (Object each : list) {
|
||||
if (each.getClass() == String.class) {
|
||||
result.add(String.format("\"%s\"", each.toString()));
|
||||
} else {
|
||||
result.add(String.format("%s", each.toString()));
|
||||
}
|
||||
}
|
||||
return String.format("'[%s]'", StringUtils.join(result, ','));
|
||||
}
|
||||
|
||||
public String getSeries() {
|
||||
return jsonify(series);
|
||||
}
|
||||
public String getSeries() {
|
||||
return jsonify(series);
|
||||
}
|
||||
|
||||
/**
|
||||
* The order of the ticks is taken from the keys in current
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String getTicks() {
|
||||
return jsonify(current.keySet());
|
||||
}
|
||||
/**
|
||||
* The order of the ticks is taken from the keys in current
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String getTicks() {
|
||||
return jsonify(current.keySet());
|
||||
}
|
||||
|
||||
public void render() {
|
||||
String command = String.format(
|
||||
"global_progress.render(%s, %s, %s);", getPercentages(),
|
||||
getTicks(), getSeries());
|
||||
Clients.evalJavaScript(command);
|
||||
}
|
||||
public void render() {
|
||||
String command = String.format(
|
||||
"global_progress.render(%s, %s, %s);", getPercentages(),
|
||||
getTicks(), getSeries());
|
||||
Clients.evalJavaScript(command);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Diego Pino García <dpino@igalia.com>
|
||||
*
|
||||
*
|
||||
*/
|
||||
static class Series {
|
||||
|
||||
private String label;
|
||||
|
||||
private String color;
|
||||
|
||||
private Series() {
|
||||
|
||||
}
|
||||
|
||||
public static Series create(String label) {
|
||||
Series series = new Series();
|
||||
series.label = label;
|
||||
return series;
|
||||
}
|
||||
|
||||
public static Series create(String label, String color) {
|
||||
Series series = new Series();
|
||||
series.label = label;
|
||||
series.color = color;
|
||||
return series;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return String.format("{\"label\": \"%s\", \"color\": \"%s\"}", label, color);
|
||||
}
|
||||
|
||||
private String label;
|
||||
|
||||
private String color;
|
||||
|
||||
private Series() {
|
||||
|
||||
}
|
||||
|
||||
public static Series create(String label) {
|
||||
Series series = new Series();
|
||||
series.label = label;
|
||||
return series;
|
||||
}
|
||||
|
||||
public static Series create(String label, String color) {
|
||||
Series series = new Series();
|
||||
series.label = label;
|
||||
series.color = color;
|
||||
return series;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("{\"label\": \"%s\", \"color\": \"%s\"}",
|
||||
label, color);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Diego Pino García <dpino@igalia.com>
|
||||
*
|
||||
*
|
||||
*/
|
||||
static class TaskStatus {
|
||||
|
||||
private Map<String, BigDecimal> data = new LinkedHashMap<String, BigDecimal>();
|
||||
|
||||
private TaskStatus() {
|
||||
|
||||
}
|
||||
|
||||
public static TaskStatus create() {
|
||||
return new TaskStatus();
|
||||
}
|
||||
|
||||
private String getData() {
|
||||
List<String> result = new ArrayList<String>();
|
||||
|
||||
TreeSet<String> keys = new TreeSet<String>(data.keySet());
|
||||
for (String key : keys) {
|
||||
BigDecimal value = data.get(key);
|
||||
result.add(String.format("[\"%s\", %.2f]", key, value));
|
||||
}
|
||||
return String.format("'[%s]'", StringUtils.join(result, ","));
|
||||
}
|
||||
|
||||
public void data(String key, BigDecimal value) {
|
||||
data.put(key, value);
|
||||
}
|
||||
|
||||
public void render() {
|
||||
String command = String.format("task_status.render(%s);", getData());
|
||||
Clients.evalJavaScript(command);
|
||||
}
|
||||
|
||||
|
||||
private final Map<String, BigDecimal> data = new LinkedHashMap<String, BigDecimal>();
|
||||
|
||||
private TaskStatus() {
|
||||
|
||||
}
|
||||
|
||||
public static TaskStatus create() {
|
||||
return new TaskStatus();
|
||||
}
|
||||
|
||||
private String getData() {
|
||||
List<String> result = new ArrayList<String>();
|
||||
|
||||
TreeSet<String> keys = new TreeSet<String>(data.keySet());
|
||||
for (String key : keys) {
|
||||
BigDecimal value = data.get(key);
|
||||
result.add(String.format("[\"%s\", %.2f]", key, value));
|
||||
}
|
||||
return String.format("'[%s]'", StringUtils.join(result, ","));
|
||||
}
|
||||
|
||||
public void data(String key, BigDecimal value) {
|
||||
data.put(key, value);
|
||||
}
|
||||
|
||||
public void render() {
|
||||
String command = String
|
||||
.format("task_status.render(%s);", getData());
|
||||
Clients.evalJavaScript(command);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
static class TaskCompletationLag {
|
||||
|
||||
private final String id = "task_completation_lag";
|
||||
|
||||
private final Map<String, Integer> data = new LinkedHashMap<String, Integer>();
|
||||
|
||||
private TaskCompletationLag() {
|
||||
|
||||
}
|
||||
|
||||
public static TaskCompletationLag create() {
|
||||
return new TaskCompletationLag();
|
||||
}
|
||||
|
||||
public void data(String interval, Integer value) {
|
||||
data.put(interval, value);
|
||||
}
|
||||
|
||||
public void render() {
|
||||
String _data = JSONHelper.values(data);
|
||||
String ticks = JSONHelper.keys(data);
|
||||
String command = String.format("%s.render(%s, %s);", id, _data,
|
||||
ticks);
|
||||
Clients.evalJavaScript(command);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class JSONHelper {
|
||||
|
||||
public static String format(Map<String, Integer> data) {
|
||||
List<String> result = new ArrayList<String>();
|
||||
for (String key : data.keySet()) {
|
||||
Integer value = data.get(data);
|
||||
result.add(String.format("[\"%s\", %d]", key, value));
|
||||
}
|
||||
return String.format("'[%s]'", StringUtils.join(result, ','));
|
||||
}
|
||||
|
||||
public static String keys(Map<String, ?> map) {
|
||||
List<String> result = new ArrayList<String>();
|
||||
for (String each : map.keySet()) {
|
||||
result.add(String.format("\"%s\"", each));
|
||||
}
|
||||
return String.format("'[%s]'", StringUtils.join(result, ','));
|
||||
}
|
||||
|
||||
public static String values(Map<?, Integer> map) {
|
||||
List<String> result = new ArrayList<String>();
|
||||
for (Integer each : map.values()) {
|
||||
result.add(each.toString());
|
||||
}
|
||||
return String.format("'[%s]'", StringUtils.join(result, ','));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -22,10 +22,13 @@ package org.libreplan.web.dashboard;
|
|||
import java.math.BigDecimal;
|
||||
import java.math.MathContext;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.joda.time.Days;
|
||||
import org.joda.time.LocalDate;
|
||||
|
|
@ -45,7 +48,7 @@ import org.springframework.stereotype.Component;
|
|||
|
||||
/**
|
||||
* Model for UI operations related to Order Dashboard View
|
||||
*
|
||||
*
|
||||
* @author Nacho Barrientos <nacho@igalia.com>
|
||||
* @author Lorenzo Tilve Álvaro <ltilve@igalia.com>
|
||||
*/
|
||||
|
|
@ -64,12 +67,11 @@ public class DashboardModel implements IDashboardModel {
|
|||
public static double LTC_STRETCHES_MIN_VALUE = 0;
|
||||
public static double LTC_STRETCHES_MAX_VALUE = 0;
|
||||
|
||||
|
||||
private Order currentOrder;
|
||||
private Integer taskCount = null;
|
||||
|
||||
private Map<TaskStatusEnum, BigDecimal> taskStatusStats;
|
||||
private Map<TaskDeadlineViolationStatusEnum, BigDecimal> taskDeadlineViolationStatusStats;
|
||||
private final Map<TaskStatusEnum, BigDecimal> taskStatusStats;
|
||||
private final Map<TaskDeadlineViolationStatusEnum, BigDecimal> taskDeadlineViolationStatusStats;
|
||||
private List<Double> taskEstimationAccuracyHistogram;
|
||||
private BigDecimal marginWithDeadLine;
|
||||
private List<Double> lagInTaskCompletionHistogram;
|
||||
|
|
@ -81,10 +83,11 @@ public class DashboardModel implements IDashboardModel {
|
|||
TaskDeadlineViolationStatusEnum.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCurrentOrder(Order order) {
|
||||
this.currentOrder = order;
|
||||
this.taskCount = null;
|
||||
if(tasksAvailable()) {
|
||||
if (tasksAvailable()) {
|
||||
this.calculateTaskStatusStatistics();
|
||||
this.calculateTaskViolationStatusStatistics();
|
||||
this.calculateMarginWithDeadLine();
|
||||
|
|
@ -94,38 +97,49 @@ public class DashboardModel implements IDashboardModel {
|
|||
}
|
||||
|
||||
/* Progress KPI: "Number of tasks by status" */
|
||||
@Override
|
||||
public BigDecimal getPercentageOfFinishedTasks() {
|
||||
return taskStatusStats.get(TaskStatusEnum.FINISHED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigDecimal getPercentageOfInProgressTasks() {
|
||||
return taskStatusStats.get(TaskStatusEnum.IN_PROGRESS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigDecimal getPercentageOfReadyToStartTasks() {
|
||||
return taskStatusStats.get(TaskStatusEnum.READY_TO_START);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigDecimal getPercentageOfBlockedTasks() {
|
||||
return taskStatusStats.get(TaskStatusEnum.BLOCKED);
|
||||
}
|
||||
|
||||
/* Progress KPI: "Deadline violation" */
|
||||
@Override
|
||||
public BigDecimal getPercentageOfOnScheduleTasks() {
|
||||
return taskDeadlineViolationStatusStats.get(TaskDeadlineViolationStatusEnum.ON_SCHEDULE);
|
||||
return taskDeadlineViolationStatusStats
|
||||
.get(TaskDeadlineViolationStatusEnum.ON_SCHEDULE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigDecimal getPercentageOfTasksWithViolatedDeadline() {
|
||||
return taskDeadlineViolationStatusStats.get(TaskDeadlineViolationStatusEnum.DEADLINE_VIOLATED);
|
||||
return taskDeadlineViolationStatusStats
|
||||
.get(TaskDeadlineViolationStatusEnum.DEADLINE_VIOLATED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigDecimal getPercentageOfTasksWithNoDeadline() {
|
||||
return taskDeadlineViolationStatusStats.get(TaskDeadlineViolationStatusEnum.NO_DEADLINE);
|
||||
return taskDeadlineViolationStatusStats
|
||||
.get(TaskDeadlineViolationStatusEnum.NO_DEADLINE);
|
||||
}
|
||||
|
||||
/* Progress KPI: "Global Progress of the Project" */
|
||||
public BigDecimal getAdvancePercentageByHours(){
|
||||
TaskGroup rootAsTaskGroup = (TaskGroup)getRootTask();
|
||||
@Override
|
||||
public BigDecimal getAdvancePercentageByHours() {
|
||||
TaskGroup rootAsTaskGroup = (TaskGroup) getRootTask();
|
||||
if (this.getRootTask() == null) {
|
||||
throw new RuntimeException("Root task is null");
|
||||
}
|
||||
|
|
@ -133,17 +147,20 @@ public class DashboardModel implements IDashboardModel {
|
|||
return ratio.multiply(BigDecimal.TEN).multiply(BigDecimal.TEN);
|
||||
}
|
||||
|
||||
public BigDecimal getExpectedAdvancePercentageByHours(){
|
||||
TaskGroup rootAsTaskGroup = (TaskGroup)getRootTask();
|
||||
@Override
|
||||
public BigDecimal getExpectedAdvancePercentageByHours() {
|
||||
TaskGroup rootAsTaskGroup = (TaskGroup) getRootTask();
|
||||
if (this.getRootTask() == null) {
|
||||
throw new RuntimeException("Root task is null");
|
||||
}
|
||||
BigDecimal ratio = rootAsTaskGroup.getTheoreticalProgressByNumHoursForAllTasksUntilNow();
|
||||
BigDecimal ratio = rootAsTaskGroup
|
||||
.getTheoreticalProgressByNumHoursForAllTasksUntilNow();
|
||||
return ratio.multiply(BigDecimal.TEN).multiply(BigDecimal.TEN);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigDecimal getCriticalPathProgressByNumHours() {
|
||||
TaskGroup rootAsTaskGroup = (TaskGroup)getRootTask();
|
||||
TaskGroup rootAsTaskGroup = (TaskGroup) getRootTask();
|
||||
if (this.getRootTask() == null) {
|
||||
throw new RuntimeException("Root task is null");
|
||||
}
|
||||
|
|
@ -151,17 +168,20 @@ public class DashboardModel implements IDashboardModel {
|
|||
return ratio.multiply(BigDecimal.TEN).multiply(BigDecimal.TEN);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigDecimal getExpectedCriticalPathProgressByNumHours() {
|
||||
TaskGroup rootAsTaskGroup = (TaskGroup)getRootTask();
|
||||
TaskGroup rootAsTaskGroup = (TaskGroup) getRootTask();
|
||||
if (this.getRootTask() == null) {
|
||||
throw new RuntimeException("Root task is null");
|
||||
}
|
||||
BigDecimal ratio = rootAsTaskGroup.getTheoreticalProgressByNumHoursForCriticalPathUntilNow();
|
||||
BigDecimal ratio = rootAsTaskGroup
|
||||
.getTheoreticalProgressByNumHoursForCriticalPathUntilNow();
|
||||
return ratio.multiply(BigDecimal.TEN).multiply(BigDecimal.TEN);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigDecimal getCriticalPathProgressByDuration() {
|
||||
TaskGroup rootAsTaskGroup = (TaskGroup)getRootTask();
|
||||
TaskGroup rootAsTaskGroup = (TaskGroup) getRootTask();
|
||||
if (this.getRootTask() == null) {
|
||||
throw new RuntimeException("Root task is null");
|
||||
}
|
||||
|
|
@ -169,16 +189,19 @@ public class DashboardModel implements IDashboardModel {
|
|||
return ratio.multiply(BigDecimal.TEN).multiply(BigDecimal.TEN);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigDecimal getExpectedCriticalPathProgressByDuration() {
|
||||
TaskGroup rootAsTaskGroup = (TaskGroup)getRootTask();
|
||||
TaskGroup rootAsTaskGroup = (TaskGroup) getRootTask();
|
||||
if (this.getRootTask() == null) {
|
||||
throw new RuntimeException("Root task is null");
|
||||
}
|
||||
BigDecimal ratio = rootAsTaskGroup.getTheoreticalProgressByDurationForCriticalPathUntilNow();
|
||||
BigDecimal ratio = rootAsTaskGroup
|
||||
.getTheoreticalProgressByDurationForCriticalPathUntilNow();
|
||||
return ratio.multiply(BigDecimal.TEN).multiply(BigDecimal.TEN);
|
||||
}
|
||||
|
||||
/* Time KPI: Margin with deadline */
|
||||
@Override
|
||||
public BigDecimal getMarginWithDeadLine() {
|
||||
return this.marginWithDeadLine;
|
||||
}
|
||||
|
|
@ -208,6 +231,7 @@ public class DashboardModel implements IDashboardModel {
|
|||
}
|
||||
|
||||
/* Time KPI: Estimation accuracy */
|
||||
@Override
|
||||
public List<Double> getFinishedTasksEstimationAccuracyHistogram() {
|
||||
return this.taskEstimationAccuracyHistogram;
|
||||
}
|
||||
|
|
@ -216,21 +240,19 @@ public class DashboardModel implements IDashboardModel {
|
|||
if (this.getRootTask() == null) {
|
||||
throw new RuntimeException("Root task is null");
|
||||
}
|
||||
CalculateFinishedTasksEstimationDeviationVisitor visitor =
|
||||
new CalculateFinishedTasksEstimationDeviationVisitor();
|
||||
CalculateFinishedTasksEstimationDeviationVisitor visitor = new CalculateFinishedTasksEstimationDeviationVisitor();
|
||||
TaskElement rootTask = getRootTask();
|
||||
rootTask.acceptVisitor(visitor);
|
||||
List<Double> deviations = visitor.getDeviations();
|
||||
|
||||
// [-100, -90), [-90, -80), ..., [190, 200), [200, inf)
|
||||
this.taskEstimationAccuracyHistogram = createHistogram(
|
||||
EA_STRETCHES_MIN_VALUE,
|
||||
EA_STRETCHES_MAX_VALUE,
|
||||
EA_STRETCHES_PERCENTAGE_STEP,
|
||||
deviations);
|
||||
EA_STRETCHES_MIN_VALUE, EA_STRETCHES_MAX_VALUE,
|
||||
EA_STRETCHES_PERCENTAGE_STEP, deviations);
|
||||
}
|
||||
|
||||
/* Time KPI: Lead/Lag in task completion */
|
||||
@Override
|
||||
public List<Double> getLagInTaskCompletionHistogram() {
|
||||
return this.lagInTaskCompletionHistogram;
|
||||
}
|
||||
|
|
@ -239,8 +261,7 @@ public class DashboardModel implements IDashboardModel {
|
|||
if (this.getRootTask() == null) {
|
||||
throw new RuntimeException("Root task is null");
|
||||
}
|
||||
CalculateFinishedTasksLagInCompletionVisitor visitor =
|
||||
new CalculateFinishedTasksLagInCompletionVisitor();
|
||||
CalculateFinishedTasksLagInCompletionVisitor visitor = new CalculateFinishedTasksLagInCompletionVisitor();
|
||||
TaskElement rootTask = getRootTask();
|
||||
rootTask.acceptVisitor(visitor);
|
||||
List<Double> deviations = visitor.getDeviations();
|
||||
|
|
@ -253,45 +274,137 @@ public class DashboardModel implements IDashboardModel {
|
|||
LTC_STRETCHES_MAX_VALUE = Collections.max(deviations);
|
||||
}
|
||||
LTC_STRETCHES_STEP = (LTC_STRETCHES_MAX_VALUE - LTC_STRETCHES_MIN_VALUE)
|
||||
/LTC_NUMBER_OF_INTERVALS;
|
||||
/ LTC_NUMBER_OF_INTERVALS;
|
||||
this.lagInTaskCompletionHistogram = createHistogram(
|
||||
LTC_STRETCHES_MIN_VALUE,
|
||||
LTC_STRETCHES_MAX_VALUE,
|
||||
LTC_STRETCHES_STEP,
|
||||
deviations);
|
||||
LTC_STRETCHES_MIN_VALUE, LTC_STRETCHES_MAX_VALUE,
|
||||
LTC_STRETCHES_STEP, deviations);
|
||||
}
|
||||
|
||||
private List<Double> createHistogram(double lowBound, double highBound,
|
||||
double intervalStep, List<Double> values) {
|
||||
double variableRange = highBound - lowBound;
|
||||
/* TODO: What if highBound == lowBound? */
|
||||
int numberOfClasses = (int)(variableRange/intervalStep);
|
||||
int[] classes = new int[numberOfClasses+1];
|
||||
int numberOfClasses = (int) (variableRange / intervalStep);
|
||||
int[] classes = new int[numberOfClasses + 1];
|
||||
|
||||
for(Double value: values) {
|
||||
for (Double value : values) {
|
||||
int index;
|
||||
if (value >= highBound) {
|
||||
index = numberOfClasses;
|
||||
} else {
|
||||
index = (int)(numberOfClasses *
|
||||
(((value.doubleValue() - lowBound))/variableRange));
|
||||
index = (int) (numberOfClasses * (((value.doubleValue() - lowBound)) / variableRange));
|
||||
}
|
||||
classes[index]++;
|
||||
}
|
||||
|
||||
List<Double> histogram = new ArrayList<Double>();
|
||||
int numberOfConsideredTasks = values.size();
|
||||
for (int numberOfElementsInClass: classes) {
|
||||
for (int numberOfElementsInClass : classes) {
|
||||
Double relativeCount = new Double(0.0);
|
||||
if (numberOfConsideredTasks > 0) {
|
||||
relativeCount = new Double(1.0*numberOfElementsInClass/
|
||||
numberOfConsideredTasks);
|
||||
relativeCount = new Double(1.0 * numberOfElementsInClass
|
||||
/ numberOfConsideredTasks);
|
||||
}
|
||||
histogram.add(relativeCount);
|
||||
}
|
||||
return histogram;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Map<Interval, Integer> calculateTaskCompletation() {
|
||||
Map<Interval, Integer> result = new LinkedHashMap<Interval, Integer>();
|
||||
final Integer one = Integer.valueOf(1);
|
||||
|
||||
// Get deviations of finished tasks, calculate max, min and delta
|
||||
List<Double> deviations = getDeviations();
|
||||
if (deviations.isEmpty()) {
|
||||
return result;
|
||||
}
|
||||
Double max = Collections.max(deviations);
|
||||
Double min = Collections.min(deviations);
|
||||
double delta = (max - min) / Interval.MAX_INTERVALS;
|
||||
|
||||
// Create MAX_INTERVALS
|
||||
double from = min;
|
||||
for (int i = 0; i < Interval.MAX_INTERVALS; i++) {
|
||||
result.put(Interval.create(from, from + delta), Integer.valueOf(0));
|
||||
from = from + delta;
|
||||
}
|
||||
|
||||
// Construct map with number of tasks for each interval
|
||||
final Set<Interval> intervals = result.keySet();
|
||||
for (Double each : deviations) {
|
||||
Interval interval = Interval.containingValue(intervals, each);
|
||||
if (interval != null) {
|
||||
Integer value = result.get(interval);
|
||||
result.put(interval, value + one);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Diego Pino García<dpino@igalia.com>
|
||||
*
|
||||
*/
|
||||
static class Interval {
|
||||
|
||||
public static final double MAX_INTERVALS = 6;
|
||||
|
||||
private double min;
|
||||
|
||||
private double max;
|
||||
|
||||
private Interval() {
|
||||
|
||||
}
|
||||
|
||||
public static Interval create(double min, double max) {
|
||||
return new Interval(min, max);
|
||||
}
|
||||
|
||||
private Interval(double min, double max) {
|
||||
this.min = min;
|
||||
this.max = max;
|
||||
}
|
||||
|
||||
public static Interval copy(Interval interval) {
|
||||
return new Interval(interval.min, interval.max);
|
||||
}
|
||||
|
||||
public static Interval containingValue(Collection<Interval> intervals,
|
||||
Double value) {
|
||||
for (Interval each : intervals) {
|
||||
if (each.includes(value)) {
|
||||
return each;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean includes(double value) {
|
||||
return (value >= min) && (value <= max);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("[%.2f, %.2f]", min, max);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private List<Double> getDeviations() {
|
||||
if (this.getRootTask() == null) {
|
||||
throw new RuntimeException("Root task is null");
|
||||
}
|
||||
CalculateFinishedTasksEstimationDeviationVisitor visitor = new CalculateFinishedTasksEstimationDeviationVisitor();
|
||||
TaskElement rootTask = getRootTask();
|
||||
rootTask.acceptVisitor(visitor);
|
||||
return visitor.getDeviations();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<TaskStatusEnum, Integer> calculateTaskStatus() {
|
||||
AccumulateTasksStatusVisitor visitor = new AccumulateTasksStatusVisitor();
|
||||
TaskElement rootTask = getRootTask();
|
||||
|
|
@ -322,7 +435,8 @@ public class DashboardModel implements IDashboardModel {
|
|||
throw new RuntimeException("Root task is null");
|
||||
}
|
||||
rootTask.acceptVisitor(visitor);
|
||||
Map<TaskDeadlineViolationStatusEnum, Integer> count = visitor.getTaskDeadlineViolationStatusData();
|
||||
Map<TaskDeadlineViolationStatusEnum, Integer> count = visitor
|
||||
.getTaskDeadlineViolationStatusData();
|
||||
mapAbsoluteValuesToPercentages(count, taskDeadlineViolationStatusStats);
|
||||
}
|
||||
|
||||
|
|
@ -353,8 +467,9 @@ public class DashboardModel implements IDashboardModel {
|
|||
}
|
||||
|
||||
private int countTasksInAResultMap(Map<? extends Object, Integer> map) {
|
||||
/* It's only needed to count the number of tasks once
|
||||
* each time setOrder is called.
|
||||
/*
|
||||
* It's only needed to count the number of tasks once each time setOrder
|
||||
* is called.
|
||||
*/
|
||||
if (this.taskCount != null) {
|
||||
return this.taskCount.intValue();
|
||||
|
|
@ -367,6 +482,7 @@ public class DashboardModel implements IDashboardModel {
|
|||
return sum;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean tasksAvailable() {
|
||||
return getRootTask() != null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import java.util.Map;
|
|||
|
||||
import org.libreplan.business.orders.entities.Order;
|
||||
import org.libreplan.business.planner.entities.TaskStatusEnum;
|
||||
import org.libreplan.web.dashboard.DashboardModel.Interval;
|
||||
|
||||
interface IDashboardModel {
|
||||
|
||||
|
|
@ -69,7 +70,9 @@ interface IDashboardModel {
|
|||
|
||||
/* Time KPI: "Lead/Lag in task completion" */
|
||||
List<Double> getLagInTaskCompletionHistogram();
|
||||
|
||||
|
||||
Map<TaskStatusEnum, Integer> calculateTaskStatus();
|
||||
|
||||
|
||||
Map<Interval, Integer> calculateTaskCompletation();
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,9 +34,11 @@
|
|||
|
||||
<div height="30px" sclass="toolbar-box" />
|
||||
|
||||
<window id="dashboardWindow" apply="${dsController}" vflex="1" style="margin:0;overflow:auto">
|
||||
<window id="dashboardWindow" apply="${dsController}" vflex="1" contentStyle="margin: 0; overflow: auto">
|
||||
|
||||
<div id="projectDashboardChartsDiv" sclass="dashboards-container" height="100%">
|
||||
<div id="projectDashboardChartsDiv" sclass="dashboards-container" height="100%" width="100%">
|
||||
|
||||
<!-- Progress -->
|
||||
<groupbox closable="false">
|
||||
<caption label="${i18n:_('Progress')}" />
|
||||
<hbox>
|
||||
|
|
@ -44,7 +46,7 @@
|
|||
<n:div id="task-status" style="height:200px; width:400px; margin-left: 100px;"></n:div>
|
||||
|
||||
<!-- Tasks summary -->
|
||||
<grid id="gridTasksSummary" style="margin-top: 50px" visible="false">
|
||||
<grid id="gridTasksSummary" style="margin-top: 50px;" width="300px">
|
||||
<auxhead>
|
||||
<auxheader label="${i18n:_('Tasks summary')}" colspan="2"/>
|
||||
</auxhead>
|
||||
|
|
@ -72,7 +74,18 @@
|
|||
</rows>
|
||||
</grid>
|
||||
</hbox>
|
||||
</groupbox>
|
||||
|
||||
<!-- Time -->
|
||||
<groupbox closable="false">
|
||||
<caption label="${i18n:_('Time')}" />
|
||||
<hbox>
|
||||
<n:div id="task-completation-lag" style="height:200px; width:560px;"></n:div>
|
||||
<n:div id="deadline-violation" style="height:200px; width:400px; margin-left: 100px;"></n:div>
|
||||
<n:div id="margin-with-deadline" style="height:200px; width:400px; margin-left: 100px;"></n:div>
|
||||
</hbox>
|
||||
</groupbox>
|
||||
|
||||
</div>
|
||||
|
||||
<div id="projectDashboardNoTasksWarningDiv" visible="false">
|
||||
|
|
@ -87,12 +100,24 @@
|
|||
|
||||
<!-- The variable for containing the 'global progress' has to be global and created before defer -->
|
||||
<script type="text/javascript">
|
||||
var global_progress = {
|
||||
title: 'Titulo',
|
||||
};
|
||||
var global_progress = { };
|
||||
var task_status = { };
|
||||
var task_completation_lag = { };
|
||||
</script>
|
||||
|
||||
<n:style type="text/css">
|
||||
.tooltip {
|
||||
display:none;
|
||||
position:absolute;
|
||||
border:1px solid #333;
|
||||
background-color:#161616;
|
||||
border-radius:5px;
|
||||
padding:10px;
|
||||
color:#fff;
|
||||
font-size:12px Arial;
|
||||
}
|
||||
</n:style>
|
||||
|
||||
<!-- Configure the parameters for the 'global progress' chart. The object contains a method 'render' that
|
||||
is called from the Java file once all objects in the view have been created -->
|
||||
<script type="text/javascript" defer="true">
|
||||
|
|
@ -161,6 +186,66 @@
|
|||
this.plot = jQuery.jqplot('task-status', [jQuery.parseJSON(data)], this);
|
||||
}
|
||||
};
|
||||
|
||||
task_completation_lag = {
|
||||
id: 'task-completation-lag',
|
||||
title: 'Task completation lag',
|
||||
data: [],
|
||||
seriesDefaults:{
|
||||
renderer:$.jqplot.BarRenderer,
|
||||
rendererOptions: {
|
||||
fillToZero: true
|
||||
}
|
||||
},
|
||||
axesDefaults: {
|
||||
tickRenderer: $.jqplot.CanvasAxisTickRenderer ,
|
||||
tickOptions: {
|
||||
angle: -30,
|
||||
fontSize: '10pt'
|
||||
}
|
||||
},
|
||||
axes: {
|
||||
xaxis: {
|
||||
label: 'Number of Days / Days Interval',
|
||||
renderer: $.jqplot.CategoryAxisRenderer,
|
||||
},
|
||||
},
|
||||
render: function(data, conf) {
|
||||
if (conf.ticks !== undefined) {
|
||||
this.axes.xaxis.ticks = jQuery.parseJSON(conf.ticks);
|
||||
}
|
||||
if (conf.titles !== undefined) {
|
||||
this.axes.xaxis.ticks = jQuery.parseJSON(conf.ticks);
|
||||
}
|
||||
|
||||
this.plot = $.jqplot(this.id, [jQuery.parseJSON(data)], this);
|
||||
this.attachTooltip();
|
||||
},
|
||||
attachTooltip: function() {
|
||||
var node = $('#' + this.id);
|
||||
node.bind('jqplotDataHighlight',
|
||||
function (ev, seriesIndex, pointIndex, data) {
|
||||
var x = ev.pageX - node.offset().left;
|
||||
var y = ev.pageY - node.offset().top;
|
||||
|
||||
var tooltip = $('<span class="tooltip"></span>');
|
||||
tooltip.text(data[1]).appendTo(node);
|
||||
tooltip.css({'top': y, 'left': x, 'position': 'absolute'});
|
||||
tooltip.fadeIn('slow');
|
||||
}
|
||||
);
|
||||
node.bind('jqplotDataUnhighlight',
|
||||
function (ev) {
|
||||
$('.tooltip').remove();
|
||||
}
|
||||
);
|
||||
node.mousemove(function(ev) {
|
||||
var x = ev.pageX - node.offset().left;
|
||||
var y = ev.pageY - node.offset().top;
|
||||
$('.tooltip').css({'top': y, 'left': x, 'position': 'absolute'})
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
]]>
|
||||
</script>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue