Working in history and timeline for epics (initial version)

remotes/origin/issue/4795/notification_even_they_are_disabled
David Barragán Merino 2016-07-01 12:37:19 +02:00
parent 389a18026b
commit a7c262ffdc
16 changed files with 173 additions and 50 deletions

View File

@ -168,6 +168,11 @@ class HistoryViewSet(ReadOnlyListViewSet):
return self.response_for_queryset(qs) return self.response_for_queryset(qs)
class EpicHistory(HistoryViewSet):
content_type = "epics.epic"
permission_classes = (permissions.EpicHistoryPermission,)
class UserStoryHistory(HistoryViewSet): class UserStoryHistory(HistoryViewSet):
content_type = "userstories.userstory" content_type = "userstories.userstory"
permission_classes = (permissions.UserStoryHistoryPermission,) permission_classes = (permissions.UserStoryHistoryPermission,)

View File

@ -106,6 +106,17 @@ def milestone_values(diff):
return values return values
def epic_values(diff):
values = _common_users_values(diff)
if "status" in diff:
values["status"] = _get_us_status_values(diff["status"])
# TODO EPICS: What happen with usr stories?
return values
def userstory_values(diff): def userstory_values(diff):
values = _common_users_values(diff) values = _common_users_values(diff)
@ -190,6 +201,18 @@ def extract_attachments(obj) -> list:
"order": attach.order} "order": attach.order}
@as_tuple
def extract_epic_custom_attributes(obj) -> list:
with suppress(ObjectDoesNotExist):
custom_attributes_values = obj.custom_attributes_values.attributes_values
for attr in obj.project.epiccustomattributes.all():
with suppress(KeyError):
value = custom_attributes_values[str(attr.id)]
yield {"id": attr.id,
"name": attr.name,
"value": value}
@as_tuple @as_tuple
def extract_user_story_custom_attributes(obj) -> list: def extract_user_story_custom_attributes(obj) -> list:
with suppress(ObjectDoesNotExist): with suppress(ObjectDoesNotExist):
@ -235,6 +258,7 @@ def project_freezer(project) -> dict:
"total_milestones", "total_milestones",
"total_story_points", "total_story_points",
"tags", "tags",
"is_epics_activated",
"is_backlog_activated", "is_backlog_activated",
"is_kanban_activated", "is_kanban_activated",
"is_wiki_activated", "is_wiki_activated",
@ -256,6 +280,31 @@ def milestone_freezer(milestone) -> dict:
return snapshot return snapshot
def epic_freezer(epic) -> dict:
snapshot = {
"ref": epic.ref,
"owner": epic.owner_id,
"status": epic.status.id if epic.status else None,
"is_closed": epic.is_closed,
"finish_date": str(epic.finish_date),
"epics_order": epic.epics_order,
"subject": epic.subject,
"description": epic.description,
"description_html": mdrender(epic.project, epic.description),
"assigned_to": epic.assigned_to_id,
"client_requirement": epic.client_requirement,
"team_requirement": epic.team_requirement,
"attachments": extract_attachments(epic),
"tags": epic.tags,
"is_blocked": epic.is_blocked,
"blocked_note": epic.blocked_note,
"blocked_note_html": mdrender(epic.project, epic.blocked_note),
"custom_attributes": extract_epic_custom_attributes(epic),
}
return snapshot
def userstory_freezer(us) -> dict: def userstory_freezer(us) -> dict:
rp_cls = apps.get_model("userstories", "RolePoints") rp_cls = apps.get_model("userstories", "RolePoints")
rpqsd = rp_cls.objects.filter(user_story=us) rpqsd = rp_cls.objects.filter(user_story=us)

View File

@ -42,6 +42,14 @@ class IsCommentProjectAdmin(PermissionComponent):
return is_project_admin(request.user, project) return is_project_admin(request.user, project)
class EpicHistoryPermission(TaigaResourcePermission):
retrieve_perms = HasProjectPerm('view_project')
edit_comment_perms = IsCommentProjectAdmin() | IsCommentOwner()
delete_comment_perms = IsCommentProjectAdmin() | IsCommentOwner()
undelete_comment_perms = IsCommentProjectAdmin() | IsCommentDeleter()
comment_versions_perms = IsCommentProjectAdmin() | IsCommentOwner()
class UserStoryHistoryPermission(TaigaResourcePermission): class UserStoryHistoryPermission(TaigaResourcePermission):
retrieve_perms = HasProjectPerm('view_project') retrieve_perms = HasProjectPerm('view_project')
edit_comment_perms = IsCommentProjectAdmin() | IsCommentOwner() edit_comment_perms = IsCommentProjectAdmin() | IsCommentOwner()

View File

@ -50,6 +50,7 @@ from .models import HistoryType
# Freeze implementatitions # Freeze implementatitions
from .freeze_impl import project_freezer from .freeze_impl import project_freezer
from .freeze_impl import milestone_freezer from .freeze_impl import milestone_freezer
from .freeze_impl import epic_freezer
from .freeze_impl import userstory_freezer from .freeze_impl import userstory_freezer
from .freeze_impl import issue_freezer from .freeze_impl import issue_freezer
from .freeze_impl import task_freezer from .freeze_impl import task_freezer
@ -58,6 +59,7 @@ from .freeze_impl import wikipage_freezer
from .freeze_impl import project_values from .freeze_impl import project_values
from .freeze_impl import milestone_values from .freeze_impl import milestone_values
from .freeze_impl import epic_values
from .freeze_impl import userstory_values from .freeze_impl import userstory_values
from .freeze_impl import issue_values from .freeze_impl import issue_values
from .freeze_impl import task_values from .freeze_impl import task_values
@ -337,10 +339,7 @@ def take_snapshot(obj: object, *, comment: str="", user=None, delete: bool=False
# If diff and comment are empty, do # If diff and comment are empty, do
# not create empty history entry # not create empty history entry
if (not fdiff.diff and not comment if (not fdiff.diff and not comment and old_fobj is not None and entry_type != HistoryType.delete):
and old_fobj is not None
and entry_type != HistoryType.delete):
return None return None
fvals = make_diff_values(typename, fdiff) fvals = make_diff_values(typename, fdiff)
@ -394,8 +393,10 @@ def prefetch_owners_in_history_queryset(qs):
return qs return qs
# Freeze & value register
register_freeze_implementation("projects.project", project_freezer) register_freeze_implementation("projects.project", project_freezer)
register_freeze_implementation("milestones.milestone", milestone_freezer,) register_freeze_implementation("milestones.milestone", milestone_freezer,)
register_freeze_implementation("epics.epic", epic_freezer)
register_freeze_implementation("userstories.userstory", userstory_freezer) register_freeze_implementation("userstories.userstory", userstory_freezer)
register_freeze_implementation("issues.issue", issue_freezer) register_freeze_implementation("issues.issue", issue_freezer)
register_freeze_implementation("tasks.task", task_freezer) register_freeze_implementation("tasks.task", task_freezer)
@ -403,6 +404,7 @@ register_freeze_implementation("wiki.wikipage", wikipage_freezer)
register_values_implementation("projects.project", project_values) register_values_implementation("projects.project", project_values)
register_values_implementation("milestones.milestone", milestone_values) register_values_implementation("milestones.milestone", milestone_values)
register_values_implementation("epics.epic", epic_values)
register_values_implementation("userstories.userstory", userstory_values) register_values_implementation("userstories.userstory", userstory_values)
register_values_implementation("issues.issue", issue_values) register_values_implementation("issues.issue", issue_values)
register_values_implementation("tasks.task", task_values) register_values_implementation("tasks.task", task_values)

View File

@ -30,15 +30,6 @@ from taiga.permissions.services import calculate_permissions
from taiga.permissions.services import is_project_admin, is_project_owner from taiga.permissions.services import is_project_admin, is_project_owner
from . import services from . import services
<<<<<<< HEAD
=======
from .custom_attributes.serializers import EpicCustomAttributeSerializer
from .custom_attributes.serializers import UserStoryCustomAttributeSerializer
from .custom_attributes.serializers import TaskCustomAttributeSerializer
from .custom_attributes.serializers import IssueCustomAttributeSerializer
from .likes.mixins.serializers import FanResourceSerializerMixin
from .mixins.serializers import ValidateDuplicatedNameInProjectMixin
>>>>>>> 5f3559d... Epic custom attributes values
from .notifications.choices import NotifyLevel from .notifications.choices import NotifyLevel
@ -222,6 +213,7 @@ class ProjectSerializer(serializers.LightSerializer):
members = MethodField() members = MethodField()
total_milestones = Field() total_milestones = Field()
total_story_points = Field() total_story_points = Field()
is_epics_activated = Field()
is_backlog_activated = Field() is_backlog_activated = Field()
is_kanban_activated = Field() is_kanban_activated = Field()
is_wiki_activated = Field() is_wiki_activated = Field()
@ -249,6 +241,7 @@ class ProjectSerializer(serializers.LightSerializer):
tags = Field() tags = Field()
tags_colors = MethodField() tags_colors = MethodField()
default_epic_status = Field(attr="default_epic_status_id")
default_points = Field(attr="default_points_id") default_points = Field(attr="default_points_id")
default_us_status = Field(attr="default_us_status_id") default_us_status = Field(attr="default_us_status_id")
default_task_status = Field(attr="default_task_status_id") default_task_status = Field(attr="default_task_status_id")
@ -300,14 +293,13 @@ class ProjectSerializer(serializers.LightSerializer):
def get_my_permissions(self, obj): def get_my_permissions(self, obj):
if "request" in self.context: if "request" in self.context:
user = self.context["request"].user user = self.context["request"].user
return calculate_permissions( return calculate_permissions(is_authenticated=user.is_authenticated(),
is_authenticated=user.is_authenticated(), is_superuser=user.is_superuser,
is_superuser=user.is_superuser, is_member=self.get_i_am_member(obj),
is_member=self.get_i_am_member(obj), is_admin=self.get_i_am_admin(obj),
is_admin=self.get_i_am_admin(obj), role_permissions=obj.my_role_permissions_attr,
role_permissions=obj.my_role_permissions_attr, anon_permissions=obj.anon_permissions,
anon_permissions=obj.anon_permissions, public_permissions=obj.public_permissions)
public_permissions=obj.public_permissions)
return [] return []
def get_owner(self, obj): def get_owner(self, obj):
@ -441,10 +433,8 @@ class ProjectDetailSerializer(ProjectSerializer):
return len(obj.members_attr) return len(obj.members_attr)
def get_is_out_of_owner_limits(self, obj): def get_is_out_of_owner_limits(self, obj):
assert (hasattr(obj, "private_projects_same_owner_attr"), assert hasattr(obj, "private_projects_same_owner_attr"), "instance must have a private_projects_same_owner_attr attribute"
"instance must have a private_projects_same_owner_attr attribute" assert hasattr(obj, "public_projects_same_owner_attr"), "instance must have a public_projects_same_owner_attr attribute"
assert (hasattr(obj, "public_projects_same_owner_attr"),
"instance must have a public_projects_same_owner_attr attribute"
return services.check_if_project_is_out_of_owner_limits( return services.check_if_project_is_out_of_owner_limits(
obj, obj,
current_memberships=self.get_total_memberships(obj), current_memberships=self.get_total_memberships(obj),
@ -453,10 +443,8 @@ class ProjectDetailSerializer(ProjectSerializer):
) )
def get_is_private_extra_info(self, obj): def get_is_private_extra_info(self, obj):
assert (hasattr(obj, "private_projects_same_owner_attr"), assert hasattr(obj, "private_projects_same_owner_attr"), "instance must have a private_projects_same_owner_attr attribute"
"instance must have a private_projects_same_owner_attr attribute" assert hasattr(obj, "public_projects_same_owner_attr"), "instance must have a public_projects_same_owner_attr attribute"
assert (hasattr(obj, "public_projects_same_owner_attr"),
"instance must have a public_projects_same_owner_attr attribute"
return services.check_if_project_privacity_can_be_changed( return services.check_if_project_privacity_can_be_changed(
obj, obj,
current_memberships=self.get_total_memberships(obj), current_memberships=self.get_total_memberships(obj),

View File

@ -196,11 +196,13 @@ router.register(r"wiki-links", WikiLinkViewSet,
# History & Components # History & Components
from taiga.projects.history.api import EpicHistory
from taiga.projects.history.api import UserStoryHistory from taiga.projects.history.api import UserStoryHistory
from taiga.projects.history.api import TaskHistory from taiga.projects.history.api import TaskHistory
from taiga.projects.history.api import IssueHistory from taiga.projects.history.api import IssueHistory
from taiga.projects.history.api import WikiHistory from taiga.projects.history.api import WikiHistory
router.register(r"history/epic", EpicHistory, base_name="epic-history")
router.register(r"history/userstory", UserStoryHistory, base_name="userstory-history") router.register(r"history/userstory", UserStoryHistory, base_name="userstory-history")
router.register(r"history/task", TaskHistory, base_name="task-history") router.register(r"history/task", TaskHistory, base_name="task-history")
router.register(r"history/issue", IssueHistory, base_name="issue-history") router.register(r"history/issue", IssueHistory, base_name="issue-history")

View File

@ -85,6 +85,7 @@ class TimelineViewSet(ReadOnlyListViewSet):
event_type::text = ANY('{issues.issue.change, event_type::text = ANY('{issues.issue.change,
tasks.task.change, tasks.task.change,
userstories.userstory.change, userstories.userstory.change,
epics.epic.change,
wiki.wikipage.change}'::text[]) wiki.wikipage.change}'::text[])
) )
"""]) """])
@ -92,6 +93,7 @@ class TimelineViewSet(ReadOnlyListViewSet):
qs = qs.exclude(event_type__in=["issues.issue.delete", qs = qs.exclude(event_type__in=["issues.issue.delete",
"tasks.task.delete", "tasks.task.delete",
"userstories.userstory.delete", "userstories.userstory.delete",
"epics.epic.delete",
"wiki.wikipage.delete", "wiki.wikipage.delete",
"projects.project.change"]) "projects.project.change"])

View File

@ -33,8 +33,8 @@ class TimelineAppConfig(AppConfig):
sender=apps.get_model("history", "HistoryEntry"), sender=apps.get_model("history", "HistoryEntry"),
dispatch_uid="timeline") dispatch_uid="timeline")
signals.post_save.connect(handlers.create_membership_push_to_timeline, signals.post_save.connect(handlers.create_membership_push_to_timeline,
sender=apps.get_model("projects", "Membership")) sender=apps.get_model("projects", "Membership"))
signals.pre_delete.connect(handlers.delete_membership_push_to_timeline, signals.pre_delete.connect(handlers.delete_membership_push_to_timeline,
sender=apps.get_model("projects", "Membership")) sender=apps.get_model("projects", "Membership"))
signals.post_save.connect(handlers.create_user_push_to_timeline, signals.post_save.connect(handlers.create_user_push_to_timeline,
sender=get_user_model()) sender=get_user_model())

View File

@ -25,8 +25,9 @@ from django.core.management.base import BaseCommand
from django.db.models import Model from django.db.models import Model
from django.test.utils import override_settings from django.test.utils import override_settings
from taiga.timeline.service import (_get_impl_key_from_model, from taiga.timeline.service import _get_impl_key_from_model,
_timeline_impl_map, extract_user_info) from taiga.timeline.service import _timeline_impl_map,
from taiga.timeline.service import extract_user_info)
from taiga.timeline.models import Timeline from taiga.timeline.models import Timeline
from taiga.timeline.signals import _push_to_timelines from taiga.timeline.signals import _push_to_timelines
@ -54,7 +55,8 @@ class BulkCreator(object):
bulk_creator = BulkCreator() bulk_creator = BulkCreator()
def custom_add_to_object_timeline(obj:object, instance:object, event_type:str, created_datetime:object, namespace:str="default", extra_data:dict={}): def custom_add_to_object_timeline(obj:object, instance:object, event_type:str, created_datetime:object,
namespace:str="default", extra_data:dict={}):
assert isinstance(obj, Model), "obj must be a instance of Model" assert isinstance(obj, Model), "obj must be a instance of Model"
assert isinstance(instance, Model), "instance must be a instance of Model" assert isinstance(instance, Model), "instance must be a instance of Model"
event_type_key = _get_impl_key_from_model(instance.__class__, event_type) event_type_key = _get_impl_key_from_model(instance.__class__, event_type)

View File

@ -40,7 +40,9 @@ def update_timeline(initial_date, final_date):
print("Generating tasks indexed by id dict") print("Generating tasks indexed by id dict")
task_ids = timelines.values_list("object_id", flat=True) task_ids = timelines.values_list("object_id", flat=True)
tasks_per_id = {task.id: task for task in Task.objects.filter(id__in=task_ids).select_related("user_story").iterator()}
tasks_iterator = Task.objects.filter(id__in=task_ids).select_related("user_story").iterator()
tasks_per_id = {task.id: task for task in tasks_iterator}
del task_ids del task_ids
counter = 1 counter = 1

View File

@ -58,7 +58,8 @@ class BulkCreator(object):
bulk_creator = BulkCreator() bulk_creator = BulkCreator()
def custom_add_to_object_timeline(obj:object, instance:object, event_type:str, created_datetime:object, namespace:str="default", extra_data:dict={}): def custom_add_to_object_timeline(obj:object, instance:object, event_type:str, created_datetime:object,
namespace:str="default", extra_data:dict={}):
assert isinstance(obj, Model), "obj must be a instance of Model" assert isinstance(obj, Model), "obj must be a instance of Model"
assert isinstance(instance, Model), "instance must be a instance of Model" assert isinstance(instance, Model), "instance must be a instance of Model"
event_type_key = _get_impl_key_from_model(instance.__class__, event_type) event_type_key = _get_impl_key_from_model(instance.__class__, event_type)
@ -102,11 +103,13 @@ def generate_timeline(initial_date, final_date, project_id):
if project_id: if project_id:
project = Project.objects.get(id=project_id) project = Project.objects.get(id=project_id)
us_keys = ['userstories.userstory:%s'%(id) for id in project.user_stories.values_list("id", flat=True)] epic_keys = ['epics.epic:%s'%(id) for id in project.epics.values_list("id", flat=True)]
us_keys = ['userstories.userstory:%s'%(id) for id in project.user_stories.values_list("id",
flat=True)]
tasks_keys = ['tasks.task:%s'%(id) for id in project.tasks.values_list("id", flat=True)] tasks_keys = ['tasks.task:%s'%(id) for id in project.tasks.values_list("id", flat=True)]
issue_keys = ['issues.issue:%s'%(id) for id in project.issues.values_list("id", flat=True)] issue_keys = ['issues.issue:%s'%(id) for id in project.issues.values_list("id", flat=True)]
wiki_keys = ['wiki.wikipage:%s'%(id) for id in project.wiki_pages.values_list("id", flat=True)] wiki_keys = ['wiki.wikipage:%s'%(id) for id in project.wiki_pages.values_list("id", flat=True)]
keys = us_keys + tasks_keys + issue_keys + wiki_keys keys = epic_keys + us_keys + tasks_keys + issue_keys + wiki_keys
projects = projects.filter(id=project_id) projects = projects.filter(id=project_id)
history_entries = history_entries.filter(key__in=keys) history_entries = history_entries.filter(key__in=keys)
@ -121,7 +124,8 @@ def generate_timeline(initial_date, final_date, project_id):
"values_diff": {}, "values_diff": {},
"user": extract_user_info(project.owner), "user": extract_user_info(project.owner),
} }
_push_to_timelines(project, project.owner, project, "create", project.created_date, extra_data=extra_data) _push_to_timelines(project, project.owner, project, "create", project.created_date,
extra_data=extra_data)
del extra_data del extra_data
for historyEntry in history_entries.iterator(): for historyEntry in history_entries.iterator():

View File

@ -31,7 +31,7 @@ class Command(BaseCommand):
total = Project.objects.count() total = Project.objects.count()
for count,project in enumerate(Project.objects.order_by("id")): for count,project in enumerate(Project.objects.order_by("id")):
print("""*********************************** print("***********************************\n",
%s/%s %s " {}/{} {}\n".format(count+1, total, project.name),
***********************************"""%(count+1, total, project.name)) "***********************************")
call_command("rebuild_timeline", project=project.id) call_command("rebuild_timeline", project=project.id)

View File

@ -52,7 +52,8 @@ def build_project_namespace(project: object):
return "{0}:{1}".format("project", project.id) return "{0}:{1}".format("project", project.id)
def _add_to_object_timeline(obj: object, instance: object, event_type: str, created_datetime: object, namespace: str="default", extra_data: dict={}): def _add_to_object_timeline(obj: object, instance: object, event_type: str, created_datetime: object,
namespace: str="default", extra_data: dict={}):
assert isinstance(obj, Model), "obj must be a instance of Model" assert isinstance(obj, Model), "obj must be a instance of Model"
assert isinstance(instance, Model), "instance must be a instance of Model" assert isinstance(instance, Model), "instance must be a instance of Model"
from .models import Timeline from .models import Timeline
@ -74,12 +75,14 @@ def _add_to_object_timeline(obj: object, instance: object, event_type: str, crea
) )
def _add_to_objects_timeline(objects, instance: object, event_type: str, created_datetime: object, namespace: str="default", extra_data: dict={}): def _add_to_objects_timeline(objects, instance: object, event_type: str, created_datetime: object,
namespace: str="default", extra_data: dict={}):
for obj in objects: for obj in objects:
_add_to_object_timeline(obj, instance, event_type, created_datetime, namespace, extra_data) _add_to_object_timeline(obj, instance, event_type, created_datetime, namespace, extra_data)
def _push_to_timeline(objects, instance: object, event_type: str, created_datetime: object, namespace: str="default", extra_data: dict={}): def _push_to_timeline(objects, instance: object, event_type: str, created_datetime: object,
namespace: str="default", extra_data: dict={}):
if isinstance(objects, Model): if isinstance(objects, Model):
_add_to_object_timeline(objects, instance, event_type, created_datetime, namespace, extra_data) _add_to_object_timeline(objects, instance, event_type, created_datetime, namespace, extra_data)
elif isinstance(objects, QuerySet) or isinstance(objects, list): elif isinstance(objects, QuerySet) or isinstance(objects, list):
@ -89,7 +92,8 @@ def _push_to_timeline(objects, instance: object, event_type: str, created_dateti
@app.task @app.task
def push_to_timelines(project_id, user_id, obj_app_label, obj_model_name, obj_id, event_type, created_datetime, extra_data={}): def push_to_timelines(project_id, user_id, obj_app_label, obj_model_name, obj_id, event_type,
created_datetime, extra_data={}):
ObjModel = apps.get_model(obj_app_label, obj_model_name) ObjModel = apps.get_model(obj_app_label, obj_model_name)
try: try:
obj = ObjModel.objects.get(id=obj_id) obj = ObjModel.objects.get(id=obj_id)
@ -156,6 +160,7 @@ def filter_timeline_for_user(timeline, user):
content_types = { content_types = {
"view_project": ContentType.objects.get_by_natural_key("projects", "project"), "view_project": ContentType.objects.get_by_natural_key("projects", "project"),
"view_milestones": ContentType.objects.get_by_natural_key("milestones", "milestone"), "view_milestones": ContentType.objects.get_by_natural_key("milestones", "milestone"),
"view_epic": ContentType.objects.get_by_natural_key("epics", "epic"),
"view_us": ContentType.objects.get_by_natural_key("userstories", "userstory"), "view_us": ContentType.objects.get_by_natural_key("userstories", "userstory"),
"view_tasks": ContentType.objects.get_by_natural_key("tasks", "task"), "view_tasks": ContentType.objects.get_by_natural_key("tasks", "task"),
"view_issues": ContentType.objects.get_by_natural_key("issues", "issue"), "view_issues": ContentType.objects.get_by_natural_key("issues", "issue"),
@ -181,7 +186,8 @@ def filter_timeline_for_user(timeline, user):
if membership.is_admin: if membership.is_admin:
tl_filter |= Q(project=membership.project) tl_filter |= Q(project=membership.project)
else: else:
data_content_types = list(filter(None, [content_types.get(a, None) for a in membership.role.permissions])) data_content_types = list(filter(None, [content_types.get(a, None) for a in
membership.role.permissions]))
data_content_types.append(membership_content_type) data_content_types.append(membership_content_type)
tl_filter |= Q(project=membership.project, data_content_type__in=data_content_types) tl_filter |= Q(project=membership.project, data_content_type__in=data_content_types)
@ -252,6 +258,14 @@ def extract_milestone_info(instance):
} }
def extract_epic_info(instance):
return {
"id": instance.pk,
"ref": instance.ref,
"subject": instance.subject,
}
def extract_userstory_info(instance): def extract_userstory_info(instance):
return { return {
"id": instance.pk, "id": instance.pk,

View File

@ -36,9 +36,23 @@ def _push_to_timelines(project, user, obj, event_type, created_datetime, extra_d
ct = ContentType.objects.get_for_model(obj) ct = ContentType.objects.get_for_model(obj)
if settings.CELERY_ENABLED: if settings.CELERY_ENABLED:
connection.on_commit(lambda: push_to_timelines.delay(project_id, user.id, ct.app_label, ct.model, obj.id, event_type, created_datetime, extra_data=extra_data)) connection.on_commit(lambda: push_to_timelines.delay(project_id,
user.id,
ct.app_label,
ct.model,
obj.id,
event_type,
created_datetime,
extra_data=extra_data))
else: else:
push_to_timelines(project_id, user.id, ct.app_label, ct.model, obj.id, event_type, created_datetime, extra_data=extra_data) push_to_timelines(project_id,
user.id,
ct.app_label,
ct.model,
obj.id,
event_type,
created_datetime,
extra_data=extra_data)
def _clean_description_fields(values_diff): def _clean_description_fields(values_diff):

View File

@ -43,6 +43,18 @@ def milestone_timeline(instance, extra_data={}):
return result return result
@register_timeline_implementation("epics.epic", "create")
@register_timeline_implementation("epics.epic", "change")
@register_timeline_implementation("epics.epic", "delete")
def epic_timeline(instance, extra_data={}):
result ={
"epic": service.extract_epic_info(instance),
"project": service.extract_project_info(instance.project),
}
result.update(extra_data)
return result
@register_timeline_implementation("userstories.userstory", "create") @register_timeline_implementation("userstories.userstory", "create")
@register_timeline_implementation("userstories.userstory", "change") @register_timeline_implementation("userstories.userstory", "change")
@register_timeline_implementation("userstories.userstory", "delete") @register_timeline_implementation("userstories.userstory", "delete")

View File

@ -389,6 +389,16 @@ class IssueTypeFactory(Factory):
project = factory.SubFactory("tests.factories.ProjectFactory") project = factory.SubFactory("tests.factories.ProjectFactory")
class EpicCustomAttributeFactory(Factory):
class Meta:
model = "custom_attributes.EpicCustomAttribute"
strategy = factory.CREATE_STRATEGY
name = factory.Sequence(lambda n: "Epic Custom Attribute {}".format(n))
description = factory.Sequence(lambda n: "Description for Epic Custom Attribute {}".format(n))
project = factory.SubFactory("tests.factories.ProjectFactory")
class UserStoryCustomAttributeFactory(Factory): class UserStoryCustomAttributeFactory(Factory):
class Meta: class Meta:
model = "custom_attributes.UserStoryCustomAttribute" model = "custom_attributes.UserStoryCustomAttribute"
@ -419,6 +429,15 @@ class IssueCustomAttributeFactory(Factory):
project = factory.SubFactory("tests.factories.ProjectFactory") project = factory.SubFactory("tests.factories.ProjectFactory")
class EpicCustomAttributesValuesFactory(Factory):
class Meta:
model = "custom_attributes.EpicCustomAttributesValues"
strategy = factory.CREATE_STRATEGY
attributes_values = {}
epic = factory.SubFactory("tests.factories.EpicFactory")
class UserStoryCustomAttributesValuesFactory(Factory): class UserStoryCustomAttributesValuesFactory(Factory):
class Meta: class Meta:
model = "custom_attributes.UserStoryCustomAttributesValues" model = "custom_attributes.UserStoryCustomAttributesValues"