From 9d44bb6eb11b87190a605c6120510b00734c08ee Mon Sep 17 00:00:00 2001 From: pablohashescobar Date: Wed, 3 Dec 2025 14:20:03 +0530 Subject: [PATCH 1/4] chore: add bot user to workspace seed task --- apps/api/plane/bgtasks/workspace_seed_task.py | 212 ++++++++++-------- apps/api/plane/db/models/user.py | 4 + 2 files changed, 126 insertions(+), 90 deletions(-) diff --git a/apps/api/plane/bgtasks/workspace_seed_task.py b/apps/api/plane/bgtasks/workspace_seed_task.py index fb9980c3fd6..2cc8ae370e1 100644 --- a/apps/api/plane/bgtasks/workspace_seed_task.py +++ b/apps/api/plane/bgtasks/workspace_seed_task.py @@ -10,6 +10,8 @@ # Django imports from django.conf import settings from django.utils import timezone +from django.contrib.auth.hashers import make_password +from django.db import models # Third party imports from celery import shared_task @@ -34,11 +36,16 @@ CycleIssue, ModuleIssue, IssueView, + User, ) logger = logging.getLogger("plane.worker") +class BotTypeEnum(models.TextChoices): + WORKSPACE_SEED = "WORKSPACE_SEED", "Workspace Seed" + + def read_seed_file(filename): """ Read a JSON file from the seed directory. @@ -61,7 +68,7 @@ def read_seed_file(filename): return None -def create_project_and_member(workspace: Workspace) -> Dict[int, uuid.UUID]: +def create_project_and_member(workspace: Workspace, bot_user: User) -> Dict[int, uuid.UUID]: """Creates a project and associated members for a workspace. Creates a new project using the workspace name and sets up all necessary @@ -69,7 +76,7 @@ def create_project_and_member(workspace: Workspace) -> Dict[int, uuid.UUID]: Args: workspace: The workspace to create the project in - + bot_user: The bot user to use for creating the project Returns: A mapping of seed project IDs to actual project IDs """ @@ -96,7 +103,7 @@ def create_project_and_member(workspace: Workspace) -> Dict[int, uuid.UUID]: workspace=workspace, name=workspace.name, # Use workspace name identifier=project_identifier, - created_by_id=workspace.created_by_id, + created_by_id=bot_user.id, # Enable all views in seed data cycle_view=True, module_view=True, @@ -104,60 +111,56 @@ def create_project_and_member(workspace: Workspace) -> Dict[int, uuid.UUID]: ) # Create project members - ProjectMember.objects.bulk_create( - [ - ProjectMember( - project=project, - member_id=workspace_member["member_id"], - role=workspace_member["role"], - workspace_id=workspace.id, - created_by_id=workspace.created_by_id, - ) - for workspace_member in workspace_members - ] - ) + ProjectMember.objects.bulk_create([ + ProjectMember( + project=project, + member_id=workspace_member["member_id"], + role=workspace_member["role"], + workspace_id=workspace.id, + created_by_id=bot_user.id, + ) + for workspace_member in workspace_members + ]) # Create issue user properties - IssueUserProperty.objects.bulk_create( - [ - IssueUserProperty( - project=project, - user_id=workspace_member["member_id"], - workspace_id=workspace.id, - display_filters={ - "layout": "list", - "calendar": {"layout": "month", "show_weekends": False}, - "group_by": "state", - "order_by": "sort_order", - "sub_issue": True, - "sub_group_by": None, - "show_empty_groups": True, - }, - display_properties={ - "key": True, - "link": True, - "cycle": False, - "state": True, - "labels": False, - "modules": False, - "assignee": True, - "due_date": False, - "estimate": True, - "priority": True, - "created_on": True, - "issue_type": True, - "start_date": False, - "updated_on": True, - "customer_count": True, - "sub_issue_count": False, - "attachment_count": False, - "customer_request_count": True, - }, - created_by_id=workspace.created_by_id, - ) - for workspace_member in workspace_members - ] - ) + IssueUserProperty.objects.bulk_create([ + IssueUserProperty( + project=project, + user_id=workspace_member["member_id"], + workspace_id=workspace.id, + display_filters={ + "layout": "list", + "calendar": {"layout": "month", "show_weekends": False}, + "group_by": "state", + "order_by": "sort_order", + "sub_issue": True, + "sub_group_by": None, + "show_empty_groups": True, + }, + display_properties={ + "key": True, + "link": True, + "cycle": False, + "state": True, + "labels": False, + "modules": False, + "assignee": True, + "due_date": False, + "estimate": True, + "priority": True, + "created_on": True, + "issue_type": True, + "start_date": False, + "updated_on": True, + "customer_count": True, + "sub_issue_count": False, + "attachment_count": False, + "customer_request_count": True, + }, + created_by_id=bot_user.id, + ) + for workspace_member in workspace_members + ]) # update map projects_map[project_id] = project.id logger.info(f"Task: workspace_seed_task -> Project {project_id} created") @@ -165,13 +168,15 @@ def create_project_and_member(workspace: Workspace) -> Dict[int, uuid.UUID]: return projects_map -def create_project_states(workspace: Workspace, project_map: Dict[int, uuid.UUID]) -> Dict[int, uuid.UUID]: +def create_project_states( + workspace: Workspace, project_map: Dict[int, uuid.UUID], bot_user: User +) -> Dict[int, uuid.UUID]: """Creates states for each project in the workspace. Args: workspace: The workspace containing the projects project_map: Mapping of seed project IDs to actual project IDs - + bot_user: The bot user to use for creating the states Returns: A mapping of seed state IDs to actual state IDs """ @@ -190,7 +195,7 @@ def create_project_states(workspace: Workspace, project_map: Dict[int, uuid.UUID **state_seed, project_id=project_map[project_id], workspace=workspace, - created_by_id=workspace.created_by_id, + created_by_id=bot_user.id, ) state_map[state_id] = state.id @@ -198,13 +203,15 @@ def create_project_states(workspace: Workspace, project_map: Dict[int, uuid.UUID return state_map -def create_project_labels(workspace: Workspace, project_map: Dict[int, uuid.UUID]) -> Dict[int, uuid.UUID]: +def create_project_labels( + workspace: Workspace, project_map: Dict[int, uuid.UUID], bot_user: User +) -> Dict[int, uuid.UUID]: """Creates labels for each project in the workspace. Args: workspace: The workspace containing the projects project_map: Mapping of seed project IDs to actual project IDs - + bot_user: The bot user to use for creating the labels Returns: A mapping of seed label IDs to actual label IDs """ @@ -221,7 +228,7 @@ def create_project_labels(workspace: Workspace, project_map: Dict[int, uuid.UUID **label_seed, project_id=project_map[project_id], workspace=workspace, - created_by_id=workspace.created_by_id, + created_by_id=bot_user.id, ) label_map[label_id] = label.id @@ -236,6 +243,7 @@ def create_project_issues( labels_map: Dict[int, uuid.UUID], cycles_map: Dict[int, uuid.UUID], module_map: Dict[int, uuid.UUID], + bot_user: User, ) -> None: """Creates issues and their associated records for each project. @@ -273,13 +281,13 @@ def create_project_issues( state_id=states_map[state_id], project_id=project_map[project_id], workspace=workspace, - created_by_id=workspace.created_by_id, + created_by_id=bot_user.id, ) IssueSequence.objects.create( issue=issue, project_id=project_map[project_id], workspace_id=workspace.id, - created_by_id=workspace.created_by_id, + created_by_id=bot_user.id, ) IssueActivity.objects.create( @@ -288,7 +296,7 @@ def create_project_issues( workspace_id=workspace.id, comment="created the issue", verb="created", - actor_id=workspace.created_by_id, + actor_id=bot_user.id, epoch=time.time(), ) @@ -299,7 +307,7 @@ def create_project_issues( label_id=labels_map[label_id], project_id=project_map[project_id], workspace_id=workspace.id, - created_by_id=workspace.created_by_id, + created_by_id=bot_user.id, ) # Create cycle issues @@ -309,7 +317,7 @@ def create_project_issues( cycle_id=cycles_map[cycle_id], project_id=project_map[project_id], workspace_id=workspace.id, - created_by_id=workspace.created_by_id, + created_by_id=bot_user.id, ) # Create module issues @@ -320,19 +328,20 @@ def create_project_issues( module_id=module_map[module_id], project_id=project_map[project_id], workspace_id=workspace.id, - created_by_id=workspace.created_by_id, + created_by_id=bot_user.id, ) logger.info(f"Task: workspace_seed_task -> Issue {issue_id} created") return -def create_pages(workspace: Workspace, project_map: Dict[int, uuid.UUID]) -> None: +def create_pages(workspace: Workspace, project_map: Dict[int, uuid.UUID], bot_user: User) -> None: """Creates pages for each project in the workspace. Args: workspace: The workspace containing the projects project_map: Mapping of seed project IDs to actual project IDs + bot_user: The bot user to use for creating the pages """ page_seeds = read_seed_file("pages.json") @@ -351,9 +360,9 @@ def create_pages(workspace: Workspace, project_map: Dict[int, uuid.UUID]) -> Non description_html=page_seed.get("description_html", "

"), description_binary=page_seed.get("description_binary", None), description_stripped=page_seed.get("description_stripped", None), - created_by_id=workspace.created_by_id, - updated_by_id=workspace.created_by_id, - owned_by_id=workspace.created_by_id, + created_by_id=bot_user.id, + updated_by_id=bot_user.id, + owned_by_id=bot_user.id, ) logger.info(f"Task: workspace_seed_task -> Page {page_id} created") @@ -362,16 +371,24 @@ def create_pages(workspace: Workspace, project_map: Dict[int, uuid.UUID]) -> Non workspace_id=workspace.id, project_id=project_map[page_seed.get("project_id")], page_id=page.id, - created_by_id=workspace.created_by_id, - updated_by_id=workspace.created_by_id, + created_by_id=bot_user.id, + updated_by_id=bot_user.id, ) logger.info(f"Task: workspace_seed_task -> Project Page {page_id} created") return -def create_cycles(workspace: Workspace, project_map: Dict[int, uuid.UUID]) -> Dict[int, uuid.UUID]: - # Create cycles +def create_cycles(workspace: Workspace, project_map: Dict[int, uuid.UUID], bot_user: User) -> Dict[int, uuid.UUID]: + """Creates cycles for each project in the workspace. + + Args: + workspace: The workspace containing the projects + project_map: Mapping of seed project IDs to actual project IDs + bot_user: The bot user to use for creating the cycles + Returns: + A mapping of seed cycle IDs to actual cycle IDs + """ cycle_seeds = read_seed_file("cycles.json") if not cycle_seeds: return @@ -403,8 +420,8 @@ def create_cycles(workspace: Workspace, project_map: Dict[int, uuid.UUID]) -> Di end_date=end_date, project_id=project_map[project_id], workspace=workspace, - created_by_id=workspace.created_by_id, - owned_by_id=workspace.created_by_id, + created_by_id=bot_user.id, + owned_by_id=bot_user.id, ) cycle_map[cycle_id] = cycle.id @@ -412,12 +429,13 @@ def create_cycles(workspace: Workspace, project_map: Dict[int, uuid.UUID]) -> Di return cycle_map -def create_modules(workspace: Workspace, project_map: Dict[int, uuid.UUID]) -> None: +def create_modules(workspace: Workspace, project_map: Dict[int, uuid.UUID], bot_user: User) -> None: """Creates modules for each project in the workspace. Args: workspace: The workspace containing the projects project_map: Mapping of seed project IDs to actual project IDs + bot_user: The bot user to use for creating the modules """ module_seeds = read_seed_file("modules.json") if not module_seeds: @@ -438,19 +456,20 @@ def create_modules(workspace: Workspace, project_map: Dict[int, uuid.UUID]) -> N target_date=end_date, project_id=project_map[project_id], workspace=workspace, - created_by_id=workspace.created_by_id, + created_by_id=bot_user.id, ) module_map[module_id] = module.id logger.info(f"Task: workspace_seed_task -> Module {module_id} created") return module_map -def create_views(workspace: Workspace, project_map: Dict[int, uuid.UUID]) -> None: +def create_views(workspace: Workspace, project_map: Dict[int, uuid.UUID], bot_user: User) -> None: """Creates views for each project in the workspace. Args: workspace: The workspace containing the projects project_map: Mapping of seed project IDs to actual project IDs + bot_user: The bot user to use for creating the views """ view_seeds = read_seed_file("views.json") @@ -463,8 +482,8 @@ def create_views(workspace: Workspace, project_map: Dict[int, uuid.UUID]) -> Non **view_seed, project_id=project_map[project_id], workspace=workspace, - created_by_id=workspace.created_by_id, - owned_by_id=workspace.created_by_id, + created_by_id=bot_user.id, + owned_by_id=bot_user.id, ) @@ -486,29 +505,42 @@ def workspace_seed(workspace_id: uuid.UUID) -> None: # Get the workspace workspace = Workspace.objects.get(id=workspace_id) + # Create a bot user for creating all the workspace data + bot_user = User.objects.create( + username=f"bot_user_{workspace.id}", + display_name="Plane Bot", + first_name="Plane", + last_name="Bot", + is_bot=True, + bot_type="WORKSPACE_SEED", + email=f"bot_user_{workspace.id}@plane.so", + password=make_password(uuid.uuid4().hex), + is_password_autoset=True, + ) + # Create a project with the same name as workspace - project_map = create_project_and_member(workspace) + project_map = create_project_and_member(workspace, bot_user) # Create project states - state_map = create_project_states(workspace, project_map) + state_map = create_project_states(workspace, project_map, bot_user) # Create project labels - label_map = create_project_labels(workspace, project_map) + label_map = create_project_labels(workspace, project_map, bot_user) # Create project cycles - cycle_map = create_cycles(workspace, project_map) + cycle_map = create_cycles(workspace, project_map, bot_user) # Create project modules - module_map = create_modules(workspace, project_map) + module_map = create_modules(workspace, project_map, bot_user) # create project issues - create_project_issues(workspace, project_map, state_map, label_map, cycle_map, module_map) + create_project_issues(workspace, project_map, state_map, label_map, cycle_map, module_map, bot_user) # create project views - create_views(workspace, project_map) + create_views(workspace, project_map, bot_user) # create project pages - create_pages(workspace, project_map) + create_pages(workspace, project_map, bot_user) logger.info(f"Task: workspace_seed_task -> Workspace {workspace_id} seeded successfully") return diff --git a/apps/api/plane/db/models/user.py b/apps/api/plane/db/models/user.py index c9f0df9b0d6..ee70032cf42 100644 --- a/apps/api/plane/db/models/user.py +++ b/apps/api/plane/db/models/user.py @@ -35,6 +35,10 @@ def get_mobile_default_onboarding(): } +class BotTypeEnum(models.TextChoices): + WORKSPACE_SEED = "WORKSPACE_SEED", "Workspace Seed" + + class User(AbstractBaseUser, PermissionsMixin): id = models.UUIDField(default=uuid.uuid4, unique=True, editable=False, db_index=True, primary_key=True) username = models.CharField(max_length=128, unique=True) From 9346c35c62940f4b3f86a16c67488f6bed11087d Mon Sep 17 00:00:00 2001 From: pablohashescobar Date: Wed, 3 Dec 2025 14:57:40 +0530 Subject: [PATCH 2/4] refactor: use BotTypeEnum for bot type in workspace seed task --- apps/api/plane/bgtasks/workspace_seed_task.py | 7 ++----- apps/api/plane/db/models/__init__.py | 2 +- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/apps/api/plane/bgtasks/workspace_seed_task.py b/apps/api/plane/bgtasks/workspace_seed_task.py index 2cc8ae370e1..c7303e788c2 100644 --- a/apps/api/plane/bgtasks/workspace_seed_task.py +++ b/apps/api/plane/bgtasks/workspace_seed_task.py @@ -37,15 +37,12 @@ ModuleIssue, IssueView, User, + BotTypeEnum, ) logger = logging.getLogger("plane.worker") -class BotTypeEnum(models.TextChoices): - WORKSPACE_SEED = "WORKSPACE_SEED", "Workspace Seed" - - def read_seed_file(filename): """ Read a JSON file from the seed directory. @@ -512,7 +509,7 @@ def workspace_seed(workspace_id: uuid.UUID) -> None: first_name="Plane", last_name="Bot", is_bot=True, - bot_type="WORKSPACE_SEED", + bot_type=BotTypeEnum.WORKSPACE_SEED, email=f"bot_user_{workspace.id}@plane.so", password=make_password(uuid.uuid4().hex), is_password_autoset=True, diff --git a/apps/api/plane/db/models/__init__.py b/apps/api/plane/db/models/__init__.py index cf58632ac48..41fd32bd557 100644 --- a/apps/api/plane/db/models/__init__.py +++ b/apps/api/plane/db/models/__init__.py @@ -58,7 +58,7 @@ from .session import Session from .social_connection import SocialLoginConnection from .state import State, StateGroup, DEFAULT_STATES -from .user import Account, Profile, User +from .user import Account, Profile, User, BotTypeEnum from .view import IssueView from .webhook import Webhook, WebhookLog from .workspace import ( From 30fce454492ed3e4a4cfdf3ee800af97301d5fd6 Mon Sep 17 00:00:00 2001 From: pablohashescobar Date: Fri, 5 Dec 2025 16:14:59 +0530 Subject: [PATCH 3/4] refactor: update bot user display name and last name in workspace seed task --- apps/api/plane/bgtasks/workspace_seed_task.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/apps/api/plane/bgtasks/workspace_seed_task.py b/apps/api/plane/bgtasks/workspace_seed_task.py index c7303e788c2..0f41e2005f2 100644 --- a/apps/api/plane/bgtasks/workspace_seed_task.py +++ b/apps/api/plane/bgtasks/workspace_seed_task.py @@ -11,7 +11,6 @@ from django.conf import settings from django.utils import timezone from django.contrib.auth.hashers import make_password -from django.db import models # Third party imports from celery import shared_task @@ -505,9 +504,9 @@ def workspace_seed(workspace_id: uuid.UUID) -> None: # Create a bot user for creating all the workspace data bot_user = User.objects.create( username=f"bot_user_{workspace.id}", - display_name="Plane Bot", + display_name="Plane", first_name="Plane", - last_name="Bot", + last_name="", is_bot=True, bot_type=BotTypeEnum.WORKSPACE_SEED, email=f"bot_user_{workspace.id}@plane.so", From 730e099995ab4bc11a95c98bd474b92f86c2e6fc Mon Sep 17 00:00:00 2001 From: pablohashescobar Date: Fri, 5 Dec 2025 17:49:15 +0530 Subject: [PATCH 4/4] fix: return empty dictionary for missing cycle and module seeds in workspace seed task --- apps/api/plane/bgtasks/workspace_seed_task.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/api/plane/bgtasks/workspace_seed_task.py b/apps/api/plane/bgtasks/workspace_seed_task.py index 0f41e2005f2..57ac02ec127 100644 --- a/apps/api/plane/bgtasks/workspace_seed_task.py +++ b/apps/api/plane/bgtasks/workspace_seed_task.py @@ -387,7 +387,7 @@ def create_cycles(workspace: Workspace, project_map: Dict[int, uuid.UUID], bot_u """ cycle_seeds = read_seed_file("cycles.json") if not cycle_seeds: - return + return {} cycle_map: Dict[int, uuid.UUID] = {} @@ -435,7 +435,7 @@ def create_modules(workspace: Workspace, project_map: Dict[int, uuid.UUID], bot_ """ module_seeds = read_seed_file("modules.json") if not module_seeds: - return + return {} module_map: Dict[int, uuid.UUID] = {}