Skip to content

Commit

Permalink
Merge pull request #97 from Short-Tracker/fix/analytics3
Browse files Browse the repository at this point in the history
Изменил логику аналитики. Добавил сортировку по количеству задач у сотрудников
  • Loading branch information
ErendzhenovBair authored Feb 22, 2024
2 parents 2b9976d + 14a3469 commit b802e54
Show file tree
Hide file tree
Showing 12 changed files with 191 additions and 42 deletions.
53 changes: 37 additions & 16 deletions short_tracker/api/v1/analytics/analytics.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,21 @@
from datetime import timedelta

from django.db.models import F
from django.utils import timezone

from users.models import CustomUser


class TasksAnalyticsFactory:
@staticmethod
def calculate_analytics(queryset):
def calculate_analytics(queryset, sort_by=None):
analytics = {}
analytics.update(
TasksAnalyticsFactory.tasks_count(queryset)
)
analytics.update(
TasksAnalyticsFactory.performers_analytics(
queryset)
queryset, sort_by)
)
return analytics

Expand All @@ -31,45 +32,65 @@ def tasks_count(queryset):
}

@staticmethod
def performers_analytics(queryset):
def performers_analytics(queryset, sort_by=None):
performers_analytics = {}
performers_ids = queryset.values_list(
'performers', flat=True).distinct()
'performer_id', flat=True).distinct()
for performer_id in performers_ids:
performer = CustomUser.objects.get(id=performer_id)
filtered_queryset = queryset.filter(performers=performer)
filtered_queryset = queryset.filter(performer=performer)
performer_name = f"{performer.first_name} {performer.last_name}"
completed_on_time_count = filtered_queryset.filter(
on_time_count = filtered_queryset.filter(
done_date__lte=F('deadline_date'),
performers=performer).count()
completed_with_delay_count = filtered_queryset.filter(
performer=performer).count()
with_delay_count = filtered_queryset.filter(
done_date__gt=F('deadline_date'),
performers=performer).count()
performer=performer).count()
total_tasks = on_time_count + with_delay_count
performers_analytics[performer_id] = {
'performer_name': performer_name,
'completed_on_time_count': completed_on_time_count,
'completed_with_delay_count':
completed_with_delay_count,
'performer_name': performer_name,
'total_tasks': total_tasks,
'on_time_count': on_time_count,
'with_delay_count': with_delay_count,
'avg_time_create_date_to_inprogress_date':
TasksAnalyticsFactory.avg_time(
filtered_queryset, 'create_date', 'inprogress_date'),
'avg_time_create_date_to_done_date':
'avg_time_create_date_to_done_date':
TasksAnalyticsFactory.avg_time(
filtered_queryset, 'create_date', 'done_date'),
'avg_time_inprogress_date_to_done_date':
TasksAnalyticsFactory.avg_time(
filtered_queryset, 'inprogress_date', 'done_date'),
}
performers_analytics = TasksAnalyticsFactory.sort_by(
performers_analytics, sort_by
)
return {'performers_analytics': performers_analytics}

@staticmethod
def sort_by(performers_analytics, sort_by=None):
key_mapping = {
'total_tasks': 'total_tasks',
'on_time_count': 'on_time_count',
'with_delay_count': 'with_delay_count',
}
key = key_mapping.get(sort_by, 'on_time_count')
performers_analytics = dict(
sorted(
performers_analytics.items(),
key=lambda x: x[1][key], reverse=True
)
)
return performers_analytics

@staticmethod
def avg_time(queryset, field1, field2):
datetime_list = [
getattr(task, field2) - getattr(task, field1)
timezone.localtime(getattr(task, field2)) - timezone.localtime(getattr(task, field1))
for task in queryset
if getattr(task, field1) and getattr(task, field2)
]
sum_of_time = sum(datetime_list, datetime.timedelta())
length = len(datetime_list)
avg_time = sum_of_time // length if length > 0 else timedelta()
return str(avg_time).rsplit(':', 1)[0]
return str(avg_time).rsplit(':', 1)[0]
6 changes: 3 additions & 3 deletions short_tracker/api/v1/analytics/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@

class PerformerAnalyticsSerializer(serializers.Serializer):
performer_name = serializers.CharField()
completed_on_time_count = serializers.IntegerField()
completed_with_delay_count = serializers.IntegerField()
total_tasks = serializers.IntegerField()
on_time_count = serializers.IntegerField()
with_delay_count = serializers.IntegerField()
avg_time_create_date_to_inprogress_date = serializers.CharField()
avg_time_create_date_to_done_date = serializers.CharField()
avg_time_inprogress_date_to_done_date = serializers.CharField()


class TaskAnalyticsSerializer(serializers.Serializer):
total_tasks_on_time = serializers.IntegerField()
total_tasks_with_delay = serializers.IntegerField()
Expand Down
26 changes: 15 additions & 11 deletions short_tracker/api/v1/analytics/views.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from datetime import datetime, timedelta
from datetime import timedelta

from django.db.models import Prefetch
from django.db.models import F, Prefetch, Q
from django_filters.rest_framework import DjangoFilterBackend
from django.utils import timezone
from rest_framework import status, viewsets
from rest_framework.response import Response

Expand All @@ -25,28 +26,31 @@ def get_queryset(self):
status=Task.TaskStatus.DONE
).prefetch_related(
Prefetch(
'performers',
'performer',
queryset=CustomUser.objects.filter(
is_team_lead=False
)
)
)
if self.request.query_params:
start_date = self.request.query_params.get('start_date')
end_date = self.request.query_params.get('end_date')
if start_date or end_date:
filterset = TaskAnalyticsFilter(
self.request.query_params,
queryset=queryset, request=self.request)
queryset=queryset, request=self.request
)
if filterset.is_valid():
queryset = filterset.qs
queryset = filterset.qs
else:
queryset = Task.objects.filter(
done_date__gte=datetime.today() - timedelta(days=7))
return queryset.filter(
Q(done_date__gte=timezone.now() - timedelta(days=7)))
return queryset

def list(self, request, *args, **kwargs):
sort_by = request.query_params.get('sort_by')
serializer = TaskAnalyticsSerializer(
data=TasksAnalyticsFactory.calculate_analytics(
self.get_queryset()
)
)
self.get_queryset(), sort_by=sort_by
))
serializer.is_valid(raise_exception=True)
return Response(serializer.data, status=status.HTTP_200_OK)
20 changes: 8 additions & 12 deletions short_tracker/api/v1/filters.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import django_filters
from django.db.models import Case, Q, Value, When
from django.db.models import Case, F,Q, Value, When
from django.utils import timezone
from django_filters.rest_framework import FilterSet, filters

from api.v1.analytics.analytics import TasksAnalyticsFactory
from tasks.models import Task


Expand Down Expand Up @@ -74,21 +75,16 @@ def filter_is_expired(self, queryset, _, value):
)
return queryset


class TaskAnalyticsFilter(django_filters.FilterSet):
performer_id = filters.NumberFilter(
field_name='performers', method='filter_by_performer')
start_date = django_filters.DateFilter(
start_date = django_filters.IsoDateTimeFilter(
field_name='done_date',lookup_expr=('gt'))
end_date = django_filters.DateFilter(
end_date = django_filters.IsoDateTimeFilter(
field_name='done_date',lookup_expr=('lt'))
date_range = filters.DateRangeFilter(field_name='done_date')
sort_by = filters.CharFilter(method='filter_sort_by')

class Meta:
model = Task
fields=[]

def filter_by_performer(self, queryset, name, value):
if value:
return queryset.filter(performers__in=[value])
return queryset

def filter_sort_by(self, queryset, name, value):
return queryset
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 4.2.8 on 2024-02-19 14:51

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('bot', '0005_alter_allownotification_notification'),
]

operations = [
migrations.AlterField(
model_name='allownotification',
name='notification',
field=models.CharField(choices=[('msg', 'Сообщения'), ('tasks', 'Задачи'), ('status', 'Статусы'), ('deadline', 'Дедлайн')], max_length=10, verbose_name='Уведомление'),
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 4.2.8 on 2024-02-19 14:52

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('bot', '0006_alter_allownotification_notification'),
]

operations = [
migrations.AlterField(
model_name='allownotification',
name='notification',
field=models.CharField(choices=[('status', 'Статусы'), ('tasks', 'Задачи'), ('msg', 'Сообщения'), ('deadline', 'Дедлайн')], max_length=10, verbose_name='Уведомление'),
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 4.2.8 on 2024-02-19 14:54

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('bot', '0007_alter_allownotification_notification'),
]

operations = [
migrations.AlterField(
model_name='allownotification',
name='notification',
field=models.CharField(choices=[('msg', 'Сообщения'), ('tasks', 'Задачи'), ('deadline', 'Дедлайн'), ('status', 'Статусы')], max_length=10, verbose_name='Уведомление'),
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 4.2.8 on 2024-02-20 08:56

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('bot', '0008_alter_allownotification_notification'),
]

operations = [
migrations.AlterField(
model_name='allownotification',
name='notification',
field=models.CharField(choices=[('status', 'Статусы'), ('tasks', 'Задачи'), ('deadline', 'Дедлайн'), ('msg', 'Сообщения')], max_length=10, verbose_name='Уведомление'),
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 4.2.8 on 2024-02-20 10:58

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('bot', '0009_alter_allownotification_notification'),
]

operations = [
migrations.AlterField(
model_name='allownotification',
name='notification',
field=models.CharField(choices=[('deadline', 'Дедлайн'), ('status', 'Статусы'), ('msg', 'Сообщения'), ('tasks', 'Задачи')], max_length=10, verbose_name='Уведомление'),
),
]
2 changes: 2 additions & 0 deletions short_tracker/short_tracker/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,8 @@

LANGUAGE_CODE = 'ru-ru'

TIME_ZONE = 'Europe/Moscow'

TIME_ZONE = 'UTC'

USE_I18N = True
Expand Down
18 changes: 18 additions & 0 deletions short_tracker/tasks/migrations/0005_alter_task_done_date.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 4.2.8 on 2024-02-20 08:56

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('tasks', '0004_task_tasks_task_create__81ab0d_idx'),
]

operations = [
migrations.AlterField(
model_name='task',
name='done_date',
field=models.DateTimeField(auto_now_add=True, help_text='The done date of the task', null=True, verbose_name='done date'),
),
]
18 changes: 18 additions & 0 deletions short_tracker/tasks/migrations/0006_alter_task_done_date.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 4.2.8 on 2024-02-20 10:58

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('tasks', '0005_alter_task_done_date'),
]

operations = [
migrations.AlterField(
model_name='task',
name='done_date',
field=models.DateTimeField(blank=True, help_text='The done date of the task', null=True, verbose_name='done date'),
),
]

0 comments on commit b802e54

Please sign in to comment.