-
Notifications
You must be signed in to change notification settings - Fork 3.8k
[Feature] - Adding Stickies reordering feature (Issue #8488) #8736
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 our terms of service and privacy 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?
Changes from all commits
bdc77e8
8cbf84c
0359377
0d87bfa
973b20d
7ad3988
c6a32cc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,7 +8,7 @@ | |
|
|
||
| # Third party imports | ||
| from celery import Celery | ||
| from pythonjsonlogger.jsonlogger import JsonFormatter | ||
| from pythonjsonlogger.json import JsonFormatter | ||
|
||
| from celery.signals import after_setup_logger, after_setup_task_logger | ||
| from celery.schedules import crontab | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| import pytest | ||
| from plane.db.models import Sticky, Workspace | ||
|
|
||
|
|
||
| @pytest.mark.django_db | ||
| def test_default_sort_order_assignment(workspace, session_client): | ||
| # Create the first sticky | ||
| s1 = Sticky.objects.create( | ||
| workspace=workspace, | ||
| owner=session_client.user, | ||
| name="Sticky 1", | ||
| ) | ||
| assert s1.sort_order == 65535 | ||
|
|
||
| # Create the second sticky | ||
| s2 = Sticky.objects.create( | ||
| workspace=workspace, | ||
| owner=session_client.user, | ||
| name="Sticky 2", | ||
| ) | ||
| # According to the model logic, sort_order increments by +10000 | ||
| assert s2.sort_order == 65535 + 10000 | ||
|
|
||
|
|
||
| @pytest.mark.django_db | ||
| def test_api_returns_ordered_list(workspace, session_client): | ||
| Sticky.objects.create( | ||
| workspace=workspace, | ||
| owner=session_client.user, | ||
| name="Last", | ||
| sort_order=200, | ||
| ) | ||
| Sticky.objects.create( | ||
| workspace=workspace, | ||
| owner=session_client.user, | ||
| name="First", | ||
| sort_order=100, | ||
| ) | ||
|
|
||
| res = session_client.get(f"/api/workspaces/{workspace.slug}/stickies/") | ||
| results = res.data | ||
| assert results[0]["name"] == "First" | ||
| assert results[1]["name"] == "Last" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,122 @@ | ||
| # Copyright (c) 2023-present Plane Software, Inc. and contributors | ||
| # SPDX-License-Identifier: AGPL-3.0-only | ||
| # See the LICENSE file for details. | ||
|
|
||
| import pytest | ||
| from rest_framework import status | ||
|
|
||
| from plane.db.models import Page, Project, ProjectMember, ProjectPage | ||
|
|
||
|
|
||
| @pytest.fixture | ||
| def project(db, workspace, create_user): | ||
| project = Project.objects.create( | ||
| name="Pages Project", | ||
| identifier="PGS", | ||
| workspace=workspace, | ||
| created_by=create_user, | ||
| ) | ||
| ProjectMember.objects.create( | ||
| project=project, | ||
| member=create_user, | ||
| role=20, | ||
| is_active=True, | ||
| ) | ||
| return project | ||
|
|
||
|
|
||
| @pytest.fixture | ||
| def project_pages(db, workspace, project, create_user): | ||
| page_low = Page.objects.create( | ||
| workspace=workspace, | ||
| owned_by=create_user, | ||
| name="Low", | ||
| access=Page.PUBLIC_ACCESS, | ||
| sort_order=100, | ||
| ) | ||
| page_mid = Page.objects.create( | ||
| workspace=workspace, | ||
| owned_by=create_user, | ||
| name="Mid", | ||
| access=Page.PUBLIC_ACCESS, | ||
| sort_order=150, | ||
| ) | ||
| page_high = Page.objects.create( | ||
| workspace=workspace, | ||
| owned_by=create_user, | ||
| name="High", | ||
| access=Page.PUBLIC_ACCESS, | ||
| sort_order=200, | ||
| ) | ||
|
|
||
| for page in [page_low, page_mid, page_high]: | ||
| ProjectPage.objects.create( | ||
| project=project, | ||
| page=page, | ||
| workspace=workspace, | ||
| created_by=create_user, | ||
| ) | ||
|
|
||
| return {"low": page_low, "mid": page_mid, "high": page_high} | ||
|
|
||
|
|
||
| @pytest.mark.contract | ||
| class TestProjectPagesAPI: | ||
| def get_pages_url(self, workspace_slug: str, project_id: str) -> str: | ||
| return f"/api/workspaces/{workspace_slug}/projects/{project_id}/pages/" | ||
|
|
||
| def get_page_url(self, workspace_slug: str, project_id: str, page_id: str) -> str: | ||
| return f"/api/workspaces/{workspace_slug}/projects/{project_id}/pages/{page_id}/" | ||
|
|
||
| @pytest.mark.django_db | ||
| def test_list_pages_sorted_by_sort_order_desc(self, session_client, workspace, project, project_pages): | ||
| url = self.get_pages_url(workspace.slug, project.id) | ||
| response = session_client.get(url) | ||
|
|
||
| assert response.status_code == status.HTTP_200_OK | ||
| data = response.json() | ||
| assert [row["id"] for row in data] == [ | ||
| str(project_pages["high"].id), | ||
| str(project_pages["mid"].id), | ||
| str(project_pages["low"].id), | ||
| ] | ||
| assert all("sort_order" in row for row in data) | ||
|
|
||
| @pytest.mark.django_db | ||
| def test_patch_sort_order_updates_page(self, session_client, workspace, project, project_pages): | ||
| page = project_pages["mid"] | ||
| url = self.get_page_url(workspace.slug, project.id, page.id) | ||
| response = session_client.patch(url, {"sort_order": 250}, format="json") | ||
|
|
||
| assert response.status_code == status.HTTP_200_OK | ||
| page.refresh_from_db() | ||
| assert page.sort_order == 250 | ||
| assert response.json()["sort_order"] == 250 | ||
|
|
||
| @pytest.mark.django_db | ||
| def test_patch_sort_order_allowed_when_page_is_locked(self, session_client, workspace, project, project_pages): | ||
| page = project_pages["low"] | ||
| page.is_locked = True | ||
| page.save() | ||
|
|
||
| url = self.get_page_url(workspace.slug, project.id, page.id) | ||
| response = session_client.patch(url, {"sort_order": 175}, format="json") | ||
|
|
||
| assert response.status_code == status.HTTP_200_OK | ||
| page.refresh_from_db() | ||
| assert page.sort_order == 175 | ||
|
|
||
| @pytest.mark.django_db | ||
| def test_patch_non_sort_field_blocked_when_page_is_locked(self, session_client, workspace, project, project_pages): | ||
| page = project_pages["high"] | ||
| page.is_locked = True | ||
| page.save() | ||
|
|
||
| url = self.get_page_url(workspace.slug, project.id, page.id) | ||
| response = session_client.patch(url, {"name": "Renamed while locked"}, format="json") | ||
|
|
||
| assert response.status_code == status.HTTP_400_BAD_REQUEST | ||
| assert response.json()["error"] == "Page is locked" | ||
|
|
||
| page.refresh_from_db() | ||
| assert page.name == "High" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,7 +3,12 @@ import * as dotenv from "@dotenvx/dotenvx"; | |
| import { reactRouter } from "@react-router/dev/vite"; | ||
| import { defineConfig } from "vite"; | ||
| import tsconfigPaths from "vite-tsconfig-paths"; | ||
| import { joinUrlPath } from "@plane/utils"; | ||
|
|
||
| const normalizeBasePath = (value: string): string => { | ||
| const trimmed = value.trim(); | ||
| if (!trimmed || trimmed === "/") return "/"; | ||
| return `/${trimmed.replace(/^\/+|\/+$/g, "")}/`; | ||
| }; | ||
|
Comment on lines
+7
to
+11
|
||
|
|
||
| dotenv.config({ path: path.resolve(__dirname, ".env") }); | ||
|
|
||
|
|
@@ -15,7 +20,7 @@ const viteEnv = Object.keys(process.env) | |
| return a; | ||
| }, {}); | ||
|
|
||
| const basePath = joinUrlPath(process.env.VITE_SPACE_BASE_PATH ?? "", "/") ?? "/"; | ||
| const basePath = normalizeBasePath(process.env.VITE_SPACE_BASE_PATH ?? ""); | ||
|
|
||
| export default defineConfig(() => ({ | ||
| base: basePath, | ||
|
|
||
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.
Duplicate
order_by()renders query parameter ineffective.Line 103 applies
order_by(self.request.GET.get("order_by", "-created_at")), but Line 105 immediately overrides it withorder_by("-is_favorite", "-sort_order", "-created_at"). In Django, only the lastorder_by()call takes effect.If the intention is to always sort by favorites, sort_order, and created_at, remove Line 103. If the query parameter should take precedence, remove Line 105.
🔧 Proposed fix (remove the unused order_by)
.prefetch_related("labels") - .order_by(self.request.GET.get("order_by", "-created_at")) .order_by("-is_favorite", "-sort_order", "-created_at")🤖 Prompt for AI Agents