- Notifications
You must be signed in to change notification settings - Fork3k
[WIKI-769] chore: added sort order for pages#8191
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
base:preview
Are you sure you want to change the base?
Uh oh!
There was an error while loading.Please reload this page.
Conversation
coderabbitaibot commentedNov 27, 2025 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
WalkthroughThis change introduces a Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes
Poem
Pre-merge checks and finishing touches❌ Failed checks (2 warnings)
✅ Passed checks (1 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for usingCodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Linked to Plane Work Item(s) This comment was auto-generated byPlane |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Actionable comments posted: 3
🧹 Nitpick comments (2)
apps/api/plane/db/models/page.py (2)
154-163:Docstring doesn't match implementation behavior.The docstring states this sets
sort_orderfor pages "when the associated page has no parent," but the method doesn't checkself.page.parent. It unconditionally calculates and assigns a newsort_orderfor everyProjectPagecreation.If this is intentional (all new
ProjectPageentries get a unique sort order), update the docstring. If only root pages should get a calculated sort order, add a parent check:def _set_sort_for_root_page(self): """- Set sort_order for project pages when the associated page has no parent in the workspace.+ Set sort_order for newly created project pages. """+ # Optionally, if only root pages should get unique sort:+ # if self.page.parent_id is not None:+ # return largest_sort_order = ProjectPage.objects.filter(project=self.project).aggregate( largest=models.Max("sort_order") )["largest"]
165-168:Consider race condition on concurrent inserts.Multiple concurrent
ProjectPagecreations for the same project could read the samelargestvalue and assign duplicatesort_ordervalues. While this doesn't break functionality (duplicates just affect display order), you may want to add advisory locking similar to theIssuemodel pattern if strict ordering is required:withtransaction.atomic():lock_key=convert_uuid_to_integer(self.project_id)withconnection.cursor()ascursor:cursor.execute("SELECT pg_advisory_xact_lock(%s)", [lock_key])# ... rest of sort_order logicIf duplicate sort orders are acceptable, this can be deferred.
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
apps/api/plane/app/serializers/page.py(3 hunks)apps/api/plane/app/views/page/base.py(2 hunks)apps/api/plane/db/migrations/0113_auto_20251215_0934.py(1 hunks)apps/api/plane/db/models/page.py(2 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
apps/api/plane/app/serializers/page.py (1)
apps/api/plane/db/models/page.py (1)
Page(19-73)
apps/api/plane/app/views/page/base.py (1)
apps/api/plane/db/models/page.py (1)
ProjectPage(131-168)
apps/api/plane/db/models/page.py (2)
apps/api/plane/db/models/issue.py (2)
save(182-246)save(475-524)apps/api/plane/db/models/favorite.py (1)
save(48-61)
apps/api/plane/db/migrations/0113_auto_20251215_0934.py (1)
apps/api/plane/db/models/page.py (2)
Page(19-73)ProjectPage(131-168)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
- GitHub Check: check:format
- GitHub Check: Build packages
- GitHub Check: Lint API
- GitHub Check: Agent
- GitHub Check: Analyze (javascript)
🔇 Additional comments (2)
apps/api/plane/app/views/page/base.py (1)
102-110:LGTM!The
Prefetchcorrectly filtersProjectPageentries for the current project withdeleted_at__isnull=True, andto_attr="current_project_page"stores the result as a list for efficient access in the serializer'sget_sort_ordermethod.apps/api/plane/app/serializers/page.py (1)
126-132:LGTM!The implementation correctly handles the prefetched
current_project_pagelist, accessing the first element'ssort_orderwhen available and falling back toPage.DEFAULT_SORT_ORDERotherwise.
| project_pages=ProjectPage.objects.filter(page__parent_id__isnull=False).select_related("page") | ||
| project_pages_to_update= [] | ||
| forproject_pageinproject_pages.iterator(chunk_size=BATCH_SIZE): | ||
| project_page.sort_order=project_page.page.sort_order | ||
| project_pages_to_update.append(project_page) | ||
| iflen(project_pages_to_update)>=BATCH_SIZE: | ||
| ProjectPage.objects.bulk_update(project_pages_to_update, ["sort_order"],batch_size=BATCH_SIZE) | ||
| project_pages_to_update= [] | ||
| # Update remaining items | ||
| ifproject_pages_to_update: | ||
| ProjectPage.objects.bulk_update(project_pages_to_update, ["sort_order"],batch_size=BATCH_SIZE) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Existing root pages retain duplicatesort_order values.
This loop copiesPage.sort_order toProjectPage.sort_order only for child pages (parent_id__isnull=False). Existing root pages (withparent_id=None) will all have the default65535 in theirProjectPage records.
Consider also assigning uniquesort_order values toProjectPage entries for root pages:
# After updating child pages, handle root pages similarlyroot_project_pages=ProjectPage.objects.filter(page__parent_id__isnull=True).select_related("page")# Assign unique sort_order per project...
| pages=list[Any]( | ||
| Page.objects | ||
| .filter(workspace_id=workspace_id,sort_order=DEFAULT) | ||
| .only("id","sort_order") | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Syntax error:list[Any] is a type hint, not callable.
list[Any](...) will raise aTypeError at runtime. Uselist(...) to convert the queryset to a list:
- pages = list[Any](+ pages = list( Page.objects .filter(workspace_id=workspace_id, sort_order=DEFAULT) .only("id", "sort_order") )
Also, theAny import on line 3 becomes unused after this fix.
🤖 Prompt for AI Agents
In apps/api/plane/db/migrations/0113_auto_20251215_0934.py around lines 52 to56, the code uses the type hint `list[Any](...)` which is invalid at runtime;replace `list[Any](Page.objects.filter(...).only(...))` with a plain`list(Page.objects.filter(...).only(...))` to materialize the queryset, andremove the now-unused `Any` import from the top of the file.| defreverse_set_page_sort_order(apps,schema_editor): | ||
| ProjectPage=apps.get_model("db","ProjectPage") | ||
| DEFAULT_SORT_ORDER=65535 | ||
| ProjectPage.objects.update(sort_order=DEFAULT_SORT_ORDER) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Reverse migration doesn't restorePage.sort_order changes.
The forward migration modifies bothProjectPage.sort_order (lines 20-31) andPage.sort_order (lines 61-65), but the reverse only resetsProjectPage.sort_order. If the migration is rolled back,Page.sort_order values will remain modified.
If reversibility is important, consider either:
- Storing original values and restoring them in reverse
- Documenting that
Page.sort_orderchanges are permanent
🤖 Prompt for AI Agents
In apps/api/plane/db/migrations/0113_auto_20251215_0934.py around lines 68-71,the reverse_set_page_sort_order currently only resets ProjectPage.sort_order anddoes not undo the changes made to Page.sort_order in the forward migration;update the reverse function to also restore Page.sort_order (either by settingit back to the same DEFAULT_SORT_ORDER constant used for ProjectPage or, iforiginal values were captured, restore those original values) so the rollbackfully reverts both models, or explicitly document in the migration header thatPage.sort_order changes are irreversible if you choose not to implementrestoration.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Pull request overview
This PR adds sort order functionality to project-level pages by introducing asort_order field to theProjectPage model. The feature enables ordering of pages within projects, moving beyond the current creation-date-based sorting.
- Adds
sort_orderFloatField toProjectPagemodel with default value fromPage.DEFAULT_SORT_ORDER(65535) - Migrates existing page sort orders to the
ProjectPagemodel for child pages - Exposes
sort_orderthrough thePageSerializervia a prefetch mechanism
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 7 comments.
| File | Description |
|---|---|
| apps/api/plane/db/models/page.py | Addssort_order field toProjectPage model and implements auto-incrementing logic in save() method |
| apps/api/plane/db/migrations/0113_auto_20251215_0934.py | Migration that adds the field and backfills sort_order values from related Page models |
| apps/api/plane/app/views/page/base.py | Adds prefetch for ProjectPage to make sort_order available in querysets |
| apps/api/plane/app/serializers/page.py | Addsget_sort_order method to expose sort_order from prefetched ProjectPage instances |
💡Add Copilot custom instructions for smarter, more guided reviews.Learn how to get started.
| pages=list[Any]( | ||
| Page.objects | ||
| .filter(workspace_id=workspace_id,sort_order=DEFAULT) | ||
| .only("id","sort_order") | ||
| ) |
CopilotAIDec 17, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Invalid type annotation syntax. The expression 'listAny' attempts to call 'list[Any]' as a function, but this is not valid Python. This should be either a list comprehension or use the list() constructor without the type parameter. The correct syntax would be 'pages = list(Page.objects.filter(...))' or simply assign the queryset directly if you need to iterate over it multiple times.
| def_set_sort_for_root_page(self): | ||
| """ | ||
| Set sort_order for project pages when the associated page has no parent in the workspace. | ||
| """ |
CopilotAIDec 17, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
The documentation states this method sets sort_order "when the associated page has no parent in the workspace," but the implementation doesn't check whether the page has a parent. The method always sets the sort_order for any new ProjectPage. Consider either updating the documentation to match the implementation or adding a check for 'self.page.parent_id is None' if the behavior should only apply to root pages.
| defsave(self,*args,**kwargs): | ||
| ifself._state.adding: | ||
| self._set_sort_for_root_page() | ||
| super(ProjectPage,self).save(*args,**kwargs) |
CopilotAIDec 17, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Inconsistency between migration logic and model behavior. The migration (lines 20-31) copies sort_order from Page to ProjectPage only for pages WITH a parent (parent_id__isnull=False), but the model's save() method sets sort_order for ALL new ProjectPages regardless of parent status. This creates different behavior for existing vs new records. The logic should be aligned: either both should handle all pages, or both should only handle root/child pages consistently.
| to_attr="current_project_page", | ||
| ) | ||
| ) | ||
| .order_by("-is_favorite","-created_at") |
CopilotAIDec 17, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
The queryset is ordering by "-is_favorite" and "-created_at" but the newly added sort_order field is not being used for ordering. Since the PR description states this is to "add sorting of page in project level pages," you should update the order_by clause to include sort_order. For example: .order_by("-is_favorite", "sort_order", "-created_at") to order by favorites first, then by sort order, then by creation date.
| def__str__(self): | ||
| returnf"{self.project.name}{self.page.name}" | ||
| def_set_sort_for_root_page(self): |
CopilotAIDec 17, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
The method name '_set_sort_for_root_page' implies it only applies to root pages (pages without a parent), but it's actually called for all new ProjectPage instances. Consider renaming to '_set_sort_order' or '_set_initial_sort_order' to better reflect its actual behavior, which sets sort_order for any new ProjectPage regardless of whether the associated page has a parent.
| .values_list("workspace_id",flat=True) | ||
| ) | ||
| forworkspace_idinworkspace_ids.iterator(chunk_size=500): |
CopilotAIDec 17, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Calling .iterator() on a values_list QuerySet is unnecessary and potentially problematic. The values_list() already returns a QuerySet, and calling iterator() on line 43 before iterating creates an additional layer. Since workspace_ids is already a QuerySet that will be lazily evaluated, you can directly iterate over it with 'for workspace_id in workspace_ids:'. The chunk_size parameter on iterator() is also redundant here since you're iterating over workspace IDs (which should be a small set), not large model instances.
| .order_by(self.request.GET.get("order_by","-created_at")) | ||
| .prefetch_related("labels") | ||
| .prefetch_related( | ||
| Prefetch( | ||
| "project_pages", | ||
| queryset=ProjectPage.objects.filter( | ||
| project_id=self.kwargs.get("project_id"),deleted_at__isnull=True | ||
| ), | ||
| to_attr="current_project_page", | ||
| ) | ||
| ) | ||
| .order_by("-is_favorite","-created_at") |
CopilotAIDec 17, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
There are two order_by() calls in the queryset chain - one at line 100 and another at line 111. In Django's QuerySet chaining, the second order_by() call will completely override the first one. This means the order_by clause at line 100 that uses the request parameter is ineffective. You should either remove line 100 or combine both ordering strategies into a single order_by() call.
Uh oh!
There was an error while loading.Please reload this page.
Description
added sorting of page in project level pages.
Type of Change
Summary by CodeRabbit
Release Notes
✏️ Tip: You can customize this high-level summary in your review settings.