[WEB-5058] feat: enhance workspace seeding with cycles, modules, and views creation (#7951)

* feat: enhance workspace seeding with cycles, modules, and views creation

- Added `create_cycles`, `create_modules`, and `create_views` functions to the workspace seeding process, enabling the creation of cycles, modules, and views based on new seed data.
- Updated `create_project_issues` to associate issues with cycles and modules.
- Introduced new seed files: `cycles.json`, `modules.json`, and `views.json` to provide initial data for cycles, modules, and views.
- Integrated these new functionalities into the `workspace_seed` task for comprehensive workspace initialization.

* feat: add project_id to page seed data for improved association

- Added `project_id` field to the page with `id` 2 in `pages.json` to establish a clear link between pages and their respective projects.
- This enhancement supports better organization and retrieval of page data within the project context.

* feat: enhance workspace seed task with improved display properties and layout options

- Updated the `create_project_and_member` function to include new display properties and layout configurations for better project visualization.
- Modified display filters to group by state and added calendar layout options.
- Enhanced the `create_modules` and `create_views` functions with improved formatting and structure for better readability and maintainability.

* Update apps/api/plane/bgtasks/workspace_seed_task.py

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* fix: correct project_id mapping in cycle and view creation functions

- Updated the `create_cycles` function to use the correct `project_id` from the `project_map` for fetching the last cycle.
- Removed redundant `view_id` extraction in the `create_views` function to streamline view creation process.

* refactor: update create_cycles function to return project mapping

- Changed the return type of the `create_cycles` function from `None` to `Dict[int, uuid.UUID]` to provide a mapping of project IDs after cycle creation.
- This modification enhances the function's utility by allowing the caller to access the generated project mappings directly.

* refactor: remove unused view_map variable in create_views function

- Eliminated the `view_map` dictionary from the `create_views` function as it was not utilized, streamlining the code.
- This change enhances code clarity and maintainability by removing unnecessary elements.

* refactor: improve issue creation logic in create_project_issues function

- Added comments to clarify the creation of issue labels, cycle issues, and module issues within the `create_project_issues` function.
- Enhanced code readability and maintainability by structuring the issue creation process with clear conditional checks for cycles and modules.

---------

Co-authored-by: sriram veeraghanta <veeraghanta.sriram@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Nikhil
2025-10-11 20:55:48 +05:30
committed by GitHub
parent 70be4a4ace
commit c3dd790f7e
6 changed files with 281 additions and 28 deletions

View File

@@ -5,9 +5,11 @@ import time
import uuid
from typing import Dict
import logging
from datetime import timedelta
# Django imports
from django.conf import settings
from django.utils import timezone
# Third party imports
from celery import shared_task
@@ -27,6 +29,11 @@ from plane.db.models import (
IssueActivity,
Page,
ProjectPage,
Cycle,
Module,
CycleIssue,
ModuleIssue,
IssueView,
)
logger = logging.getLogger("plane.worker")
@@ -118,13 +125,33 @@ def create_project_and_member(workspace: Workspace) -> Dict[int, uuid.UUID]:
user_id=workspace_member["member_id"],
workspace_id=workspace.id,
display_filters={
"group_by": None,
"order_by": "sort_order",
"type": None,
"sub_issue": True,
"show_empty_groups": True,
"layout": "list",
"calendar_date_range": "",
"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,
)
@@ -207,6 +234,8 @@ def create_project_issues(
project_map: Dict[int, uuid.UUID],
states_map: Dict[int, uuid.UUID],
labels_map: Dict[int, uuid.UUID],
cycles_map: Dict[int, uuid.UUID],
module_map: Dict[int, uuid.UUID],
) -> None:
"""Creates issues and their associated records for each project.
@@ -236,6 +265,8 @@ def create_project_issues(
labels = issue_seed.pop("labels")
project_id = issue_seed.pop("project_id")
state_id = issue_seed.pop("state_id")
cycle_id = issue_seed.pop("cycle_id")
module_ids = issue_seed.pop("module_ids")
issue = Issue.objects.create(
**issue_seed,
@@ -261,6 +292,7 @@ def create_project_issues(
epoch=time.time(),
)
# Create issue labels
for label_id in labels:
IssueLabel.objects.create(
issue=issue,
@@ -270,6 +302,27 @@ def create_project_issues(
created_by_id=workspace.created_by_id,
)
# Create cycle issues
if cycle_id:
CycleIssue.objects.create(
issue=issue,
cycle_id=cycles_map[cycle_id],
project_id=project_map[project_id],
workspace_id=workspace.id,
created_by_id=workspace.created_by_id,
)
# Create module issues
if module_ids:
for module_id in module_ids:
ModuleIssue.objects.create(
issue=issue,
module_id=module_map[module_id],
project_id=project_map[project_id],
workspace_id=workspace.id,
created_by_id=workspace.created_by_id,
)
logger.info(f"Task: workspace_seed_task -> Issue {issue_id} created")
return
@@ -292,6 +345,7 @@ def create_pages(workspace: Workspace, project_map: Dict[int, uuid.UUID]) -> Non
page = Page.objects.create(
workspace_id=workspace.id,
is_global=False,
access=page_seed.get("access", Page.PUBLIC_ACCESS),
name=page_seed.get("name"),
description=page_seed.get("description", {}),
description_html=page_seed.get("description_html", "<p></p>"),
@@ -303,7 +357,7 @@ def create_pages(workspace: Workspace, project_map: Dict[int, uuid.UUID]) -> Non
)
logger.info(f"Task: workspace_seed_task -> Page {page_id} created")
if page_seed.get("project_id") and page_seed.get("type") == "project":
if page_seed.get("project_id") and page_seed.get("type") == "PROJECT":
ProjectPage.objects.create(
workspace_id=workspace.id,
project_id=project_map[page_seed.get("project_id")],
@@ -315,6 +369,105 @@ def create_pages(workspace: Workspace, project_map: Dict[int, uuid.UUID]) -> Non
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
cycle_seeds = read_seed_file("cycles.json")
if not cycle_seeds:
return
cycle_map: Dict[int, uuid.UUID] = {}
for cycle_seed in cycle_seeds:
cycle_id = cycle_seed.pop("id")
project_id = cycle_seed.pop("project_id")
type = cycle_seed.pop("type")
if type == "CURRENT":
start_date = timezone.now()
end_date = start_date + timedelta(days=14)
if type == "UPCOMING":
# Get the last cycle
last_cycle = Cycle.objects.filter(project_id=project_map[project_id]).order_by("-end_date").first()
if last_cycle:
start_date = last_cycle.end_date + timedelta(days=1)
end_date = start_date + timedelta(days=14)
else:
start_date = timezone.now() + timedelta(days=14)
end_date = start_date + timedelta(days=14)
cycle = Cycle.objects.create(
**cycle_seed,
start_date=start_date,
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,
)
cycle_map[cycle_id] = cycle.id
logger.info(f"Task: workspace_seed_task -> Cycle {cycle_id} created")
return cycle_map
def create_modules(workspace: Workspace, project_map: Dict[int, uuid.UUID]) -> 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
"""
module_seeds = read_seed_file("modules.json")
if not module_seeds:
return
module_map: Dict[int, uuid.UUID] = {}
for index, module_seed in enumerate(module_seeds):
module_id = module_seed.pop("id")
project_id = module_seed.pop("project_id")
start_date = timezone.now() + timedelta(days=index * 2)
end_date = start_date + timedelta(days=14)
module = Module.objects.create(
**module_seed,
start_date=start_date,
target_date=end_date,
project_id=project_map[project_id],
workspace=workspace,
created_by_id=workspace.created_by_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:
"""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
"""
view_seeds = read_seed_file("views.json")
if not view_seeds:
return
for view_seed in view_seeds:
project_id = view_seed.pop("project_id")
IssueView.objects.create(
**view_seed,
project_id=project_map[project_id],
workspace=workspace,
created_by_id=workspace.created_by_id,
owned_by_id=workspace.created_by_id,
)
@shared_task
def workspace_seed(workspace_id: uuid.UUID) -> None:
"""Seeds a new workspace with initial project data.
@@ -342,8 +495,17 @@ def workspace_seed(workspace_id: uuid.UUID) -> None:
# Create project labels
label_map = create_project_labels(workspace, project_map)
# Create project cycles
cycle_map = create_cycles(workspace, project_map)
# Create project modules
module_map = create_modules(workspace, project_map)
# create project issues
create_project_issues(workspace, project_map, state_map, label_map)
create_project_issues(workspace, project_map, state_map, label_map, cycle_map, module_map)
# create project views
create_views(workspace, project_map)
# create project pages
create_pages(workspace, project_map)

View File

@@ -0,0 +1,18 @@
[
{
"id": 1,
"name": "Cycle 1: Getting Started with Plane",
"project_id": 1,
"sort_order": 1,
"timezone": "UTC",
"type": "CURRENT"
},
{
"id": 2,
"name": "Cycle 2: Collaboration & Customization",
"project_id": 1,
"sort_order": 2,
"timezone": "UTC",
"type": "UPCOMING"
}
]

View File

@@ -6,10 +6,12 @@
"description_html": "<p class=\"editor-paragraph-block\">Hey there! This demo project is your playground to get hands-on with Plane. We've set this up so you can click around and see how everything works without worrying about breaking anything.</p><p class=\"editor-paragraph-block\">Each work item is designed to make you familiar with the basics of using Plane. Just follow along card by card at your own pace.</p><p class=\"editor-paragraph-block\">First thing to try</p><ol class=\"list-decimal pl-7 space-y-[--list-spacing-y] tight\" data-tight=\"true\"><li class=\"not-prose space-y-2\"><p class=\"editor-paragraph-block\">Look in the <strong>Properties</strong> section below where it says <strong>State: Todo</strong>.</p></li><li class=\"not-prose space-y-2\"><p class=\"editor-paragraph-block\">Click on it and change it to <strong>Done</strong> from the dropdown. Alternatively, you can drag and drop the card to the Done column.</p></li></ol>",
"description_stripped": "Hey there! This demo project is your playground to get hands-on with Plane. We've set this up so you can click around and see how everything works without worrying about breaking anything.Each work item is designed to make you familiar with the basics of using Plane. Just follow along card by card at your own pace.First thing to tryLook in the Properties section below where it says State: Todo.Click on it and change it to Done from the dropdown. Alternatively, you can drag and drop the card to the Done column.",
"sort_order": 1000,
"state_id": 3,
"state_id": 4,
"labels": [],
"priority": "none",
"project_id": 1
"priority": "urgent",
"project_id": 1,
"cycle_id": 1,
"module_ids": [1]
},
{
"id": 2,
@@ -19,8 +21,10 @@
"sort_order": 2000,
"state_id": 2,
"labels": [2],
"priority": "none",
"project_id": 1
"priority": "high",
"project_id": 1,
"cycle_id": 1,
"module_ids": [1]
},
{
"id": 3,
@@ -31,8 +35,10 @@
"sort_order": 3000,
"state_id": 1,
"labels": [],
"priority": "none",
"project_id": 1
"priority": "high",
"project_id": 1,
"cycle_id": 1,
"module_ids": [1, 2]
},
{
"id": 4,
@@ -41,10 +47,12 @@
"description_html": "<p class=\"editor-paragraph-block\">A work item is the fundamental building block of your project. Think of these as the actionable tasks that move your project forward.</p><p class=\"editor-paragraph-block\">Ready to add something to your project's to-do list? Here's how:</p><ol class=\"list-decimal pl-7 space-y-[--list-spacing-y] tight\" data-tight=\"true\"><li class=\"not-prose space-y-2\"><p class=\"editor-paragraph-block\">Click the <strong>Add work item</strong> button in the top-right corner of the Work Items page.</p><image-component src=\"https://media.docs.plane.so/seed_assets/41.png\" width=\"1085.380859375px\" height=\"482.53758375605696px\" id=\"ba055bc3-4162-4750-9ad4-9434fc0e7121\" aspectratio=\"2.249318801089918\"></image-component></li><li class=\"not-prose space-y-2\"><p class=\"editor-paragraph-block\">Give your task a clear title and add any details in the description.</p></li><li class=\"not-prose space-y-2\"><p class=\"editor-paragraph-block\">Set up the essentials:</p><ul class=\"list-disc pl-7 space-y-[--list-spacing-y] tight\" data-tight=\"true\"><li class=\"not-prose space-y-2\"><p class=\"editor-paragraph-block\">Assign it to a team member (or yourself!)</p></li><li class=\"not-prose space-y-2\"><p class=\"editor-paragraph-block\">Choose a priority level</p></li><li class=\"not-prose space-y-2\"><p class=\"editor-paragraph-block\">Add start and due dates if there's a timeline</p></li></ul></li></ol><div data-emoji-unicode=\"128161\" data-emoji-url=\"https://cdn.jsdelivr.net/npm/emoji-datasource-apple/img/apple/64/1f4a1.png\" data-logo-in-use=\"emoji\" data-background=\"green\" data-block-type=\"callout-component\"><p class=\"editor-paragraph-block\"><strong>Tip:</strong> Save time by using the keyboard shortcut <strong>C</strong> from anywhere in your project to quickly create a new work item!</p></div><div class=\"py-4 border-custom-border-400\" data-type=\"horizontalRule\"><div></div></div><p class=\"editor-paragraph-block\">Want to dive deeper into all the things you can do with work items? Check out our <a target=\"_blank\" rel=\"noopener noreferrer nofollow\" class=\"text-custom-primary-300 underline underline-offset-[3px] hover:text-custom-primary-500 transition-colors cursor-pointer\" href=\"https://docs.plane.so/core-concepts/issues/overview\">documentation</a>.</p>",
"description_stripped": "A work item is the fundamental building block of your project. Think of these as the actionable tasks that move your project forward.Ready to add something to your project's to-do list? Here's how:Click the Add work item button in the top-right corner of the Work Items page.Give your task a clear title and add any details in the description.Set up the essentials:Assign it to a team member (or yourself!)Choose a priority levelAdd start and due dates if there's a timelineTip: Save time by using the keyboard shortcut C from anywhere in your project to quickly create a new work item!Want to dive deeper into all the things you can do with work items? Check out our documentation.",
"sort_order": 4000,
"state_id": 1,
"state_id": 3,
"labels": [2],
"priority": "none",
"project_id": 1
"priority": "high",
"project_id": 1,
"cycle_id": 1,
"module_ids": [1, 2]
},
{
"id": 5,
@@ -53,10 +61,12 @@
"description_html": "<p class=\"editor-paragraph-block\">Plane offers multiple ways to look at your work items depending on what you need to see. Let's explore how to change views and customize them!</p><image-component src=\"https://media.docs.plane.so/seed_assets/51.png\" aspectratio=\"4.489130434782608\"></image-component><h2 class=\"editor-heading-block\">Switch between layouts</h2><ol class=\"list-decimal pl-7 space-y-[--list-spacing-y] tight\" data-tight=\"true\"><li class=\"not-prose space-y-2\"><p class=\"editor-paragraph-block\">Look at the top toolbar in your project. You'll see several layout icons.</p></li><li class=\"not-prose space-y-2\"><p class=\"editor-paragraph-block\">Click any of these icons to instantly switch between layouts.</p></li></ol><div data-emoji-unicode=\"128161\" data-emoji-url=\"https://cdn.jsdelivr.net/npm/emoji-datasource-apple/img/apple/64/1f4a1.png\" data-logo-in-use=\"emoji\" data-background=\"green\" data-block-type=\"callout-component\"><p class=\"editor-paragraph-block\"><strong>Tip:</strong> Different layouts work best for different needs. Try Board view for tracking progress, Calendar for deadline management, and Gantt for timeline planning! See <a target=\"_blank\" rel=\"noopener noreferrer nofollow\" class=\"text-custom-primary-300 underline underline-offset-[3px] hover:text-custom-primary-500 transition-colors cursor-pointer\" href=\"https://docs.plane.so/core-concepts/issues/layouts\"><strong>Layouts</strong></a> for more info.</p></div><h2 class=\"editor-heading-block\">Filter and display options</h2><p class=\"editor-paragraph-block\">Need to focus on specific work?</p><ol class=\"list-decimal pl-7 space-y-[--list-spacing-y] tight\" data-tight=\"true\"><li class=\"not-prose space-y-2\"><p class=\"editor-paragraph-block\">Click the <strong>Filters</strong> dropdown in the toolbar. Select criteria and choose which items to show.</p></li><li class=\"not-prose space-y-2\"><p class=\"editor-paragraph-block\">Click the <strong>Display</strong> dropdown to tailor how the information appears in your layout</p></li><li class=\"not-prose space-y-2\"><p class=\"editor-paragraph-block\">Created the perfect setup? Save it for later by clicking the the <strong>Save View</strong> button.</p></li><li class=\"not-prose space-y-2\"><p class=\"editor-paragraph-block\">Access saved views anytime from the <strong>Views</strong> section in your sidebar.</p></li></ol>",
"description_stripped": "Plane offers multiple ways to look at your work items depending on what you need to see. Let's explore how to change views and customize them!Switch between layoutsLook at the top toolbar in your project. You'll see several layout icons.Click any of these icons to instantly switch between layouts.Tip: Different layouts work best for different needs. Try Board view for tracking progress, Calendar for deadline management, and Gantt for timeline planning! See Layouts for more info.Filter and display optionsNeed to focus on specific work?Click the Filters dropdown in the toolbar. Select criteria and choose which items to show.Click the Display dropdown to tailor how the information appears in your layoutCreated the perfect setup? Save it for later by clicking the the Save View button.Access saved views anytime from the Views section in your sidebar.",
"sort_order": 5000,
"state_id": 1,
"state_id": 3,
"labels": [],
"priority": "none",
"project_id": 1
"project_id": 1,
"cycle_id": 2,
"module_ids": [2]
},
{
"id": 6,
@@ -67,8 +77,10 @@
"sort_order": 6000,
"state_id": 1,
"labels": [2],
"priority": "none",
"project_id": 1
"priority": "low",
"project_id": 1,
"cycle_id": 2,
"module_ids": [2, 3]
},
{
"id": 7,
@@ -80,6 +92,8 @@
"state_id": 1,
"labels": [],
"priority": "none",
"project_id": 1
"project_id": 1,
"cycle_id": 2,
"module_ids": [2, 3]
}
]

View File

@@ -0,0 +1,26 @@
[
{
"id": 1,
"name": "Core Workflow (System)",
"project_id": 1,
"sort_order": 1,
"status": "planned",
"description": "Manage, visualize, and track your work items across views."
},
{
"id": 2,
"name": "Onboarding Flow (Feature)",
"project_id": 1,
"sort_order": 2,
"status": "backlog",
"description": "Everything about getting started - creating a project, inviting teammates."
},
{
"id": 3,
"name": "Workspace Setup (Area)",
"project_id": 1,
"sort_order": 3,
"status": "in-progress",
"description": "The personalization layer - settings, labels, automations."
}
]

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,14 @@
[
{
"id": 1,
"name": "Project Urgent Tasks",
"description": "Project Urgent Tasks",
"access": 1,
"filters": {},
"project_id": 1,
"display_filters": {"layout": "list", "calendar": {"layout": "month", "show_weekends": false}, "group_by": "state", "order_by": "sort_order", "sub_issue": false, "sub_group_by": null, "show_empty_groups": false},
"display_properties": {"key": true, "link": true, "cycle": true, "state": true, "labels": true, "modules": true, "assignee": true, "due_date": true, "estimate": true, "priority": true, "created_on": true, "issue_type": true, "start_date": true, "updated_on": true, "customer_count": true, "sub_issue_count": true, "attachment_count": true, "customer_request_count": true},
"sort_order": 75535,
"rich_filters": {"priority__in": "urgent"}
}
]