Merge pull request #392 from taigaio/issue/3019/duplicate-entries-in-profile-timelines
Issue/3019/duplicate entries in profile timelines and moreremotes/origin/enhancement/email-actions
commit
d5c800d3e5
|
@ -10,6 +10,7 @@
|
|||
- Fix the compatibility with BitBucket webhooks and add issues and issues comments integration.
|
||||
- Add custom videoconference system.
|
||||
- Add support for comments in the Gitlab webhooks integration.
|
||||
- Now profile timelines only show content about the objects (US/Tasks/Issues/Wiki pages) you are involved.
|
||||
|
||||
### Misc
|
||||
- API: Mixin fields 'users', 'members' and 'memberships' in ProjectDetailSerializer
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
|
||||
# Copyright (C) 2014 Jesús Espino <jespinog@gmail.com>
|
||||
# Copyright (C) 2014 David Barragán <bameda@dbarragan.com>
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# 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/>.
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.test.utils import override_settings
|
||||
from django.core.management import call_command
|
||||
|
||||
from taiga.projects.models import Project
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = 'Regenerate projects timeline iterating per project'
|
||||
|
||||
@override_settings(DEBUG=False)
|
||||
def handle(self, *args, **options):
|
||||
total = Project.objects.count()
|
||||
|
||||
for count,project in enumerate(Project.objects.order_by("id")):
|
||||
print("""***********************************
|
||||
%s/%s %s
|
||||
***********************************"""%(count+1, total, project.name))
|
||||
call_command("rebuild_timeline", project=project.id)
|
|
@ -39,38 +39,44 @@ def _push_to_timeline(*args, **kwargs):
|
|||
|
||||
def _push_to_timelines(project, user, obj, event_type, created_datetime, extra_data={}):
|
||||
if project is not None:
|
||||
# Project timeline
|
||||
# Actions related with a project
|
||||
|
||||
## Project timeline
|
||||
_push_to_timeline(project, obj, event_type, created_datetime,
|
||||
namespace=build_project_namespace(project),
|
||||
extra_data=extra_data)
|
||||
|
||||
# User timeline
|
||||
_push_to_timeline(user, obj, event_type, created_datetime,
|
||||
namespace=build_user_namespace(user),
|
||||
extra_data=extra_data)
|
||||
## User profile timelines
|
||||
## - Me
|
||||
related_people = User.objects.filter(id=user.id)
|
||||
|
||||
# Calculating related people
|
||||
related_people = User.objects.none()
|
||||
## - Owner
|
||||
if hasattr(obj, "owner_id") and obj.owner_id:
|
||||
related_people |= User.objects.filter(id=obj.owner_id)
|
||||
|
||||
# Assigned to
|
||||
if hasattr(obj, "assigned_to") and obj.assigned_to and user != obj.assigned_to:
|
||||
related_people |= User.objects.filter(id=obj.assigned_to.id)
|
||||
## - Assigned to
|
||||
if hasattr(obj, "assigned_to_id") and obj.assigned_to_id:
|
||||
related_people |= User.objects.filter(id=obj.assigned_to_id)
|
||||
|
||||
# Watchers
|
||||
watchers = hasattr(obj, "watchers") and obj.watchers.exclude(id=user.id) or User.objects.none()
|
||||
if watchers:
|
||||
related_people |= watchers
|
||||
## - Watchers
|
||||
watchers = getattr(obj, "watchers", None)
|
||||
if watchers:
|
||||
related_people |= obj.watchers.all()
|
||||
|
||||
if project is not None:
|
||||
# Team
|
||||
team_members_ids = project.memberships.filter(user__isnull=False).values_list("id", flat=True)
|
||||
team = User.objects.filter(id__in=team_members_ids)
|
||||
related_people |= team
|
||||
## - Exclude inactive and system users and remove duplicate
|
||||
related_people = related_people.exclude(is_active=False)
|
||||
related_people = related_people.exclude(is_system=True)
|
||||
related_people = related_people.distinct()
|
||||
|
||||
_push_to_timeline(related_people, obj, event_type, created_datetime,
|
||||
namespace=build_user_namespace(user),
|
||||
extra_data=extra_data)
|
||||
else:
|
||||
# Actions not related with a project
|
||||
## - Me
|
||||
_push_to_timeline(user, obj, event_type, created_datetime,
|
||||
namespace=build_user_namespace(user),
|
||||
extra_data=extra_data)
|
||||
|
||||
|
||||
def on_new_history_entry(sender, instance, created, **kwargs):
|
||||
|
@ -110,27 +116,37 @@ def on_new_history_entry(sender, instance, created, **kwargs):
|
|||
|
||||
|
||||
def create_membership_push_to_timeline(sender, instance, **kwargs):
|
||||
# Creating new membership with associated user
|
||||
# If the user is the project owner we don't do anything because that info will
|
||||
# be shown in created project timeline entry
|
||||
"""
|
||||
Creating new membership with associated user. If the user is the project owner we don't
|
||||
do anything because that info will be shown in created project timeline entry
|
||||
|
||||
@param sender: Membership model
|
||||
@param instance: Membership object
|
||||
"""
|
||||
|
||||
# We shown in created project timeline entry
|
||||
if not instance.pk and instance.user and instance.user != instance.project.owner:
|
||||
created_datetime = instance.created_at
|
||||
_push_to_timelines(instance.project, instance.user, instance, "create", created_datetime)
|
||||
|
||||
#Updating existing membership
|
||||
# Updating existing membership
|
||||
elif instance.pk:
|
||||
prev_instance = sender.objects.get(pk=instance.pk)
|
||||
if instance.user != prev_instance.user:
|
||||
created_datetime = timezone.now()
|
||||
# The new member
|
||||
_push_to_timelines(instance.project, instance.user, instance, "create", created_datetime)
|
||||
# If we are updating the old user is removed from project
|
||||
if prev_instance.user:
|
||||
_push_to_timelines(instance.project,
|
||||
prev_instance.user,
|
||||
prev_instance,
|
||||
"delete",
|
||||
created_datetime)
|
||||
try:
|
||||
prev_instance = sender.objects.get(pk=instance.pk)
|
||||
if instance.user != prev_instance.user:
|
||||
created_datetime = timezone.now()
|
||||
# The new member
|
||||
_push_to_timelines(instance.project, instance.user, instance, "create", created_datetime)
|
||||
# If we are updating the old user is removed from project
|
||||
if prev_instance.user:
|
||||
_push_to_timelines(instance.project,
|
||||
prev_instance.user,
|
||||
prev_instance,
|
||||
"delete",
|
||||
created_datetime)
|
||||
except sender.DoesNotExist:
|
||||
# This happens with some tests, when a membership is created with a concrete id
|
||||
pass
|
||||
|
||||
|
||||
def delete_membership_push_to_timeline(sender, instance, **kwargs):
|
||||
|
|
|
@ -377,7 +377,7 @@ def test_owner_user_story_timeline():
|
|||
|
||||
def test_assigned_to_user_story_timeline():
|
||||
membership = factories.MembershipFactory.create()
|
||||
user_story = factories.UserStoryFactory.create(subject="test us timeline", assigned_to=membership.user)
|
||||
user_story = factories.UserStoryFactory.create(subject="test us timeline", assigned_to=membership.user, project=membership.project)
|
||||
history_services.take_snapshot(user_story, user=user_story.owner)
|
||||
user_timeline = service.get_profile_timeline(user_story.assigned_to)
|
||||
assert user_timeline[0].event_type == "userstories.userstory.create"
|
||||
|
@ -386,20 +386,22 @@ def test_assigned_to_user_story_timeline():
|
|||
|
||||
def test_watchers_to_user_story_timeline():
|
||||
membership = factories.MembershipFactory.create()
|
||||
user_story = factories.UserStoryFactory.create(subject="test us timeline")
|
||||
user_story = factories.UserStoryFactory.create(subject="test us timeline", project=membership.project)
|
||||
user_story.watchers.add(membership.user)
|
||||
history_services.take_snapshot(user_story, user=user_story.owner)
|
||||
user_timeline = service.get_profile_timeline(membership.user)
|
||||
assert user_timeline[0].event_type == "userstories.userstory.create"
|
||||
assert user_timeline[0].data["userstory"]["subject"] == "test us timeline"
|
||||
|
||||
def test_user_data_for_system_users():
|
||||
|
||||
def test_user_data_for_non_system_users():
|
||||
user_story = factories.UserStoryFactory.create(subject="test us timeline")
|
||||
history_services.take_snapshot(user_story, user=user_story.owner)
|
||||
project_timeline = service.get_project_timeline(user_story.project)
|
||||
serialized_obj = TimelineSerializer(project_timeline[0])
|
||||
serialized_obj.data["data"]["user"]["is_profile_visible"] = True
|
||||
|
||||
|
||||
def test_user_data_for_system_users():
|
||||
user_story = factories.UserStoryFactory.create(subject="test us timeline")
|
||||
user_story.owner.is_system = True
|
||||
|
@ -409,6 +411,7 @@ def test_user_data_for_system_users():
|
|||
serialized_obj = TimelineSerializer(project_timeline[0])
|
||||
serialized_obj.data["data"]["user"]["is_profile_visible"] = False
|
||||
|
||||
|
||||
def test_user_data_for_unactived_users():
|
||||
user_story = factories.UserStoryFactory.create(subject="test us timeline")
|
||||
user_story.owner.cancel()
|
||||
|
@ -418,3 +421,25 @@ def test_user_data_for_unactived_users():
|
|||
serialized_obj = TimelineSerializer(project_timeline[0])
|
||||
serialized_obj.data["data"]["user"]["is_profile_visible"] = False
|
||||
serialized_obj.data["data"]["user"]["username"] = "deleted-user"
|
||||
|
||||
def test_timeline_error_use_member_ids_instead_of_memberships_ids():
|
||||
user_story = factories.UserStoryFactory.create(subject="test error use member ids instead of "
|
||||
"memberships ids")
|
||||
|
||||
member_user = user_story.owner
|
||||
external_user = factories.UserFactory.create()
|
||||
|
||||
membership = factories.MembershipFactory.create(project=user_story.project,
|
||||
user=member_user,
|
||||
id=external_user.id)
|
||||
|
||||
history_services.take_snapshot(user_story, user=member_user)
|
||||
|
||||
user_timeline = service.get_profile_timeline(member_user)
|
||||
assert len(user_timeline) == 2
|
||||
assert user_timeline[0].event_type == "userstories.userstory.create"
|
||||
assert user_timeline[1].event_type == "users.user.create"
|
||||
|
||||
external_user_timeline = service.get_profile_timeline(external_user)
|
||||
assert len(external_user_timeline) == 1
|
||||
assert external_user_timeline[0].event_type == "users.user.create"
|
||||
|
|
Loading…
Reference in New Issue