From a943e940147076d3cc29ee3aba11bcd4f3fc6da6 Mon Sep 17 00:00:00 2001 From: Surya Prashanth Date: Tue, 1 Jul 2025 19:38:59 +0530 Subject: [PATCH] [SILO-348] fix: on Project save, add app bots as members (#3550) --- .../migrations/0007_auto_20250701_1303.py | 28 +++++++++ .../plane/authentication/utils/oauth_utils.py | 60 +++++++++++++++++++ apiserver/plane/db/models/project.py | 4 +- apiserver/plane/ee/bgtasks/app_bot_task.py | 12 +++- 4 files changed, 100 insertions(+), 4 deletions(-) create mode 100644 apiserver/plane/authentication/migrations/0007_auto_20250701_1303.py diff --git a/apiserver/plane/authentication/migrations/0007_auto_20250701_1303.py b/apiserver/plane/authentication/migrations/0007_auto_20250701_1303.py new file mode 100644 index 0000000000..e787ba3855 --- /dev/null +++ b/apiserver/plane/authentication/migrations/0007_auto_20250701_1303.py @@ -0,0 +1,28 @@ +# Generated by Django 4.2.22 on 2025-07-01 13:03 + +from django.db import migrations + +from plane.authentication.utils.oauth_utils import add_app_bots_to_existing_projects + + +def add_app_bots_to_existing_projects_migration(apps, schema_editor): + app_installation_model = apps.get_model("authentication", "WorkspaceAppInstallation") + project_model = apps.get_model("db", "Project") + project_member_model = apps.get_model("db", "ProjectMember") + add_app_bots_to_existing_projects( + workspace_app_installation_model=app_installation_model, + project_model=project_model, + project_member_model=project_member_model, + ) + +class Migration(migrations.Migration): + + dependencies = [ + ('authentication', '0006_auto_20250625_1426'), + ("db", "__first__"), + ] + + operations = [ + # rollback is not needed as we are running this in atomic transaction + migrations.RunPython(add_app_bots_to_existing_projects_migration, reverse_code=migrations.RunPython.noop), + ] diff --git a/apiserver/plane/authentication/utils/oauth_utils.py b/apiserver/plane/authentication/utils/oauth_utils.py index bb13ca156d..e50cc53f26 100644 --- a/apiserver/plane/authentication/utils/oauth_utils.py +++ b/apiserver/plane/authentication/utils/oauth_utils.py @@ -17,6 +17,7 @@ def update_bot_user_type( based on the application is_mentionable field. """ from django.db import transaction + from plane.db.models.user import BotTypeEnum if not application_model: @@ -42,3 +43,62 @@ def update_bot_user_type( else None ) bot_user.save() + + +def add_app_bots_to_existing_projects( + workspace_app_installation_model=None, project_model=None, project_member_model=None +): + from django.db import transaction + + from plane.app.permissions.base import ROLE + + if not workspace_app_installation_model: + from plane.authentication.models import WorkspaceAppInstallation + + workspace_app_installation_model = WorkspaceAppInstallation + if not project_model: + from plane.db.models.project import Project + + project_model = Project + if not project_member_model: + from plane.db.models.project import ProjectMember + + project_member_model = ProjectMember + + with transaction.atomic(): + installations = workspace_app_installation_model.objects.filter( + status="installed", + deleted_at__isnull=True, + ) + for installation in installations: + bot_user = installation.app_bot + + existing_project_ids = project_member_model.objects.filter( + workspace=installation.workspace, + member=bot_user, + deleted_at__isnull=True, + ).values_list("project_id", flat=True) + + missing_projects = ( + project_model.objects.filter( + workspace=installation.workspace, + deleted_at__isnull=True, + ) + .exclude(id__in=existing_project_ids) + .values_list("id", flat=True) + ) + + # let's bulk create project members + project_members = [] + for project_id in missing_projects: + project_members.append( + project_member_model( + project_id=project_id, + workspace=installation.workspace, + member=installation.app_bot, + role=ROLE.MEMBER.value, + ) + ) + project_member_model.objects.bulk_create( + project_members, ignore_conflicts=True + ) diff --git a/apiserver/plane/db/models/project.py b/apiserver/plane/db/models/project.py index 235e39320f..d3f7ce28e7 100644 --- a/apiserver/plane/db/models/project.py +++ b/apiserver/plane/db/models/project.py @@ -225,9 +225,11 @@ class Project(BaseModel): def save(self, *args, **kwargs): self.identifier = self.identifier.strip().upper() + # check is_adding value before calling super().save() + is_adding = self._state.adding project = super().save(*args, **kwargs) # Add app bots to the newly created project - if self._state.adding: + if is_adding: self.add_app_bots_to_project() return project diff --git a/apiserver/plane/ee/bgtasks/app_bot_task.py b/apiserver/plane/ee/bgtasks/app_bot_task.py index fa31bd2460..d96ae0d99b 100644 --- a/apiserver/plane/ee/bgtasks/app_bot_task.py +++ b/apiserver/plane/ee/bgtasks/app_bot_task.py @@ -1,11 +1,16 @@ # Third party imports +import logging +from typing import List, Optional + from celery import shared_task -from typing import Optional, List # Module imports from plane.app.permissions.base import ROLE from plane.authentication.models.oauth import WorkspaceAppInstallation from plane.db.models.project import Project, ProjectMember +from plane.utils.exception_logger import log_exception + +logger = logging.getLogger("plane.worker") @shared_task @@ -15,11 +20,12 @@ def add_app_bots_to_project(project_id: str, user_id: Optional[str] = None) -> N This ensures any workspace app bot is automatically added as a project member. """ try: + logger.info(f"Adding app bots to project {project_id}") # Get the project try: project = Project.objects.get(id=project_id) except Project.DoesNotExist: - print(f"Project with ID {project_id} does not exist") + logger.info(f"Project with ID {project_id} does not exist") return # Find all active app installations for this project's workspace @@ -49,4 +55,4 @@ def add_app_bots_to_project(project_id: str, user_id: Optional[str] = None) -> N if project_members: ProjectMember.objects.bulk_create(project_members, ignore_conflicts=True) except Exception as e: - print(f"Error adding app bots to project {project_id}: {str(e)}") + log_exception(e)