Improving milestone stats API

remotes/origin/logger
Alejandro Alonso 2015-11-05 10:16:12 +01:00
parent b142cafc14
commit a4172012ba
2 changed files with 68 additions and 13 deletions

View File

@ -113,10 +113,10 @@ class MilestoneViewSet(HistoryResourceMixin, WatchedResourceMixin, ModelCrudView
'estimated_finish': milestone.estimated_finish, 'estimated_finish': milestone.estimated_finish,
'total_points': total_points, 'total_points': total_points,
'completed_points': milestone.closed_points.values(), 'completed_points': milestone.closed_points.values(),
'total_userstories': milestone.user_stories.count(), 'total_userstories': milestone.get_cached_user_stories().count(),
'completed_userstories': len([us for us in milestone.user_stories.all() if us.is_closed]), 'completed_userstories': milestone.get_cached_user_stories().filter(is_closed=True).count(),
'total_tasks': milestone.tasks.all().count(), 'total_tasks': milestone.tasks.count(),
'completed_tasks': milestone.tasks.all().filter(status__is_closed=True).count(), 'completed_tasks': milestone.tasks.filter(status__is_closed=True).count(),
'iocaine_doses': milestone.tasks.filter(is_iocaine=True).count(), 'iocaine_doses': milestone.tasks.filter(is_iocaine=True).count(),
'days': [] 'days': []
} }
@ -125,11 +125,12 @@ class MilestoneViewSet(HistoryResourceMixin, WatchedResourceMixin, ModelCrudView
optimal_points = sumTotalPoints optimal_points = sumTotalPoints
milestone_days = (milestone.estimated_finish - milestone.estimated_start).days milestone_days = (milestone.estimated_finish - milestone.estimated_start).days
optimal_points_per_day = sumTotalPoints / milestone_days if milestone_days else 0 optimal_points_per_day = sumTotalPoints / milestone_days if milestone_days else 0
while current_date <= milestone.estimated_finish: while current_date <= milestone.estimated_finish:
milestone_stats['days'].append({ milestone_stats['days'].append({
'day': current_date, 'day': current_date,
'name': current_date.day, 'name': current_date.day,
'open_points': sumTotalPoints - sum(milestone.closed_points_by_date(current_date).values()), 'open_points': sumTotalPoints - milestone.total_closed_points_by_date(current_date),
'optimal_points': optimal_points, 'optimal_points': optimal_points,
}) })
current_date = current_date + datetime.timedelta(days=1) current_date = current_date + datetime.timedelta(days=1)

View File

@ -14,7 +14,9 @@
# You should have received a copy of the GNU Affero General Public License # You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from django.apps import apps
from django.db import models from django.db import models
from django.db.models import Prefetch, Count
from django.conf import settings from django.conf import settings
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.utils import timezone from django.utils import timezone
@ -53,6 +55,8 @@ class Milestone(WatchedModelMixin, models.Model):
order = models.PositiveSmallIntegerField(default=1, null=False, blank=False, order = models.PositiveSmallIntegerField(default=1, null=False, blank=False,
verbose_name=_("order")) verbose_name=_("order"))
_importing = None _importing = None
_total_closed_points_by_date = None
_cached_user_stories = None
class Meta: class Meta:
verbose_name = "milestone" verbose_name = "milestone"
@ -82,6 +86,14 @@ class Milestone(WatchedModelMixin, models.Model):
super().save(*args, **kwargs) super().save(*args, **kwargs)
def get_cached_user_stories(self):
if self._cached_user_stories is None:
self._cached_user_stories = self.user_stories.\
prefetch_related("role_points", "role_points__points").\
annotate(num_tasks=Count("tasks"))
return self._cached_user_stories
def _get_user_stories_points(self, user_stories): def _get_user_stories_points(self, user_stories):
role_points = [us.role_points.all() for us in user_stories] role_points = [us.role_points.all() for us in user_stories]
flat_role_points = itertools.chain(*role_points) flat_role_points = itertools.chain(*role_points)
@ -91,13 +103,13 @@ class Milestone(WatchedModelMixin, models.Model):
@property @property
def total_points(self): def total_points(self):
return self._get_user_stories_points( return self._get_user_stories_points(
[us for us in self.user_stories.all()] [us for us in self.get_cached_user_stories()]
) )
@property @property
def closed_points(self): def closed_points(self):
return self._get_user_stories_points( return self._get_user_stories_points(
[us for us in self.user_stories.all() if us.is_closed] [us for us in self.get_cached_user_stories() if us.is_closed]
) )
def _get_increment_points(self): def _get_increment_points(self):
@ -148,9 +160,51 @@ class Milestone(WatchedModelMixin, models.Model):
def shared_increment_points(self): def shared_increment_points(self):
return self._get_increment_points()["shared_increment"] return self._get_increment_points()["shared_increment"]
def closed_points_by_date(self, date): def total_closed_points_by_date(self, date):
return self._get_user_stories_points([ # Milestone instance will keep a cache of the total closed points by date
us for us in self.user_stories.filter( if self._total_closed_points_by_date is None:
finish_date__lt=date + datetime.timedelta(days=1) self._total_closed_points_by_date = {}
).prefetch_related('role_points', 'role_points__points') if us.is_closed
]) # We need to keep the milestone user stories indexed by id in a dict
user_stories = {}
for us in self.get_cached_user_stories():
us._total_us_points = sum(self._get_user_stories_points([us]).values())
user_stories[us.id] = us
tasks = self.tasks.\
select_related("user_story").\
exclude(finished_date__isnull=True).\
exclude(user_story__isnull=True)
# For each finished task we try to know the proporional part of points
# it represetnts from the user story and add it to the closed points
# for that date
# This calulation is the total user story points divided by its number of tasks
for task in tasks:
user_story = user_stories[task.user_story.id]
total_us_points = user_story._total_us_points
us_tasks_counter = user_story.num_tasks
# If the task was finished before starting the sprint it needs
# to be included
finished_date = task.finished_date.date()
if finished_date < self.estimated_start:
finished_date = self.estimated_start
points_by_date = self._total_closed_points_by_date.get(finished_date, 0)
points_by_date += total_us_points / us_tasks_counter
self._total_closed_points_by_date[finished_date] = points_by_date
# At this point self._total_closed_points_by_date keeps a dict where the
# finished date of the task is the key and the value is the increment of points
# We are transforming this dict of increments in an acumulation one including
# all the dates from the sprint
acumulated_date_points = 0
current_date = self.estimated_start
while current_date <= self.estimated_finish:
acumulated_date_points += self._total_closed_points_by_date.get(current_date, 0)
self._total_closed_points_by_date[current_date] = acumulated_date_points
current_date = current_date + datetime.timedelta(days=1)
return self._total_closed_points_by_date.get(date, 0)