Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0864354d69 |
@@ -44,6 +44,7 @@ from .cycle import (
|
||||
CycleIssueSerializer,
|
||||
CycleWriteSerializer,
|
||||
CycleUserPropertiesSerializer,
|
||||
CycleAnalyticsSerializer,
|
||||
)
|
||||
from .asset import FileAssetSerializer
|
||||
from .issue import (
|
||||
|
||||
@@ -7,6 +7,7 @@ from .issue import IssueStateSerializer
|
||||
from plane.db.models import (
|
||||
Cycle,
|
||||
CycleIssue,
|
||||
CycleAnalytics,
|
||||
CycleUserProperties,
|
||||
)
|
||||
|
||||
@@ -93,6 +94,7 @@ class CycleIssueSerializer(BaseSerializer):
|
||||
"cycle",
|
||||
]
|
||||
|
||||
|
||||
class CycleUserPropertiesSerializer(BaseSerializer):
|
||||
class Meta:
|
||||
model = CycleUserProperties
|
||||
@@ -102,3 +104,9 @@ class CycleUserPropertiesSerializer(BaseSerializer):
|
||||
"project",
|
||||
"cycle" "user",
|
||||
]
|
||||
|
||||
|
||||
class CycleAnalyticsSerializer(BaseSerializer):
|
||||
class Meta:
|
||||
model = CycleAnalytics
|
||||
fields = "__all__"
|
||||
|
||||
@@ -9,6 +9,7 @@ from plane.app.views import (
|
||||
CycleProgressEndpoint,
|
||||
CycleAnalyticsEndpoint,
|
||||
TransferCycleIssueEndpoint,
|
||||
CycleIssueStateAnalyticsEndpoint,
|
||||
CycleUserPropertiesEndpoint,
|
||||
CycleArchiveUnarchiveEndpoint,
|
||||
)
|
||||
@@ -118,4 +119,9 @@ urlpatterns = [
|
||||
CycleAnalyticsEndpoint.as_view(),
|
||||
name="project-cycle",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/cycles/<uuid:cycle_id>/cycle-progress/",
|
||||
CycleIssueStateAnalyticsEndpoint.as_view(),
|
||||
name="project-cycle-progress",
|
||||
),
|
||||
]
|
||||
|
||||
@@ -100,10 +100,9 @@ from .cycle.base import (
|
||||
TransferCycleIssueEndpoint,
|
||||
CycleAnalyticsEndpoint,
|
||||
CycleProgressEndpoint,
|
||||
CycleIssueStateAnalyticsEndpoint,
|
||||
)
|
||||
from .cycle.issue import (
|
||||
CycleIssueViewSet,
|
||||
)
|
||||
from .cycle.issue import CycleIssueViewSet
|
||||
from .cycle.archive import (
|
||||
CycleArchiveUnarchiveEndpoint,
|
||||
)
|
||||
|
||||
@@ -31,6 +31,7 @@ from plane.app.permissions import allow_permission, ROLE
|
||||
from plane.app.serializers import (
|
||||
CycleSerializer,
|
||||
CycleUserPropertiesSerializer,
|
||||
CycleAnalyticsSerializer,
|
||||
CycleWriteSerializer,
|
||||
)
|
||||
from plane.bgtasks.issue_activities_task import issue_activity
|
||||
@@ -44,6 +45,8 @@ from plane.db.models import (
|
||||
User,
|
||||
Project,
|
||||
ProjectMember,
|
||||
CycleAnalytics,
|
||||
CycleIssueStateProgress,
|
||||
)
|
||||
from plane.utils.analytics_plot import burndown_plot
|
||||
from plane.bgtasks.recent_visited_task import recent_visited_task
|
||||
@@ -958,6 +961,37 @@ class TransferCycleIssueEndpoint(BaseAPIView):
|
||||
updated_cycles, ["cycle_id"], batch_size=100
|
||||
)
|
||||
|
||||
estimate_type = Project.objects.filter(
|
||||
workspace__slug=slug,
|
||||
pk=project_id,
|
||||
estimate__isnull=False,
|
||||
estimate__type="points",
|
||||
).exists()
|
||||
|
||||
CycleIssueStateProgress.objects.bulk_create(
|
||||
[
|
||||
CycleIssueStateProgress(
|
||||
cycle_id=new_cycle_id,
|
||||
state_id=cycle_issue.issue.state_id,
|
||||
issue_id=cycle_issue.issue_id,
|
||||
state_group=cycle_issue.issue.state.group,
|
||||
type="ADDED",
|
||||
estimate_id=cycle_issue.issue.estimate_point_id,
|
||||
estimate_value=(
|
||||
cycle_issue.issue.estimate_point.value
|
||||
if estimate_type
|
||||
else None
|
||||
),
|
||||
project_id=project_id,
|
||||
workspace_id=cycle_issue.workspace_id,
|
||||
created_by_id=request.user.id,
|
||||
updated_by_id=request.user.id,
|
||||
)
|
||||
for cycle_issue in cycle_issues
|
||||
],
|
||||
batch_size=10,
|
||||
)
|
||||
|
||||
# Capture Issue Activity
|
||||
issue_activity.delay(
|
||||
type="cycle.activity.created",
|
||||
@@ -1148,6 +1182,7 @@ class CycleProgressEndpoint(BaseAPIView):
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
|
||||
|
||||
class CycleAnalyticsEndpoint(BaseAPIView):
|
||||
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST])
|
||||
@@ -1367,3 +1402,19 @@ class CycleAnalyticsEndpoint(BaseAPIView):
|
||||
},
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
|
||||
|
||||
class CycleIssueStateAnalyticsEndpoint(BaseAPIView):
|
||||
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST])
|
||||
def get(self, request, slug, project_id, cycle_id):
|
||||
cycle_state_progress = CycleAnalytics.objects.filter(
|
||||
cycle_id=cycle_id,
|
||||
project_id=project_id,
|
||||
workspace__slug=slug,
|
||||
)
|
||||
|
||||
return Response(
|
||||
CycleAnalyticsSerializer(cycle_state_progress, many=True).data,
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
|
||||
@@ -24,6 +24,8 @@ from plane.db.models import (
|
||||
Issue,
|
||||
IssueAttachment,
|
||||
IssueLink,
|
||||
CycleIssueStateProgress,
|
||||
Project,
|
||||
)
|
||||
from plane.utils.grouper import (
|
||||
issue_group_values,
|
||||
@@ -268,6 +270,10 @@ class CycleIssueViewSet(BaseViewSet):
|
||||
]
|
||||
new_issues = list(set(issues) - set(existing_issues))
|
||||
|
||||
# Fetch issue details
|
||||
issue_objects = Issue.objects.filter(id__in=new_issues)
|
||||
issue_dict = {issue.id: issue for issue in issue_objects}
|
||||
|
||||
# New issues to create
|
||||
created_records = CycleIssue.objects.bulk_create(
|
||||
[
|
||||
@@ -284,6 +290,42 @@ class CycleIssueViewSet(BaseViewSet):
|
||||
batch_size=10,
|
||||
)
|
||||
|
||||
# estimate_type = Project.objects.filter(
|
||||
# workspace__slug=slug,
|
||||
# pk=project_id,
|
||||
# estimate__isnull=False,
|
||||
# estimate__type="points",
|
||||
# ).exists()
|
||||
|
||||
# for issue_id in new_issues:
|
||||
# print(issue_id, "issue id")
|
||||
# print(issue_dict[issue_id].state_id, "state_id")
|
||||
|
||||
# CycleIssueStateProgress.objects.bulk_create(
|
||||
# [
|
||||
# CycleIssueStateProgress(
|
||||
# cycle_id=cycle_id,
|
||||
# state_id=str(issue_dict[issue_id].state_id),
|
||||
# issue_id=issue_id,
|
||||
# state_group=issue_dict[issue_id].state.group,
|
||||
# type="ADDED",
|
||||
# estimate_id=issue_dict[issue_id].estimate_id,
|
||||
# estimate_value=(
|
||||
# issue_dict[issue_id].estimate_point.value
|
||||
# if estimate_type
|
||||
# else None
|
||||
# ),
|
||||
# project_id=project_id,
|
||||
# workspace_id=cycle.workspace_id,
|
||||
# created_by_id=request.user.id,
|
||||
# updated_by_id=request.user.id,
|
||||
# )
|
||||
# print(issue_id, "issue id")
|
||||
# for issue_id in new_issues
|
||||
# ],
|
||||
# batch_size=10,
|
||||
# )
|
||||
|
||||
# Updated Issues
|
||||
updated_records = []
|
||||
update_cycle_issue_activity = []
|
||||
@@ -336,6 +378,28 @@ class CycleIssueViewSet(BaseViewSet):
|
||||
project_id=project_id,
|
||||
cycle_id=cycle_id,
|
||||
)
|
||||
issue = Issue.objects.get(pk=issue_id)
|
||||
estimate_type = Project.objects.filter(
|
||||
workspace__slug=slug,
|
||||
pk=project_id,
|
||||
estimate__isnull=False,
|
||||
estimate__type="points",
|
||||
).exists()
|
||||
|
||||
CycleIssueStateProgress.objects.create(
|
||||
cycle_id=cycle_id,
|
||||
state_id=issue.state_id,
|
||||
issue_id=issue_id,
|
||||
state_group=issue.state.group,
|
||||
type="REMOVED",
|
||||
estimate_id=issue.estimate_id,
|
||||
estimate_value=(
|
||||
issue.estimate_point.value if estimate_type else None
|
||||
),
|
||||
project_id=project_id,
|
||||
created_by_id=request.user.id,
|
||||
updated_by_id=request.user.id,
|
||||
)
|
||||
issue_activity.delay(
|
||||
type="cycle.activity.deleted",
|
||||
requested_data=json.dumps(
|
||||
|
||||
@@ -42,6 +42,7 @@ from plane.db.models import (
|
||||
IssueSubscriber,
|
||||
Project,
|
||||
ProjectMember,
|
||||
CycleIssueStateProgress,
|
||||
)
|
||||
from plane.utils.grouper import (
|
||||
issue_group_values,
|
||||
@@ -544,6 +545,8 @@ class IssueViewSet(BaseViewSet):
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
|
||||
|
||||
recent_visited_task.delay(
|
||||
slug=slug,
|
||||
entity_name="issue",
|
||||
@@ -601,6 +604,29 @@ class IssueViewSet(BaseViewSet):
|
||||
current_instance = json.dumps(
|
||||
IssueSerializer(issue).data, cls=DjangoJSONEncoder
|
||||
)
|
||||
estimate_type = Project.objects.filter(
|
||||
workspace__slug=slug,
|
||||
pk=project_id,
|
||||
estimate__isnull=False,
|
||||
estimate__type="points",
|
||||
).exists()
|
||||
|
||||
if issue.cycle_id:
|
||||
CycleIssueStateProgress.objects.create(
|
||||
cycle_id=issue.cycle_id,
|
||||
state_id=issue.state_id,
|
||||
issue_id=issue.id,
|
||||
state_group=issue.state.group,
|
||||
type="UPDATED",
|
||||
estimate_id=issue.estimate_point_id,
|
||||
estimate_value=(
|
||||
issue.estimate_point.value if estimate_type else None
|
||||
),
|
||||
project_id=project_id,
|
||||
workspace_id=issue.workspace_id,
|
||||
created_by_id=request.user.id,
|
||||
updated_by_id=request.user.id,
|
||||
)
|
||||
|
||||
requested_data = json.dumps(self.request.data, cls=DjangoJSONEncoder)
|
||||
serializer = IssueCreateSerializer(
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
# Django imports
|
||||
from django.db.models import Sum
|
||||
from django.utils import timezone
|
||||
from django.db.models import F
|
||||
from django.db.models.functions import RowNumber
|
||||
from django.db.models import Max, Subquery, OuterRef
|
||||
|
||||
# Third party imports
|
||||
from celery import shared_task
|
||||
from plane.db.models import Cycle, CycleIssueStateProgress, CycleAnalytics
|
||||
|
||||
|
||||
@shared_task
|
||||
def track_cycle_issue_state_progress():
|
||||
|
||||
active_cycles = Cycle.objects.filter(
|
||||
start_date__lte=timezone.now(), end_date__gte=timezone.now()
|
||||
).values_list("id", "project_id", "workspace_id")
|
||||
|
||||
analytics_records = []
|
||||
current_date = timezone.now().date()
|
||||
|
||||
for cycle_id, project_id, workspace_id in active_cycles:
|
||||
# Subquery to get the latest id for each issue_id
|
||||
# Subquery to get the latest created_at for each issue_id
|
||||
# latest_created_at = CycleIssueStateProgress.objects.filter(
|
||||
# cycle_id=cycle_id,
|
||||
# type__in=["ADDED", "UPDATED"],
|
||||
# issue_id=OuterRef("issue_id"),
|
||||
# created_at__lte=timezone.now(),
|
||||
# ).values('issue_id').annotate(
|
||||
# latest_created=Max('created_at')
|
||||
# ).values('latest_created')
|
||||
|
||||
# # Main query to get the latest unique issues
|
||||
# cycle_issues = CycleIssueStateProgress.objects.filter(
|
||||
# cycle_id=cycle_id,
|
||||
# type__in=["ADDED", "UPDATED"],
|
||||
# created_at=Subquery(latest_created_at),
|
||||
# issue_id=OuterRef("issue_id")
|
||||
# ).order_by("issue_id")
|
||||
|
||||
cycle_issues = CycleIssueStateProgress.objects.filter(
|
||||
id=Subquery(
|
||||
CycleIssueStateProgress.objects.filter(
|
||||
cycle_id=cycle_id,
|
||||
type__in=["ADDED", "UPDATED"],
|
||||
issue=OuterRef("issue"),
|
||||
)
|
||||
.order_by("-created_at")
|
||||
.values("id")[:1]
|
||||
)
|
||||
)
|
||||
# print()
|
||||
for issue in cycle_issues.values():
|
||||
print(issue, "issues")
|
||||
|
||||
total_issues = cycle_issues.count()
|
||||
total_estimate_points = (
|
||||
cycle_issues.aggregate(
|
||||
total_estimate_points=Sum("estimate_value")
|
||||
)["total_estimate_points"]
|
||||
or 0
|
||||
)
|
||||
|
||||
state_groups = [
|
||||
"backlog",
|
||||
"unstarted",
|
||||
"started",
|
||||
"completed",
|
||||
"cancelled",
|
||||
]
|
||||
state_data = {
|
||||
group: {
|
||||
"count": cycle_issues.filter(state_group=group).count(),
|
||||
"estimate_points": cycle_issues.filter(
|
||||
state_group=group
|
||||
).aggregate(total_estimate_points=Sum("estimate_value"))[
|
||||
"total_estimate_points"
|
||||
]
|
||||
or 0,
|
||||
}
|
||||
for group in state_groups
|
||||
}
|
||||
|
||||
# Prepare analytics record for bulk insert
|
||||
analytics_records.append(
|
||||
CycleAnalytics(
|
||||
cycle_id=cycle_id,
|
||||
date=current_date,
|
||||
total_issues=total_issues,
|
||||
total_estimate_points=total_estimate_points,
|
||||
backlog_issues=state_data["backlog"]["count"],
|
||||
unstarted_issues=state_data["unstarted"]["count"],
|
||||
started_issues=state_data["started"]["count"],
|
||||
completed_issues=state_data["completed"]["count"],
|
||||
cancelled_issues=state_data["cancelled"]["count"],
|
||||
backlog_estimate_points=state_data["backlog"][
|
||||
"estimate_points"
|
||||
],
|
||||
unstarted_estimate_points=state_data["unstarted"][
|
||||
"estimate_points"
|
||||
],
|
||||
started_estimate_points=state_data["started"][
|
||||
"estimate_points"
|
||||
],
|
||||
completed_estimate_points=state_data["completed"][
|
||||
"estimate_points"
|
||||
],
|
||||
cancelled_estimate_points=state_data["cancelled"][
|
||||
"estimate_points"
|
||||
],
|
||||
project_id=project_id,
|
||||
workspace_id=workspace_id,
|
||||
)
|
||||
)
|
||||
|
||||
# Bulk create the records at once
|
||||
if analytics_records:
|
||||
CycleAnalytics.objects.bulk_create(analytics_records)
|
||||
@@ -40,6 +40,10 @@ app.conf.beat_schedule = {
|
||||
"task": "plane.bgtasks.deletion_task.hard_delete",
|
||||
"schedule": crontab(hour=0, minute=0),
|
||||
},
|
||||
"track-cycle-issue-state-progress": {
|
||||
"task": "plane.bgtasks.cycle_issue_state_progress_task.track_cycle_issue_state_progress",
|
||||
"schedule": crontab(hour=9, minute=6),
|
||||
},
|
||||
}
|
||||
|
||||
# Load task modules from all registered Django app configs.
|
||||
|
||||
+192
File diff suppressed because one or more lines are too long
@@ -2,7 +2,16 @@ from .analytic import AnalyticView
|
||||
from .api import APIActivityLog, APIToken
|
||||
from .asset import FileAsset
|
||||
from .base import BaseModel
|
||||
from .cycle import Cycle, CycleFavorite, CycleIssue, CycleUserProperties
|
||||
from .cycle import (
|
||||
Cycle,
|
||||
CycleFavorite,
|
||||
CycleIssue,
|
||||
CycleUserProperties,
|
||||
CycleAnalytics,
|
||||
CycleUpdates,
|
||||
CycleUpdateReaction,
|
||||
CycleIssueStateProgress,
|
||||
)
|
||||
from .dashboard import Dashboard, DashboardWidget, Widget
|
||||
from .deploy_board import DeployBoard
|
||||
from .estimate import Estimate, EstimatePoint
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
# Python Imports
|
||||
import pytz
|
||||
|
||||
# Django imports
|
||||
from django.conf import settings
|
||||
from django.db import models
|
||||
@@ -55,10 +58,12 @@ class Cycle(ProjectBaseModel):
|
||||
description = models.TextField(
|
||||
verbose_name="Cycle Description", blank=True
|
||||
)
|
||||
start_date = models.DateField(
|
||||
start_date = models.DateTimeField(
|
||||
verbose_name="Start Date", blank=True, null=True
|
||||
)
|
||||
end_date = models.DateField(verbose_name="End Date", blank=True, null=True)
|
||||
end_date = models.DateTimeField(
|
||||
verbose_name="End Date", blank=True, null=True
|
||||
)
|
||||
owned_by = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL,
|
||||
on_delete=models.CASCADE,
|
||||
@@ -71,6 +76,11 @@ class Cycle(ProjectBaseModel):
|
||||
progress_snapshot = models.JSONField(default=dict)
|
||||
archived_at = models.DateTimeField(null=True)
|
||||
logo_props = models.JSONField(default=dict)
|
||||
# timezone
|
||||
USER_TIMEZONE_CHOICES = tuple(zip(pytz.all_timezones, pytz.all_timezones))
|
||||
user_timezone = models.CharField(
|
||||
max_length=255, default="UTC", choices=USER_TIMEZONE_CHOICES
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Cycle"
|
||||
@@ -176,3 +186,140 @@ class CycleUserProperties(ProjectBaseModel):
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.cycle.name} {self.user.email}"
|
||||
|
||||
|
||||
class TypeEnum(models.TextChoices):
|
||||
ADDED = "ADDED", "Added"
|
||||
UPDATED = "UPDATED", "Updated"
|
||||
REMOVED = "REMOVED", "Removed"
|
||||
TRANSFER = "TRANSFER", "Transfer"
|
||||
|
||||
|
||||
class CycleIssueStateProgress(ProjectBaseModel):
|
||||
cycle = models.ForeignKey(
|
||||
"db.Cycle",
|
||||
on_delete=models.DO_NOTHING,
|
||||
related_name="cycle_issue_state_progress",
|
||||
)
|
||||
state = models.ForeignKey(
|
||||
"db.State",
|
||||
on_delete=models.DO_NOTHING,
|
||||
related_name="cycle_issue_state_progress",
|
||||
)
|
||||
issue = models.ForeignKey(
|
||||
"db.Issue",
|
||||
on_delete=models.DO_NOTHING,
|
||||
related_name="cycle_issue_state_progress",
|
||||
)
|
||||
state_group = models.CharField(max_length=255)
|
||||
type = models.CharField(
|
||||
max_length=30,
|
||||
choices=TypeEnum.choices,
|
||||
)
|
||||
estimate_id = models.UUIDField(null=True)
|
||||
estimate_value = models.FloatField(null=True)
|
||||
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Cycle Issue State Progress"
|
||||
verbose_name_plural = "Cycle Issue State Progress"
|
||||
db_table = "cycle_issue_state_progress"
|
||||
ordering = ("-created_at",)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.cycle.name} {self.issue.name}"
|
||||
|
||||
|
||||
class CycleAnalytics(ProjectBaseModel):
|
||||
cycle = models.ForeignKey(
|
||||
"db.Cycle", on_delete=models.CASCADE, related_name="cycle_analytics"
|
||||
)
|
||||
date = models.DateField()
|
||||
data = models.JSONField(default=dict)
|
||||
|
||||
total_issues = models.FloatField(default=0)
|
||||
total_estimate_points = models.FloatField(default=0)
|
||||
|
||||
# state group wise distribution
|
||||
backlog_issues = models.FloatField(default=0)
|
||||
unstarted_issues = models.FloatField(default=0)
|
||||
started_issues = models.FloatField(default=0)
|
||||
completed_issues = models.FloatField(default=0)
|
||||
cancelled_issues = models.FloatField(default=0)
|
||||
|
||||
backlog_estimate_points = models.FloatField(default=0)
|
||||
unstarted_estimate_points = models.FloatField(default=0)
|
||||
started_estimate_points = models.FloatField(default=0)
|
||||
completed_estimate_points = models.FloatField(default=0)
|
||||
cancelled_estimate_points = models.FloatField(default=0)
|
||||
|
||||
class Meta:
|
||||
unique_together = ["cycle", "date"]
|
||||
verbose_name = "Cycle Analytics"
|
||||
verbose_name_plural = "Cycle Analytics"
|
||||
db_table = "cycle_analytics"
|
||||
ordering = ("-created_at",)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.user.email} <{self.cycle.name}>"
|
||||
|
||||
|
||||
class UpdatesEnum(models.TextChoices):
|
||||
ONTRACK = "ONTRACK", "On Track"
|
||||
OFFTRACK = "OFFTRACK", "Off Track"
|
||||
AT_RISK = "AT_RISK", "At Risk"
|
||||
STARTED = "STARTED", "Started"
|
||||
SCOPE_INCREASED = "SCOPE_INCREASED", "Scope Increased"
|
||||
SCOPE_DECREASED = "SCOPE_DECREASED", "Scope Decreased"
|
||||
|
||||
|
||||
class CycleUpdates(ProjectBaseModel):
|
||||
cycle = models.ForeignKey(
|
||||
"db.Cycle", on_delete=models.CASCADE, related_name="cycle_updates"
|
||||
)
|
||||
description = models.TextField(blank=True)
|
||||
status = models.CharField(
|
||||
max_length=30,
|
||||
choices=UpdatesEnum.choices,
|
||||
)
|
||||
completed_issues = models.FloatField(default=0)
|
||||
total_issues = models.FloatField(default=0)
|
||||
total_estimate_points = models.FloatField(default=0)
|
||||
completed_estimate_points = models.FloatField(default=0)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Cycle Updates"
|
||||
verbose_name_plural = "Cycle Updates"
|
||||
db_table = "cycle_updates"
|
||||
ordering = ("-created_at",)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.cycle.name}"
|
||||
|
||||
|
||||
class CycleUpdateReaction(ProjectBaseModel):
|
||||
cycle = models.ForeignKey(
|
||||
"db.Cycle",
|
||||
on_delete=models.CASCADE,
|
||||
related_name="cycle_update_reactions",
|
||||
)
|
||||
update = models.ForeignKey(
|
||||
"db.CycleUpdates",
|
||||
on_delete=models.CASCADE,
|
||||
related_name="cycle_update_reactions",
|
||||
)
|
||||
reaction = models.CharField(max_length=20)
|
||||
actor = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL,
|
||||
on_delete=models.CASCADE,
|
||||
related_name="cycle_update_reactions",
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Cycle Update Reaction"
|
||||
verbose_name_plural = "Cycle Update Reactions"
|
||||
db_table = "cycle_update_reactions"
|
||||
ordering = ("-created_at",)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.actor.email} <{self.cycle.name}>"
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
# Python imports
|
||||
import pytz
|
||||
from uuid import uuid4
|
||||
|
||||
# Django imports
|
||||
@@ -119,6 +120,11 @@ class Project(BaseModel):
|
||||
related_name="default_state",
|
||||
)
|
||||
archived_at = models.DateTimeField(null=True)
|
||||
# timezone
|
||||
USER_TIMEZONE_CHOICES = tuple(zip(pytz.all_timezones, pytz.all_timezones))
|
||||
user_timezone = models.CharField(
|
||||
max_length=255, default="UTC", choices=USER_TIMEZONE_CHOICES
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
"""Return name of the project"""
|
||||
|
||||
@@ -279,6 +279,7 @@ CELERY_IMPORTS = (
|
||||
"plane.bgtasks.file_asset_task",
|
||||
"plane.bgtasks.email_notification_task",
|
||||
"plane.bgtasks.api_logs_task",
|
||||
"plane.bgtasks.cycle_issue_state_progress_task",
|
||||
# management tasks
|
||||
"plane.bgtasks.dummy_data_task",
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user