diff --git a/src/dw_design_system/dwds/components/cta.html b/src/dw_design_system/dwds/components/cta.html index e10d6529c..e720d066f 100644 --- a/src/dw_design_system/dwds/components/cta.html +++ b/src/dw_design_system/dwds/components/cta.html @@ -4,4 +4,9 @@ {% if description %}
{{ description }}
{% endif %} {{ extra_content }} + {% if footer_text %} + + {% endif %} diff --git a/src/peoplefinder/services/person.py b/src/peoplefinder/services/person.py index 92457e7e7..31f66526b 100644 --- a/src/peoplefinder/services/person.py +++ b/src/peoplefinder/services/person.py @@ -26,6 +26,7 @@ AuditLogService, ObjectRepr, ) +from peoplefinder.services.team import TeamService from peoplefinder.tasks import notify_user_about_profile_changes, person_update_notifier from peoplefinder.types import EditSections, ProfileSections from user.models import User @@ -299,6 +300,9 @@ def profile_updated( # Notify external services person_update_notifier.delay(person.id) + for team_id in person.roles.all().values_list("team__pk", flat=True).distinct(): + TeamService().clear_profile_completion_cache(team_id) + def profile_deletion_initiated( self, request: Optional[HttpRequest], person: Person, initiated_by: User ) -> None: diff --git a/src/peoplefinder/services/team.py b/src/peoplefinder/services/team.py index 3982e0728..64d030870 100644 --- a/src/peoplefinder/services/team.py +++ b/src/peoplefinder/services/team.py @@ -1,6 +1,7 @@ from typing import Iterator, Optional, TypedDict from django.contrib.postgres.aggregates import ArrayAgg +from django.core.cache import cache from django.db import connection, transaction from django.db.models import ( Case, @@ -16,7 +17,7 @@ from django.db.models.functions import Concat from django.utils.text import slugify -from peoplefinder.models import AuditLog, Team, TeamMember, TeamTree +from peoplefinder.models import AuditLog, Person, Team, TeamMember, TeamTree from peoplefinder.services.audit_log import ( AuditLogSerializer, AuditLogService, @@ -111,7 +112,7 @@ def update_team_parent(self, team: Team, parent: Team) -> None: [team.id, parent.id], ) - def get_all_child_teams(self, parent: Team) -> QuerySet: + def get_all_child_teams(self, parent: Team) -> QuerySet[Team]: """Return all child teams of the given parent team. Args: @@ -261,14 +262,10 @@ def can_team_be_deleted(self, team: Team) -> tuple[bool, list[str]]: reasons = [] sub_teams = self.get_all_child_teams(team) - if sub_teams: reasons.append("sub-teams") - has_members = TeamMember.active.filter( - Q(team=team) | Q(team__in=sub_teams) - ).exists() - + has_members = self.get_team_members(team).exists() if has_members: reasons.append("members") @@ -310,6 +307,48 @@ def team_deleted(self, team: Team, deleted_by: User) -> None: """ AuditLogService.log(AuditLog.Action.DELETE, deleted_by, team) + def get_team_members(self, team: Team) -> QuerySet[TeamMember]: + sub_teams = self.get_all_child_teams(team) + + return TeamMember.active.filter(Q(team=team) | Q(team__in=sub_teams)) + + def get_profile_completion_cache_key(self, team_pk: int) -> str: + return f"team_{team_pk}__profile_completion" + + def clear_profile_completion_cache(self, team_pk: int): + cache.delete(self.get_profile_completion_cache_key(team_pk)) + + def profile_completion(self, team: Team) -> float | None: + """ + Calculate the percentage of users in the team with 100% profile + completion. + + Returns: + float: A percentage + """ + cache_key = self.get_profile_completion_cache_key(team.pk) + if cached_value := cache.get(cache_key, None): + return cached_value + + # Get all people from all teams + people = Person.objects.filter( + id__in=Subquery(self.get_team_members(team).values("person_id")) + ) + completed_profiles = people.filter(profile_completion__gte=100) + + total_members = len(people) + total_completed_profiles = len(completed_profiles) + + if total_members == 0: + return None + + completed_profile_percent = total_completed_profiles / total_members + + # Cache the result for an hour. + timeout = 60 * 60 + cache.set(cache_key, completed_profile_percent, timeout) + return completed_profile_percent + class TeamAuditLogSerializer(AuditLogSerializer): model = Team diff --git a/src/peoplefinder/templates/peoplefinder/components/team-card-new.html b/src/peoplefinder/templates/peoplefinder/components/team-card-new.html index cfdea91d9..529de27d5 100644 --- a/src/peoplefinder/templates/peoplefinder/components/team-card-new.html +++ b/src/peoplefinder/templates/peoplefinder/components/team-card-new.html @@ -2,4 +2,4 @@ {% url 'team-view' team.slug as team_url %} -{% include "dwds/components/cta_card.html" with title=team.name url=team_url extra_content=team.description|markdown|truncatewords_html:25 %} +{% include "dwds/components/cta_card.html" with title=team.name url=team_url extra_content=team.description|markdown|truncatewords_html:25 footer_text=profile_completion %} diff --git a/src/peoplefinder/templates/peoplefinder/team.html b/src/peoplefinder/templates/peoplefinder/team.html index 7f9575872..3cc11256d 100644 --- a/src/peoplefinder/templates/peoplefinder/team.html +++ b/src/peoplefinder/templates/peoplefinder/team.html @@ -23,7 +23,8 @@{{ profile_completion }}
{% endif %} +