Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Schch fix completion percentage #1455

Merged
merged 4 commits into from
Aug 31, 2017

Conversation

schch
Copy link
Contributor

@schch schch commented Aug 24, 2017

Hi Dmitry,

it looks like the calculation of the completion percentage is inconsistent.

When the depth of a task hierarchy is greater than 1 and there are gaps between tasks the percentage of the tasks at least two levels above a leave are wrong.
completion-broken

super task_0 and super task_1 should have the same percentage like both instances of the nesteted tasks task_0 and task_3.

In addition to this a milestone counts as a day towards completion but has a duration of 0. This also seems to be inconsistent.

The solution is to calculate the completion using the recursively accumulated completed and total duration of all tasks which do not have nested tasks. For a given tasks this has to be done for the complete part of the hierarchy i.e. sub-tree the task is a part of.

This pull request fixes both issues and includes a new test.

Cheers,
Christoph

Copy link
Contributor

@dbarashev dbarashev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey, thanks for digging into this clumsy code! Some comments which will make it even better.

@@ -29,7 +29,7 @@ public void run() {
}
TaskContainmentHierarchyFacade facade = createContainmentFacade();
for (Task t : facade.getTasksInDocumentOrder()) {
if (!facade.hasNestedTasks(t)) {
if (facade.getDepth(t) == 0) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the algorithm is now going top-down, you can remove this loop completely and just take the root task from the facade.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, ok. So there is always a single root task no matter of the project structure?

@@ -43,29 +43,52 @@ public void run(Task task) {
recalculateSupertaskCompletionPercentageBottomUp(task, facade);
}

private class PropagatedDays {
public int myCompletedDays;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pls make the class static and fields final

public int myCompletedDays;
public long myPlannedDays;

PropagatedDays() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You probably don't need this constructor

@@ -43,29 +43,52 @@ public void run(Task task) {
recalculateSupertaskCompletionPercentageBottomUp(task, facade);
}

private class PropagatedDays {
public int myCompletedDays;
public long myPlannedDays;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why planned is long while completed is int? In the code, completed is like 100x planned so it should be at least wider

myPlannedDays = plannedDays;
};
}

private void recalculateSupertaskCompletionPercentageBottomUp(Task task, TaskContainmentHierarchyFacade facade) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you take the root task at lines 31-35, you can remove this method completely.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, but then the implementation of void run(Task task) has to be changed too or its invocations need to be replaced by run().

}
int completionPercentage = plannedDays == 0 ? 0 : (int) (completedDays / plannedDays);
task.setCompletionPercentage(completionPercentage);
} else {
long nextDuration = task.getDuration().getLength();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest returning new PropagatedDays(nextDuration * task.getCompletionPercentage(), nextDuration) right away and moving accumulating vars into the previous branch of if

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any particular reason for this?
This way there have to be two return statements, one within each branch of the if-statement which leaves the method without an obvious return statement.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to have less mutable vars. In fact, I would even remove else like below but that doesn't matter too much to me :)

if (nestedTasks.length == 0) {
  return new SubtreeCompletion....
}
long completedDays...
// other code from current if > 0 block

@@ -43,29 +43,52 @@ public void run(Task task) {
recalculateSupertaskCompletionPercentageBottomUp(task, facade);
}

private class PropagatedDays {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's rename it to SubtreeCompletion ?

Task supertask_1_0 = taskManager.createTask();
Task supertask_1_1 = taskManager.createTask();

supertask_0_0.setStart(CalendarFactory.createGanttCalendar(2000, 01, 10));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I recommend using TaskBuilder interface, like this:

Task task_0_0 = taskManager.newTaskBuilder()
    .withStartDate(TestSetupHelper.newMonday().getTime())
    .withDuration(taskManager.createLength(1))
    .withCompletion(100)
    .withParent(supertask_0)
    .build();

This will make the test code more readable.

Please pay attention that in the code end date is start date + 1.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice. I just used the available code within the existing tests.

supertask_0.move(supertask);
supertask_1.move(supertask);

supertask.move(project);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Project has to be moved under taskManager.getRootTask() as well.

supertask.move(project);
//
RecalculateTaskCompletionPercentageAlgorithm alg = taskManager.getAlgorithmCollection().getRecalculateTaskCompletionPercentageAlgorithm();
alg.run(project);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe let's just run the algorithm without arguments, applying to the whole project?

}

public void run(Task task) {
if (!isEnabled()) {
return;
}
TaskContainmentHierarchyFacade facade = createContainmentFacade();
recalculateSupertaskCompletionPercentageBottomUp(task, facade);
recalculateSupertaskCompletionPercentage(facade.getRootTask(), facade);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May I ask you to update the callers of this method so that they called run without arguments? After that this method can be removed. Here is the list of callers:

TaskModelModificationListener.taskProgressChanged(TaskPropertyEvent)  (net.sourceforge.ganttproject)
TestTaskCompletionPercentage.testCompletionWithMilestones()  (net.sourceforge.ganttproject.test.task)
TaskModelModificationListener.taskAdded(TaskHierarchyEvent)  (net.sourceforge.ganttproject)
TestTaskCompletionPercentage.testCompletionIs50WhenAllNestedTasksHalfCompleted()  (net.sourceforge.ganttproject.test.task)
TaskModelModificationListener.taskScheduleChanged(TaskScheduleEvent)  (net.sourceforge.ganttproject)
TestTaskCompletionPercentage.testCompletionIs0WhenAllNestedTasksNotStarted()  (net.sourceforge.ganttproject.test.task)
TaskManagerImpl.projectOpened()  (net.sourceforge.ganttproject.task)
TestTaskCompletionPercentage.testCompletionIs100WhenAllNestedTasksCompleted()  (net.sourceforge.ganttproject.test.task)
    

}
int completionPercentage = plannedDays == 0 ? 0 : (int) (completedDays / plannedDays);
task.setCompletionPercentage(completionPercentage);
} else {
long nextDuration = task.getDuration().getLength();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to have less mutable vars. In fact, I would even remove else like below but that doesn't matter too much to me :)

if (nestedTasks.length == 0) {
  return new SubtreeCompletion....
}
long completedDays...
// other code from current if > 0 block

Copy link
Contributor

@dbarashev dbarashev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, merging now. Thanks!

@dbarashev dbarashev merged commit d8ea137 into bardsoftware:master Aug 31, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants