Skip to content

Workspace labels #3101 #6917

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

Open
wants to merge 1 commit into
base: preview
Choose a base branch
from
Open

Conversation

YurkinPark
Copy link

@YurkinPark YurkinPark commented Apr 11, 2025

Description

This PR contains changes related to scope of labels: we have labels specified for projects, this update makes it scoped in workspace. Idea is to make it easier for dashboards especially: sometimes you need to build dashboards based on labels attached to issue and its a bit painful when you have 50+ projects inside

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • Feature (non-breaking change which adds functionality)
  • Improvement (change that would cause existing functionality to not work as expected)
  • Code refactoring
  • Performance improvements
  • Documentation update

Test Scenarios

  • make sure you have labels (may be with same name in different projects)
  • apply this PRs patch
  • restart services (especially api which brings with 2 migrations)
  • check labels under workspace on [workspace]/labels uri: all the labels should be there, duplicated labels must be removed, issues updated accordingly

References

#3101

Summary by CodeRabbit

  • New Features

    • Introduced workspace-level label management, allowing users to create, edit, and organize labels that are not tied to a specific project.
    • Added a dedicated "Labels" section to the workspace sidebar for easier access to workspace-wide labels.
  • Improvements

    • Unified label selection and filtering across the app, supporting both workspace and project scopes.
    • Enhanced permission controls for label editing based on workspace or project association.
    • Streamlined label management UI with clearer distinction between workspace and project labels.
  • Bug Fixes

    • Improved error handling and validation for label creation and updates, preventing duplicate names within the same workspace.
  • Refactor

    • Simplified and consolidated label-related components and services for more consistent behavior and easier maintenance.
    • Updated label data types and APIs to support labels without a project association.
  • Chores

    • Updated database constraints to ensure label name uniqueness within a workspace or project.

Copy link
Contributor

coderabbitai bot commented Apr 11, 2025

Walkthrough

This update introduces workspace-scoped label management, including new API endpoints, viewsets, and UI components. It refactors label operations to support both workspace and project scopes, updates permission checks, and revises unique constraints in the database. The frontend adapts to these changes by generalizing label fetching, storage, and usage across the application.

Changes

File(s) Change Summary
apiserver/plane/app/urls/workspace.py, apiserver/plane/app/views/init.py, apiserver/plane/app/views/workspace/label.py Added WorkspaceLabelViewSet for workspace-scoped label CRUD; registered new RESTful routes; updated imports.
apiserver/plane/db/models/label.py, apiserver/plane/db/migrations/0097_remove_label_unique_name_when_project_null_and_not_deleted_and_more.py Modified/removal of unique constraints on label names to consolidate uniqueness enforcement.
apiserver/plane/space/serializer/issue.py Updated LabelSerializer to allow project_detail to be null and made only workspace a read-only field.
packages/constants/src/workspace.ts, web/ce/components/workspace/sidebar/helper.tsx Added workspace label navigation item and icon to sidebar.
web/core/services/issue/issue_label.service.ts Refactored label service to support workspace-level operations and optional project context in CRUD methods.
web/core/store/label.store.ts Unified label handling for workspace and project scopes; added new computed properties and updated CRUD/fetching methods.
packages/types/src/issues.d.ts Made project_id in IIssueLabel nullable.
web/core/components/issues/issue-layouts/properties/label-dropdown.tsx, web/core/components/issues/issue-layouts/properties/labels.tsx, web/core/components/issues/select/label.tsx Refactored label fetching and usage to be workspace-centric; removed project-specific dependencies.
web/core/components/inbox/inbox-filter/filters/filter-selection.tsx, web/core/components/inbox/sidebar/inbox-list-item.tsx, web/core/components/issues/archived-issues-header.tsx, web/core/components/issues/filters.tsx, web/core/components/issues/issue-layouts/filters/applied-filters/roots/archived-issue.tsx, web/core/components/issues/issue-layouts/filters/applied-filters/roots/cycle-root.tsx, web/core/components/issues/issue-layouts/filters/applied-filters/roots/draft-issue.tsx, web/core/components/issues/issue-layouts/filters/applied-filters/roots/module-root.tsx, web/core/components/issues/issue-layouts/filters/applied-filters/roots/project-root.tsx, web/core/components/issues/issue-layouts/filters/applied-filters/roots/project-view-root.tsx, web/core/components/views/form.tsx Renamed label variables from projectLabels to labels for workspace scope; updated props accordingly.
web/core/components/labels/setting-label-group.tsx, web/core/components/labels/setting-label-item.tsx, web/core/components/labels/setting-label-list.tsx, web/core/components/labels/index.ts Renamed and refactored label settings components from project-specific to general settings; updated exports and imports.
web/core/components/labels/stores/index.ts Added MobX store for label form state management.
web/core/components/labels/delete-label-modal.tsx Made label deletion support optional projectId; removed requirement for projectId to be present.
web/core/components/labels/label-drag-n-drop-HOC.tsx Updated permission checks for label editability to consider workspace scope if project_id is absent.
web/core/store/issue/issue-details/sub_issues.store.ts Updated label fetching in sub-issues store to use workspace-scoped method.
web/core/components/labels/create-update-label-inline.tsx Minor syntax update in export, no logic change.
web/core/components/issues/issue-detail/label/select/label-select.tsx Refactored label fetching to workspace scope; improved error handling.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Frontend
    participant API
    participant DB

    User->>Frontend: Navigates to Workspace Labels
    Frontend->>API: GET /workspaces/{slug}/issue-labels/
    API->>DB: Query labels WHERE workspace=slug AND project IS NULL
    DB-->>API: Return label list
    API-->>Frontend: Respond with label data
    Frontend-->>User: Display workspace labels

    User->>Frontend: Creates/Updates/Deletes a label
    Frontend->>API: POST/PUT/PATCH/DELETE /workspaces/{slug}/issue-labels/...
    API->>DB: Perform label operation (with unique constraint enforcement)
    DB-->>API: Success/Error
    API-->>Frontend: Respond with result
    Frontend-->>User: Update UI accordingly
Loading

Suggested labels

🌟improvement

Suggested reviewers

  • prateekshourya29
  • anmolsinghbhatia

Poem

In the workspace garden, labels bloom anew,
With endpoints fresh and colors true.
From project roots, they now arise,
Across the space, beneath wide skies.
Rabbits hop and organize with glee—
This patch brings order, for all to see!
🐇🌱✨

✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🔭 Outside diff range comments (3)
web/core/components/issues/issue-detail/label/root.tsx (1)

80-82: 🛠️ Refactor suggestion

Update error message to reference workspace instead of project

The error checking logic still looks for a project-specific error message, which should be updated to reflect the workspace-level scope.

-          if (error && (error as any).error === "Label with the same name already exists in the project")
-            errMessage = t("label.create.already_exists");
+          if (error && (error as any).error === "Label with the same name already exists in the workspace")
+            errMessage = t("label.create.already_exists");
web/app/[workspaceSlug]/(projects)/labels/page.tsx (1)

21-34: 🛠️ Refactor suggestion

Potential issue: Unused scroll handling code

The component still contains auto-scroll setup code that references the scrollableContainerRef, but the actual scrollable container has been removed from the JSX. This could lead to unnecessary code execution.

Consider removing the unused scroll handling code since the scrollable container is no longer being rendered:

-  const scrollableContainerRef = useRef<HTMLDivElement | null>(null);
-
-  // Enable Auto Scroll for Labels list
-  useEffect(() => {
-    const element = scrollableContainerRef.current;
-
-    if (!element) return;
-
-    return combine(
-      autoScrollForElements({
-        element,
-      })
-    );
-  }, [scrollableContainerRef?.current]);
web/core/components/issues/issue-layouts/properties/label-dropdown.tsx (1)

145-152: 🛠️ Refactor suggestion

Update projectId check in handleAddLabel function.

The function still checks for projectId before proceeding, but with workspace-scoped labels, this check might not be appropriate.

-  const handleAddLabel = async (labelName: string) => {
-    if (!projectId) return;
-    setSubmitting(true);
-    const label = await createLabel(workspaceSlug, { name: labelName, color: getRandomLabelColor() });
-    onChange([...value, label.id]);
-    setQuery("");
-    setSubmitting(false);
-  };
+  const handleAddLabel = async (labelName: string) => {
+    if (!workspaceSlug) return;
+    setSubmitting(true);
+    const label = await createLabel(workspaceSlug, { name: labelName, color: getRandomLabelColor() });
+    onChange([...value, label.id]);
+    setQuery("");
+    setSubmitting(false);
+  };
🧹 Nitpick comments (11)
apiserver/plane/db/models/label.py (1)

21-23: Enforce uniqueness if needed
Introducing a workspace foreign key is good for workspace-level ownership. However, consider whether name collisions per workspace should be restricted or allowed. You might benefit from a unique constraint on (workspace, name, deleted_at).

web/core/store/label.store.ts (2)

107-108: Update comment to reflect the new property name.

The comment still refers to "labelMap" which isn't aligned with the new property name.

/**
- * Returns the labelMap in a tree format
+ * Returns the labels in a tree format
 */

202-203: Update error message for consistency.

The error message still references "workspace store" which is fine, but could be more specific.

-    console.log("Failed to update label from workspace store");
+    console.log("Failed to update label in workspace");
apiserver/plane/app/views/__init__.py (1)

135-135: Consider updating documentation for BulkCreateIssueLabelsEndpoint

With labels now managed at the workspace level, it's worth considering whether BulkCreateIssueLabelsEndpoint should be updated or if additional documentation is needed to explain its continued use in the project context.

apiserver/plane/db/migrations/0093_manage_label_project.py (2)

27-28: Remove commented code

There's a commented line that appears to be an alternative approach that wasn't used. This should be removed to avoid confusion.

-            #label.project = None

37-37: Line length exceeds PEP 8 recommendations

According to the static analysis hint, this line is too long (90 characters) compared to PEP 8 recommendations (88 characters). Consider breaking it into multiple lines.

-        migrations.RunPython(clear_label_project, reverse_code=migrations.RunPython.noop),
+        migrations.RunPython(
+            clear_label_project, 
+            reverse_code=migrations.RunPython.noop
+        ),
🧰 Tools
🪛 Ruff (0.8.2)

37-37: Line too long (90 > 88)

(E501)

apiserver/plane/app/views/issue/label.py (1)

14-15: Remove unused imports

The imported WorkSpaceBasePermission and Workspace are not used in this file following the removal of the LabelViewSet class.

- from plane.app.permissions import allow_permission, WorkSpaceBasePermission, ROLE
- from plane.db.models import Project, Label, Workspace
+ from plane.app.permissions import allow_permission, ROLE
+ from plane.db.models import Project, Label
🧰 Tools
🪛 Ruff (0.8.2)

14-14: plane.app.permissions.WorkSpaceBasePermission imported but unused

Remove unused import: plane.app.permissions.WorkSpaceBasePermission

(F401)


15-15: plane.db.models.Workspace imported but unused

Remove unused import: plane.db.models.Workspace

(F401)

web/core/components/issues/issue-layouts/properties/label-dropdown.tsx (1)

127-130: Remove unused dependency from useCallback.

The projectId parameter is no longer used in the function body, but is still included in the dependencies array.

-}, [storeLabels, workspaceSlug, projectId, fetchLabels]);
+}, [storeLabels, workspaceSlug, fetchLabels]);
web/core/components/labels/setting-label-list.tsx (1)

99-99: Use optional chaining for nullable property access.

The current code checks label.children before accessing its length property, but optional chaining would be more concise and safer.

-                  if (label.children && label.children.length) {
+                  if (label.children?.length) {
🧰 Tools
🪛 Biome (1.9.4)

[error] 99-99: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

apiserver/plane/app/views/workspace/label.py (2)

12-13: Static analysis: line too long
Static analysis flagged line 13 (94 characters). Consider splitting the import for enhanced readability.

-from plane.app.permissions.workspace import WorkSpaceBasePermission, WorkspaceViewerPermission
+from plane.app.permissions.workspace import (
+    WorkSpaceBasePermission,
+    WorkspaceViewerPermission
+)
🧰 Tools
🪛 Ruff (0.8.2)

13-13: Line too long (94 > 88)

(E501)


49-63: Mismatch in error message
When handling IntegrityError, the message says "project" instead of "workspace," which might confuse users.

-except IntegrityError:
-    return Response(
-        {"error": "Label with the same name already exists in the project"},
-        status=status.HTTP_400_BAD_REQUEST,
-    )
+except IntegrityError:
+    return Response(
+        {"error": "Label with the same name already exists in this workspace"},
+        status=status.HTTP_400_BAD_REQUEST,
+    )
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4c688b1 and 8e16d8f.

📒 Files selected for processing (52)
  • apiserver/plane/api/serializers/issue.py (0 hunks)
  • apiserver/plane/app/serializers/issue.py (1 hunks)
  • apiserver/plane/app/urls/issue.py (0 hunks)
  • apiserver/plane/app/urls/workspace.py (2 hunks)
  • apiserver/plane/app/views/__init__.py (2 hunks)
  • apiserver/plane/app/views/issue/label.py (1 hunks)
  • apiserver/plane/app/views/workspace/label.py (2 hunks)
  • apiserver/plane/db/migrations/0093_manage_label_project.py (1 hunks)
  • apiserver/plane/db/migrations/0094_remove_label_unique_name_when_project_null_and_not_deleted_and_more.py (1 hunks)
  • apiserver/plane/db/models/label.py (3 hunks)
  • apiserver/plane/space/serializer/issue.py (1 hunks)
  • docker-compose-local.yml (4 hunks)
  • packages/constants/src/workspace.ts (2 hunks)
  • web/app/[workspaceSlug]/(projects)/labels/header.tsx (1 hunks)
  • web/app/[workspaceSlug]/(projects)/labels/layout.tsx (1 hunks)
  • web/app/[workspaceSlug]/(projects)/labels/page.tsx (2 hunks)
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(detail)/header.tsx (2 hunks)
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(detail)/mobile-header.tsx (2 hunks)
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/draft-issues/header.tsx (2 hunks)
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(list)/mobile-header.tsx (2 hunks)
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(detail)/header.tsx (2 hunks)
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(detail)/mobile-header.tsx (2 hunks)
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(detail)/[viewId]/header.tsx (2 hunks)
  • web/ce/components/workspace/sidebar/helper.tsx (2 hunks)
  • web/ce/constants/project/settings/tabs.ts (0 hunks)
  • web/core/components/inbox/inbox-filter/filters/filter-selection.tsx (2 hunks)
  • web/core/components/inbox/sidebar/inbox-list-item.tsx (2 hunks)
  • web/core/components/issues/archived-issues-header.tsx (2 hunks)
  • web/core/components/issues/filters.tsx (2 hunks)
  • web/core/components/issues/issue-detail/label/root.tsx (1 hunks)
  • web/core/components/issues/issue-detail/label/select/label-select.tsx (3 hunks)
  • web/core/components/issues/issue-layouts/filters/applied-filters/roots/archived-issue.tsx (2 hunks)
  • web/core/components/issues/issue-layouts/filters/applied-filters/roots/cycle-root.tsx (2 hunks)
  • web/core/components/issues/issue-layouts/filters/applied-filters/roots/draft-issue.tsx (2 hunks)
  • web/core/components/issues/issue-layouts/filters/applied-filters/roots/module-root.tsx (2 hunks)
  • web/core/components/issues/issue-layouts/filters/applied-filters/roots/project-root.tsx (2 hunks)
  • web/core/components/issues/issue-layouts/filters/applied-filters/roots/project-view-root.tsx (2 hunks)
  • web/core/components/issues/issue-layouts/properties/label-dropdown.tsx (4 hunks)
  • web/core/components/issues/issue-layouts/properties/labels.tsx (5 hunks)
  • web/core/components/issues/issue-layouts/utils.tsx (1 hunks)
  • web/core/components/issues/select/label.tsx (4 hunks)
  • web/core/components/labels/create-update-label-inline.tsx (1 hunks)
  • web/core/components/labels/delete-label-modal.tsx (2 hunks)
  • web/core/components/labels/index.ts (1 hunks)
  • web/core/components/labels/setting-label-group.tsx (3 hunks)
  • web/core/components/labels/setting-label-item.tsx (2 hunks)
  • web/core/components/labels/setting-label-list.tsx (5 hunks)
  • web/core/components/labels/stores/index.ts (1 hunks)
  • web/core/components/views/form.tsx (3 hunks)
  • web/core/services/issue/issue_label.service.ts (1 hunks)
  • web/core/store/issue/issue-details/sub_issues.store.ts (1 hunks)
  • web/core/store/label.store.ts (7 hunks)
💤 Files with no reviewable changes (3)
  • web/ce/constants/project/settings/tabs.ts
  • apiserver/plane/api/serializers/issue.py
  • apiserver/plane/app/urls/issue.py
🧰 Additional context used
🧬 Code Graph Analysis (34)
web/core/components/labels/setting-label-item.tsx (1)
web/core/store/router.store.ts (1)
  • workspaceSlug (69-71)
web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(detail)/mobile-header.tsx (1)
web/core/store/label.store.ts (1)
  • labels (95-104)
web/core/components/issues/filters.tsx (1)
web/core/store/label.store.ts (1)
  • labels (95-104)
web/core/components/issues/issue-layouts/filters/applied-filters/roots/module-root.tsx (1)
web/core/store/label.store.ts (1)
  • labels (95-104)
web/core/components/issues/archived-issues-header.tsx (1)
web/core/store/label.store.ts (1)
  • labels (95-104)
web/core/components/issues/issue-detail/label/root.tsx (1)
web/core/store/router.store.ts (1)
  • workspaceSlug (69-71)
web/app/[workspaceSlug]/(projects)/labels/layout.tsx (3)
web/core/components/core/app-header.tsx (1)
  • AppHeader (13-27)
web/app/[workspaceSlug]/(projects)/labels/header.tsx (1)
  • WorkspaceLabelHeader (20-57)
web/core/components/core/content-wrapper.tsx (1)
  • ContentWrapper (12-16)
web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(detail)/[viewId]/header.tsx (1)
web/core/store/label.store.ts (1)
  • labels (95-104)
apiserver/plane/db/migrations/0093_manage_label_project.py (2)
apiserver/plane/db/models/issue.py (1)
  • Issue (104-253)
apiserver/plane/db/models/label.py (2)
  • Label (7-44)
  • save (31-41)
web/core/components/labels/delete-label-modal.tsx (1)
web/core/store/router.store.ts (1)
  • workspaceSlug (69-71)
web/core/components/issues/issue-layouts/filters/applied-filters/roots/project-view-root.tsx (1)
web/core/store/label.store.ts (1)
  • labels (95-104)
web/core/components/issues/issue-layouts/filters/applied-filters/roots/archived-issue.tsx (1)
web/core/store/label.store.ts (1)
  • labels (95-104)
apiserver/plane/app/views/issue/label.py (3)
apiserver/plane/app/serializers/issue.py (1)
  • LabelSerializer (298-309)
apiserver/plane/app/permissions/workspace.py (1)
  • WorkSpaceBasePermission (15-44)
apiserver/plane/db/models/label.py (1)
  • Label (7-44)
web/core/store/issue/issue-details/sub_issues.store.ts (1)
web/core/store/router.store.ts (1)
  • workspaceSlug (69-71)
web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/draft-issues/header.tsx (1)
web/core/store/label.store.ts (1)
  • labels (95-104)
web/core/components/issues/issue-layouts/filters/applied-filters/roots/cycle-root.tsx (1)
web/core/store/label.store.ts (1)
  • labels (95-104)
web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(detail)/header.tsx (1)
web/core/store/label.store.ts (1)
  • labels (95-104)
web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(detail)/header.tsx (1)
web/core/store/label.store.ts (1)
  • labels (95-104)
web/core/components/issues/issue-layouts/utils.tsx (1)
web/core/store/label.store.ts (2)
  • workspaceLabels (82-90)
  • labels (95-104)
apiserver/plane/app/views/workspace/label.py (3)
apiserver/plane/app/serializers/issue.py (3)
  • LabelSerializer (298-309)
  • create (132-207)
  • create (514-521)
apiserver/plane/db/models/label.py (2)
  • Label (7-44)
  • save (31-41)
apiserver/plane/app/permissions/workspace.py (2)
  • WorkSpaceBasePermission (15-44)
  • WorkspaceViewerPermission (89-96)
web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(detail)/mobile-header.tsx (1)
web/core/store/label.store.ts (1)
  • labels (95-104)
apiserver/plane/app/urls/workspace.py (1)
apiserver/plane/app/views/workspace/label.py (1)
  • LabelViewSet (30-86)
web/core/components/issues/issue-layouts/filters/applied-filters/roots/project-root.tsx (1)
web/core/store/label.store.ts (1)
  • labels (95-104)
web/core/components/inbox/sidebar/inbox-list-item.tsx (1)
web/core/store/label.store.ts (1)
  • labels (95-104)
web/core/components/views/form.tsx (1)
web/core/store/label.store.ts (1)
  • labels (95-104)
web/core/components/issues/issue-layouts/filters/applied-filters/roots/draft-issue.tsx (1)
web/core/store/label.store.ts (1)
  • labels (95-104)
web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(list)/mobile-header.tsx (1)
web/core/store/label.store.ts (1)
  • labels (95-104)
web/core/components/issues/issue-layouts/properties/labels.tsx (3)
packages/hooks/src/use-platform-os.tsx (1)
  • usePlatformOS (3-34)
web/core/services/issue/issue_label.service.ts (1)
  • getLabels (20-26)
web/core/store/label.store.ts (1)
  • labels (95-104)
apiserver/plane/app/views/__init__.py (2)
apiserver/plane/app/views/workspace/label.py (2)
  • LabelViewSet (30-86)
  • WorkspaceLabelsEndpoint (18-27)
apiserver/plane/app/views/issue/label.py (1)
  • BulkCreateIssueLabelsEndpoint (19-45)
web/core/components/inbox/inbox-filter/filters/filter-selection.tsx (1)
web/core/store/label.store.ts (1)
  • labels (95-104)
web/app/[workspaceSlug]/(projects)/labels/page.tsx (4)
web/core/hooks/store/user/user-permissions.ts (1)
  • useUserPermissions (7-12)
admin/core/hooks/store/use-workspace.tsx (1)
  • useWorkspace (6-10)
web/core/store/workspace/index.ts (1)
  • currentWorkspace (98-103)
web/core/components/labels/setting-label-list.tsx (1)
  • SettingsLabelList (25-145)
web/core/components/issues/issue-layouts/properties/label-dropdown.tsx (5)
packages/hooks/src/use-platform-os.tsx (1)
  • usePlatformOS (3-34)
web/core/services/issue/issue_label.service.ts (1)
  • getLabels (20-26)
web/core/hooks/store/user/user-permissions.ts (1)
  • useUserPermissions (7-12)
web/core/store/router.store.ts (2)
  • workspaceSlug (69-71)
  • projectId (85-87)
web/core/store/label.store.ts (1)
  • labels (95-104)
web/core/services/issue/issue_label.service.ts (1)
web/core/store/router.store.ts (1)
  • workspaceSlug (69-71)
web/core/store/label.store.ts (3)
packages/types/src/issues.d.ts (1)
  • IIssueLabelTree (125-127)
web/core/store/router.store.ts (1)
  • workspaceSlug (69-71)
web/core/services/issue/issue_label.service.ts (1)
  • IssueLabelService (7-51)
🪛 Ruff (0.8.2)
apiserver/plane/db/migrations/0093_manage_label_project.py

37-37: Line too long (90 > 88)

(E501)

apiserver/plane/app/views/issue/label.py

14-14: plane.app.permissions.WorkSpaceBasePermission imported but unused

Remove unused import: plane.app.permissions.WorkSpaceBasePermission

(F401)


15-15: plane.db.models.Workspace imported but unused

Remove unused import: plane.db.models.Workspace

(F401)

apiserver/plane/app/views/workspace/label.py

13-13: Line too long (94 > 88)

(E501)

🪛 Biome (1.9.4)
web/core/components/labels/setting-label-list.tsx

[error] 99-99: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

web/core/store/label.store.ts

[error] 110-110: This return should return a value because it is located in a getter.

(lint/suspicious/useGetterReturn)

🔇 Additional comments (115)
packages/constants/src/workspace.ts (2)

290-295: Add workspace-based element to sidebar
This addition neatly introduces a dedicated "labels" navigation item at the workspace level, consistent with the refactor toward workspace-wide labeling. Good job!


308-308: Include "labels" in navigation links
Ensuring that the newly introduced "labels" item is also listed in the dynamic navigation array is essential. This helps maintain consistent UI behavior.

apiserver/plane/space/serializer/issue.py (1)

48-48:

❓ Verification inconclusive

Confirm no lingering project references
Removing "project" from read_only_fields aligns with workspace-level label handling. Please verify that no references to project remain in code paths expecting it.


🏁 Script executed:

#!/bin/bash
# Looking for any usage of 'project' in LabelSerializer references

rg -A 5 'class LabelSerializer' | rg 'project'

Length of output: 46


Verify Removal of "project" References

The change to use only "workspace" in the read_only_fields of apiserver/plane/space/serializer/issue.py appears consistent with workspace-level label handling. Our initial search within the context of LabelSerializer did not return any references to "project". However, since the verification command produced no output (which might be due to limitations in the search), please manually verify that no code paths expecting "project" remain in the LabelSerializer logic.

  • Action: Manually review any additional uses of "project" in related serializer logic to confirm complete removal.
apiserver/plane/db/models/label.py (2)

4-4: Adopt new base model
Using BaseModel instead of WorkspaceBaseModel is consistent with the shift away from project-specific constraints. Ensure any workspace-level properties that were handled by the old base class are still addressed.


7-7: Refactor to a generic label class
Switching to class Label(BaseModel) confirms your intent to handle labels universally. This is a step in the right direction for workspace-wide labeling.

web/core/store/label.store.ts (4)

95-104: LGTM! The implementation of the labels getter looks good.

The method now correctly retrieves labels at the workspace level instead of project level, automatically fetching them if needed.


32-37: LGTM! Updated interface definition is consistent with workspace-level operations.

The method signatures have been appropriately updated to remove project-specific parameters, focusing on workspace-level operations.


141-154: LGTM! Updated fetchLabels implementation correctly uses workspace-level endpoints.

The implementation now works at the workspace level instead of project level, correctly updating the fetch status.


272-285: LGTM! deleteLabel implementation updated correctly for workspace-level operation.

The method has been simplified to work at the workspace level and properly handles cleanup.

web/core/components/labels/create-update-label-inline.tsx (1)

34-35: LGTM! Property reordering doesn't affect functionality.

The reordering of destructured properties is a cosmetic change and does not impact the component's behavior.

apiserver/plane/db/migrations/0094_remove_label_unique_name_when_project_null_and_not_deleted_and_more.py (1)

1-25: LGTM! Migration correctly removes project-specific constraints and field.

The migration is properly structured with the correct dependency and removes the constraints and field that are no longer needed for project-specific label uniqueness, which aligns with moving to workspace-level labels.

web/core/components/issues/filters.tsx (1)

49-49: LGTM! Variable renamed consistently with the store changes.

The component correctly uses the renamed property from the useLabel hook and passes it to the FilterSelection component, maintaining consistency with the changes in the label store.

Also applies to: 127-127

web/core/components/issues/issue-layouts/filters/applied-filters/roots/module-root.tsx (2)

19-19: Variable rename aligns with workspace-level label management

The change from projectLabels to labels aligns with the PR objective of centralizing labels at the workspace level instead of being project-specific. This correctly uses the labels getter from the label store.


85-85: Consistent variable name update

The prop has been updated to use the renamed labels variable, maintaining consistency with the change on line 19.

web/core/components/issues/issue-layouts/filters/applied-filters/roots/draft-issue.tsx (2)

18-18: Variable rename aligns with workspace-level label management

The change from projectLabels to labels aligns with the PR objective of centralizing labels at the workspace level instead of being project-specific. This correctly uses the labels getter from the label store.


70-70: Consistent variable name update

The prop has been updated to use the renamed labels variable, maintaining consistency with the change on line 18.

web/core/components/labels/setting-label-item.tsx (3)

30-30: Component renamed to reflect workspace-level scope

The component name change from ProjectSettingLabelItem to SettingLabelItem aligns with the broader refactoring to transition from project-specific label handling to workspace-level operations.


45-45: Removed project dependency from parameter destructuring

Removing projectId from the destructured parameters is consistent with the move to workspace-level operations, as labels are now associated with workspaces rather than projects.


49-55: Simplified removeFromGroup function to use workspace context

The function has been properly updated to remove the dependency on projectId and now operates solely at the workspace level, which aligns with the PR objectives.

web/app/[workspaceSlug]/(projects)/labels/layout.tsx (1)

1-14: Well-structured new workspace label layout component

The new WorkspaceLabelLayout component is appropriately structured with a clean implementation. It properly integrates the AppHeader component with the WorkspaceLabelHeader and wraps content in the ContentWrapper component.

The component follows the same pattern as other layout components in the application and supports the PR objective of centralizing label management at the workspace level.

web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(detail)/header.tsx (2)

75-75: Renaming projectLabels to labels aligns with workspace-level label management.

This change correctly updates the variable name to match the accessor in the label store, aligning with the PR objective of transitioning from project-specific to workspace-level label management.


253-253: Updated property usage from projectLabels to labels.

This change is consistent with the variable renaming above and ensures proper data is passed to the FilterSelection component.

web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(detail)/mobile-header.tsx (2)

57-57: Renaming projectLabels to labels aligns with workspace-level label management.

This change correctly updates the variable name to match the accessor in the label store as part of the workspace-level centralization of labels.


156-156: Updated property usage from projectLabels to labels.

This change properly updates the prop passed to FilterSelection to use the renamed labels variable, maintaining consistency across the codebase.

web/core/components/views/form.tsx (3)

53-53: Renaming projectLabels to labels aligns with workspace-level label management.

This change correctly updates the variable name to align with the label store's accessor method, which returns workspace-level labels rather than project-specific ones.


278-278: Updated FilterSelection component to use renamed labels variable.

This change maintains consistency with the variable renaming approach and ensures the FilterSelection component receives the correct workspace-level labels.


328-328: Updated AppliedFiltersList component to use renamed labels variable.

This change correctly passes the workspace-level labels to the AppliedFiltersList component, consistent with the overall approach of centralizing labels at the workspace level.

web/core/components/issues/issue-layouts/filters/applied-filters/roots/cycle-root.tsx (2)

20-20: Renaming projectLabels to labels aligns with workspace-level label management.

This change correctly updates the variable name to match the label store's accessor method, maintaining consistency across the codebase as part of the transition to workspace-level labels.


86-86: Updated AppliedFiltersList component to use renamed labels variable.

This change ensures the AppliedFiltersList component receives the correct workspace-level labels, consistent with the overall refactoring approach.

web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(detail)/mobile-header.tsx (1)

66-66: Appropriate variable renaming for workspace-level labels

The changes from projectLabels to labels align with the PR objective of moving labels from being project-specific to workspace-specific. The variable naming now correctly reflects that labels are scoped at the workspace level.

Also applies to: 175-175

apiserver/plane/app/serializers/issue.py (1)

299-309: Removed project references from LabelSerializer to support workspace-level labels

The removal of the "project_id" field from the fields list and "project" from the read_only_fields list correctly implements the shift from project-specific to workspace-specific labels. This ensures labels are now associated only with workspaces, aligning with the PR objective.

web/ce/components/workspace/sidebar/helper.tsx (1)

1-1: Added workspace-level labels navigation

The addition of the "labels" case with the Tag icon in the sidebar navigation is a logical enhancement that supports the PR objective. This allows users to access and manage labels at the workspace level, improving the workflow for building dashboards across multiple projects.

Also applies to: 13-14

web/core/components/issues/issue-layouts/filters/applied-filters/roots/archived-issue.tsx (1)

19-19: Updated variable naming for workspace-level labels

The changes from projectLabels to labels properly align with the PR objective of centralizing labels at the workspace level. This consistent variable renaming ensures that archived issues use the same workspace-scoped labels as other components in the system.

Also applies to: 75-75

web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(detail)/[viewId]/header.tsx (2)

65-65: Variable name updated to reflect workspace-level labels

The change from projectLabels to labels aligns with the PR objective of transitioning from project-specific labels to workspace-specific labels.


235-235: Prop name updated to match the renamed variable

This change correctly updates the prop name to match the renamed variable, ensuring consistency.

web/core/components/labels/delete-label-modal.tsx (3)

22-22: Removed projectId parameter to support workspace-level operations

The removal of projectId from the useParams hook aligns with the transition to workspace-level label management.


34-34: Updated condition to remove project-level dependency

The conditional check has been simplified to only verify workspaceSlug and data, removing the dependency on projectId which is no longer needed for workspace-level operations.


38-38: Updated method call to match new API signature

The call to deleteLabel has been updated to exclude projectId, passing only workspaceSlug and data.id as arguments, reflecting the API change for workspace-level label operations.

web/core/components/issues/issue-layouts/utils.tsx (3)

236-236: Updated destructuring to support both workspace and regular labels

The destructuring now includes both workspaceLabels and labels to support the transition to workspace-level label management.


238-239: Renamed variable to avoid naming collision

The variable labels has been renamed to llabels to avoid naming collision with the destructured labels. This allows for a clean transition to the new naming structure while maintaining the same functionality.


243-243: Updated map function to use the renamed variable

The .map() call has been updated to use llabels instead of the previous labels, maintaining consistency with the renamed variable.

web/core/components/inbox/sidebar/inbox-list-item.tsx (2)

34-34: Updated variable name for workspace-level labels

The destructuring has been updated from projectLabels to labels to align with the workspace-level label management approach.


99-99: Updated reference to use the renamed variable

The find method now correctly uses labels instead of projectLabels to match the renamed variable.

apiserver/plane/app/views/__init__.py (1)

64-64: LGTM: Import updated to reflect workspace-level label management

The import statement has been properly updated to import LabelViewSet from the .workspace.label module, aligning with the shift from project-specific to workspace-specific label management.

web/core/components/issues/archived-issues-header.tsx (2)

27-27: LGTM: Variable renamed to reflect workspace-level labels

Successfully updated from projectLabels to labels to align with the workspace-level approach to label management.


86-86: LGTM: Prop name updated to match the variable name change

The prop passed to FilterSelection has been correctly updated to use the renamed variable.

apiserver/plane/db/migrations/0093_manage_label_project.py (2)

10-14: LGTM: Handling workspace-level label creation

Good approach to check if a workspace-level label already exists before creating one. If it doesn't exist, the current label is properly converted by setting its project to None.


15-26:

❓ Verification inconclusive

Verify issue-label associations are preserved correctly

The migration correctly handles moving issue associations from project-specific labels to workspace-level labels. However, ensure that the IssueLabel model properly enforces any required constraints when creating new associations.


🏁 Script executed:

#!/bin/bash
# Check the structure of the IssueLabel model to ensure it can handle workspace-level labels
ast-grep --pattern $'class IssueLabel'

Length of output: 39


Verify IssueLabel Model Constraints for Workspace-Level Associations

The migration correctly moves issue associations from project-specific labels to workspace-level labels. However, our initial automated check for the IssueLabel model did not return any output. Please manually verify that the IssueLabel model properly enforces all required constraints—such as using unique_together or UniqueConstraint—to ensure data integrity when creating new associations.

  • File: apiserver/plane/db/migrations/0093_manage_label_project.py (Lines 15-26)
  • Action: Confirm that the underlying model (check its definition via the new script output or by manually reviewing its source file) includes the necessary constraints for workspace-level labels.

🏁 Script executed:

#!/bin/bash
echo "Searching for IssueLabel definition and any constraint configurations:"
rg "class IssueLabel" -n .
echo "Searching for potential constraints (unique_together or UniqueConstraint) related to IssueLabel:"
rg -n "IssueLabel.*(unique_together|UniqueConstraint)" .

Length of output: 834


Verify Uniqueness Constraints in IssueLabel Model

The migration (in apiserver/plane/db/migrations/0093_manage_label_project.py, Lines 15–26) moves associations from project-specific labels to workspace-level labels. Our automated searches did not clearly reveal uniqueness constraints (like those in a Meta class via unique_together or UniqueConstraint) in the IssueLabel model defined in apiserver/plane/db/models/issue.py. Please manually verify that the IssueLabel model enforces all required constraints to preserve data integrity when creating new associations.

Location for Verification:

  • File: apiserver/plane/db/models/issue.py (check around the IssueLabel class definition)
  • Confirm the presence of a Meta configuration with appropriate uniqueness constraints.

🏁 Script executed:

#!/bin/bash
echo "Displaying IssueLabel class definition in context from apiserver/plane/db/models/issue.py:"
rg "class IssueLabel" -C 10 apiserver/plane/db/models/issue.py

Length of output: 1003


ACTION: Verify IssueLabel Uniqueness Constraints

The migration in apiserver/plane/db/migrations/0093_manage_label_project.py (lines 15–26) moves issue associations from project-specific labels to workspace-level labels. However, the IssueLabel model in apiserver/plane/db/models/issue.py currently only defines the required foreign keys along with verbose naming in its Meta class—and does not implement any explicit uniqueness constraints (e.g., via unique_together or UniqueConstraint).

Please manually verify that this model configuration meets your data integrity requirements. In particular, confirm whether the absence of workspace-level uniqueness constraints is intentional or if additional constraints should be added to prevent duplicate associations.

File for Verification: apiserver/plane/db/models/issue.py (IssueLabel class definition)
Action Required:

  • Check if duplicate associations are a risk under the current design.
  • If uniqueness is required, update the Meta class accordingly.
  • Otherwise, document why duplicate records are acceptable or handled elsewhere.
web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(list)/mobile-header.tsx (2)

50-50: Clean variable renaming aligned with workspace-scoped labels

The variable has been appropriately renamed from projectLabels to labels to reflect the shift from project-specific to workspace-level label management.


160-160: Consistent prop naming with workspace labels

The labels prop usage here correctly aligns with the variable renaming above, maintaining consistency throughout the component.

web/core/components/labels/stores/index.ts (1)

1-26: Well-structured MobX store for label form state management

This new store effectively centralizes the label form's state management, which will help maintain consistent UI states across components that deal with labels. The implementation follows MobX best practices with proper observable definitions and action methods.

The transition to workspace-level labels likely requires this kind of centralized state management, making this a good addition to the codebase.

apiserver/plane/app/urls/workspace.py (2)

20-20: Added LabelViewSet to imports

The import change correctly adds the LabelViewSet which is needed for the new URL patterns below.


165-181: New workspace-level label API endpoints

These new URL patterns appropriately implement workspace-level label management, aligning with the PR's goal of transitioning from project-specific to workspace-level labels.

The endpoints follow RESTful practices and provide all necessary operations (list, create, retrieve, update, and delete) for managing labels at the workspace level.

web/core/store/issue/issue-details/sub_issues.store.ts (1)

344-344: Updated to fetch workspace-level labels

The method call has been correctly updated to fetch labels at the workspace level instead of the project level, removing the projectId parameter and using the fetchLabels method instead of fetchProjectLabels.

This change is consistent with the PR's objective of making labels workspace-specific rather than project-specific.

web/core/components/labels/index.ts (1)

4-6: Naming refactor supports shift to workspace-level labels

The renaming of exports from project-specific to more general names (project-setting-* to setting-*) aligns with the PR objective of moving labels from project scope to workspace scope. This change maintains consistency across the codebase.

web/core/components/issues/issue-layouts/filters/applied-filters/roots/project-view-root.tsx (2)

34-34: Variable renamed to match workspace-scoped approach

The variable rename from projectLabels to labels properly aligns with the workspace-level label management approach. This change reflects that labels are now being fetched at the workspace level rather than project level.


141-141: Updated prop to use workspace-scoped labels

Correctly updated the prop to use the renamed labels variable, maintaining consistency with the variable renaming above.

web/core/components/labels/setting-label-group.tsx (3)

13-13: Updated import to use renamed component

Import has been correctly updated to reflect the new component naming convention, ensuring consistency with other label-related component changes.


32-32: Renamed component to match workspace-level scope

Component has been renamed from ProjectSettingLabelGroup to SettingLabelGroup, properly aligning with the PR objective of moving from project-specific to workspace-level label management.


131-131: Updated child component reference

The child component reference has been correctly updated to use SettingLabelItem instead of ProjectSettingLabelItem, maintaining consistency with the renaming pattern.

apiserver/plane/app/views/issue/label.py (1)

19-45:

❓ Verification inconclusive

Verify migration of BulkCreateIssueLabelsEndpoint implementation

The BulkCreateIssueLabelsEndpoint class still references project_id when creating labels. Since labels are now scoped to workspaces rather than projects, this implementation may need to be updated or moved.


🏁 Script executed:

#!/bin/bash
# Check if there's a corresponding endpoint for workspace-level label creation
grep -r "BulkCreateIssueLabelsEndpoint" --include="*.py" apiserver/

Length of output: 403


Action Required: Verify BulkCreateIssueLabelsEndpoint Migration

The current implementation in apiserver/plane/app/views/issue/label.py still retrieves a project using project_id and assigns it to new labels—even though labels are now intended to be workspace-scoped. Please verify if this endpoint should be updated to work directly with workspaces (e.g., passing or extracting workspace_id without relying on project_id), or if the endpoint should be deprecated/moved in favor of a workspace-level creation endpoint.

  • File: apiserver/plane/app/views/issue/label.py (Lines 19-45)
  • Considerations:
    • Confirm whether the use of project_id is still required to derive workspace_id or if it should be replaced with a direct workspace_id parameter.
    • Check if an updated endpoint exists or is planned for workspace-level label creation.
web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/draft-issues/header.tsx (2)

37-37: Approve: Migrated from project-specific to workspace-level labels

This change aligns with the PR objective of changing the scope of labels from project-specific to workspace-specific. The useLabel hook now returns workspace-level labels rather than project-specific labels.


140-140: Approve: Updated FilterSelection component props

The FilterSelection component now receives workspace-level labels rather than project-specific labels, making it consistent with the new label scope.

web/core/components/issues/issue-layouts/properties/labels.tsx (6)

67-67: Approve: Migrated from project-specific to workspace-level label getter

Updated to use the workspace-level getLabels method instead of project-specific getProjectLabels, aligning with the PR objective of centralizing labels at the workspace level.


69-69: Approve: Removed project ID parameter from label retrieval

The label getter method no longer requires a project ID parameter, as labels are now scoped at the workspace level instead of project level.


87-88: Approve: Renamed variable for consistency

Renamed projectLabels to labels throughout the component to reflect the shift from project-specific to workspace-level labels.


128-132: Approve: Updated tooltip content reference

Updated the label reference in the tooltip content to use the workspace-level labels.


141-141: Approve: Updated dependency array

Updated the dependency array in the useMemo hook to reference the new labels variable instead of projectLabels.


182-183: Approve: Updated labels reference in rendering logic

The rendering logic now correctly uses the workspace-level labels variable.

web/core/components/inbox/inbox-filter/filters/filter-selection.tsx (2)

23-23: Approve: Migrated to workspace-level labels

Updated to use workspace-level labels from the useLabel hook, aligning with the PR objective of centralizing labels at the workspace level.


82-82: Approve: Updated FilterLabels component props

The FilterLabels component now receives workspace-level labels, maintaining consistency with the centralized label management approach.

web/core/components/issues/issue-layouts/filters/applied-filters/roots/project-root.tsx (2)

25-25: Approve: Migrated to workspace-level labels

Updated to use workspace-level labels from the useLabel hook, aligning with the PR objective of centralizing labels at the workspace level.


82-82: Approve: Updated AppliedFiltersList component props

The AppliedFiltersList component now receives workspace-level labels, maintaining consistency with the centralized label management approach.

web/app/[workspaceSlug]/(projects)/labels/header.tsx (3)

3-19: Good selection of imports and well-organized code

The imports are logically grouped by category (next, lucide-react, ui, components, hooks) with appropriate comments, making the code easy to follow.


20-32: Looks good! Proper permission handling for workspace-level operations

The component correctly uses useUserPermissions hook with permissions check at the workspace level for editing labels. The newLabel function properly interacts with the label form state.


33-56: Clean UI implementation following design patterns

The header component is well-structured with a clear separation between left (breadcrumbs) and right (action button) items. Conditional rendering based on permissions is correctly implemented.

web/core/components/issues/select/label.tsx (5)

51-51: Function references updated correctly to match workspace-level operations

The function references have been updated from project-specific (getProjectLabels, fetchProjectLabels) to workspace-level (getLabels, fetchLabels) operations, consistent with the changes across the codebase.


66-70: Variable renamed to match new API structure

The variable has been appropriately renamed from projectLabels to labels to reflect that labels are now workspace-scoped instead of project-scoped.


73-73: Updated fetch call to use workspace-level API

The fetch call has been updated to only use the workspace slug parameter, removing the project ID parameter, which aligns with the new workspace-level label API.


134-134: Updated reference in JSX to use renamed variable

The component correctly uses the renamed labels variable when mapping through values, maintaining functionality while adapting to the workspace-level label structure.


167-170: Updated references in conditional rendering

The component's conditional rendering logic has been updated to use the renamed labels variable, ensuring consistency throughout the component.

web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(detail)/header.tsx (2)

101-101: Variable reference updated for workspace-level label access

The destructured property from the useLabel hook has been updated from projectLabels to labels, aligning with the workspace-level label structure introduced in this PR.


285-285: Prop name updated consistently in component usage

The labels prop passed to the FilterSelection component has been updated to use the renamed labels variable instead of projectLabels, maintaining consistent naming throughout the application.

web/app/[workspaceSlug]/(projects)/labels/page.tsx (5)

10-12: Updated imports to use workspace-level components

The import has been updated from ProjectSettingsLabelList to SettingsLabelList and useProject has been replaced with useWorkspace, reflecting the shift to workspace-level label management.


17-17: Store hook updated to workspace context

The component now correctly uses currentWorkspace from the useWorkspace hook instead of currentProjectDetails from useProject, aligning with the workspace-level data management approach.


19-19: Page title updated to reflect workspace context

The page title now correctly uses the workspace name instead of the project name, consistent with the workspace-level label management approach.


36-38: Simplified permission check

The permission check has been simplified to only verify if workspaceUserInfo exists, which is appropriate for workspace-level access control.


43-43: Updated component rendering

The component now directly renders SettingsLabelList without the scrollable container div, which is consistent with the component's implementation as seen in the relevant code snippets.

docker-compose-local.yml (4)

60-61: Improved developer experience with shared packages setup.

Adding the packages volume mount and specific yarn command allows the web service to access shared code, which aligns with the shift to workspace-level operations and improves local development workflow.


77-78: Consistent configuration for space service.

The shared packages volume mount and yarn command provide consistency with other services, enabling proper access to shared components needed for workspace-level label operations.


93-94: Admin service properly configured with shared packages.

The admin service now correctly has access to the shared packages directory, maintaining consistency with other services in the development environment.


109-110: Live service configuration matches other services.

The addition of packages volume mount and yarn command ensures the live service has the same access to shared code as other services, creating a uniform development environment.

web/core/components/issues/issue-layouts/properties/label-dropdown.tsx (3)

79-82: Function names updated to reflect workspace-level labels.

The change from project-specific label functions to workspace-level functions aligns with the PR objective of centralizing label management.


84-84: Expanded permission check to include member role.

Allowing both admin and member roles to create labels at the workspace level provides more flexibility in label management.


86-87: Variable names updated to match workspace-level approach.

Renaming from project-specific to generic label variables maintains consistency with the new workspace-centric approach.

web/core/components/issues/issue-detail/label/select/label-select.tsx (4)

30-31: Function names updated to reflect workspace-level labels.

Changed from project-specific label functions to workspace-level functions, aligning with the centralized label management approach.


39-39: Expanded permission check to include member role.

Allowing both admin and member roles to create labels at the workspace level provides more flexibility in label management.


41-41: Updated to use workspace-level label getter.

Changed from getProjectLabels(projectId) to getLabels() to retrieve labels at the workspace level.


200-201: Address translation TODO comment.

The TODO comment about translating text should be addressed before finalizing the PR.

Please review all TODO comments in the codebase and ensure they're addressed before merging this PR. The comment on line 200 needs to be handled with proper translation.

web/core/components/labels/setting-label-list.tsx (6)

6-6: Added labelFormState to centralize form state management.

Using a shared state store for label form management improves code organization and maintainability.


17-18: Updated component imports to match workspace-level naming.

Changed from project-specific label components to generic workspace-level components, maintaining consistency with the new approach.


25-28: Renamed component and simplified params.

Component renamed from ProjectSettingsLabelList to SettingsLabelList and params simplified to only include workspaceSlug, aligning with workspace-level label management.


35-38: Updated hooks and permissions for workspace-level operations.

Renamed hooks and updated permission checks to operate at the workspace level, providing consistency with the centralized label management approach.


41-43: Simplified label operation callbacks.

Removed projectId parameter from label creation and update callbacks, focusing on workspace-level operations.


71-85: Improved form state management with centralized store.

The label form state is now managed through a centralized store, making the component more maintainable and reducing local state complexity.

apiserver/plane/app/views/workspace/label.py (6)

2-3: No issues with the new import
No immediate concerns with importing IntegrityError, as it's properly used to handle duplicate name conflicts.


10-10: Base classes import
The addition of BaseAPIView and BaseViewSet import is consistent with the rest of the code.


30-34: Introduction of LabelViewSet
Defining the serializer and referencing the Label model with workspace-level permissions is a solid approach to handle label operations.


35-45: Workspace scoping in get_queryset
The get_queryset method correctly filters by workspace__slug and optimizes queries with select_related, ensuring proper workspace-level label retrieval.


46-48: Correct cache invalidation
Using invalidate_cache on the workspace label path is a suitable choice for keeping responses consistent.


83-86: Destroy method override
Overriding destroy with workspace-level admin checks is consistent and enforces proper permissions.

web/core/services/issue/issue_label.service.ts (4)

20-26: getLabels method
Switching to issue-labels/ at the workspace scope aligns with the shift away from project specificity.


28-34: createIssueLabel
Removing the project-based parameter is correct, enabling label creation at the workspace level.


36-42: patchIssueLabel
Refactoring the endpoint to /issue-labels/${labelId}/ properly moves partial updates to the workspace scope.


44-50: deleteIssueLabel
Replacing the project-level URL with a workspace-level path allows label deletion without project constraints.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🔭 Outside diff range comments (1)
apiserver/plane/app/views/issue/label.py (1)

19-45: 💡 Verification agent

🧩 Analysis chain

Review transition strategy for BulkCreateIssueLabelsEndpoint

The BulkCreateIssueLabelsEndpoint still creates labels with both project_id and workspace_id. Considering the shift to workspace-scoped labels, should this endpoint be updated to align with the new approach? Currently, it continues to create project-scoped labels, which might be inconsistent with the new data model.


🏁 Script executed:

#!/bin/bash
# Check the label model definition to confirm if project_id is still a valid field
rg "class Label" -A 15 --type py

Length of output: 9175


Transition Strategy for BulkCreateIssueLabelsEndpoint – Action Needed
It appears that the BulkCreateIssueLabelsEndpoint is still passing both a project identifier and a workspace identifier when creating labels. Based on our review of the Label model and corresponding serializers (which now use a workspace foreign key without an explicit project field), the new data model favors workspace-scoped labels. To ensure consistency across endpoints, please consider removing the use of the project_id parameter from label creation and adjusting any related filtering logic accordingly.

  • File: apiserver/plane/app/views/issue/label.py (BulkCreateIssueLabelsEndpoint)
    • Remove the assignment of project_id when constructing a new Label instance.
    • Rely solely on workspace_id (derived from the project’s workspace) to associate labels.
🧹 Nitpick comments (13)
apiserver/plane/app/views/issue/label.py (1)

12-15: Remove unused imports

The imports for WorkSpaceBasePermission and Workspace are not used in this file after the removal of the LabelViewSet class. These should be removed to keep the imports clean.

- from .. import BaseAPIView
- from plane.app.serializers import LabelSerializer
- from plane.app.permissions import allow_permission, WorkSpaceBasePermission, ROLE
- from plane.db.models import Project, Label, Workspace
+ from .. import BaseAPIView
+ from plane.app.serializers import LabelSerializer
+ from plane.app.permissions import allow_permission, ROLE
+ from plane.db.models import Project, Label
🧰 Tools
🪛 Ruff (0.8.2)

14-14: plane.app.permissions.WorkSpaceBasePermission imported but unused

Remove unused import: plane.app.permissions.WorkSpaceBasePermission

(F401)


15-15: plane.db.models.Workspace imported but unused

Remove unused import: plane.db.models.Workspace

(F401)

apiserver/plane/db/migrations/0093_manage_label_project.py (1)

37-37: Minor style issue: line length

The line exceeds the recommended 88 character limit (90 > 88). Consider breaking it into multiple lines for better readability.

- migrations.RunPython(clear_label_project, reverse_code=migrations.RunPython.noop),
+ migrations.RunPython(
+     clear_label_project, 
+     reverse_code=migrations.RunPython.noop
+ ),
🧰 Tools
🪛 Ruff (0.8.2)

37-37: Line too long (90 > 88)

(E501)

web/core/components/labels/setting-label-list.tsx (2)

99-99: Use optional chaining for null/undefined check

The conditional check for label.children && label.children.length should use optional chaining for cleaner code.

- if (label.children && label.children.length) {
+ if (label.children?.length) {
🧰 Tools
🪛 Biome (1.9.4)

[error] 99-99: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


51-60: Simplify conditional logic

The conditional check and early return in the onDrop function can be simplified since the entire function body is inside the condition.

- if (workspaceSlug) {
-   updateLabelPosition(
-     workspaceSlug?.toString(),
-     draggingLabelId,
-     droppedParentId,
-     droppedLabelId,
-     dropAtEndOfList
-   );
-   return;
- }
+ if (!workspaceSlug) return;
+ 
+ updateLabelPosition(
+   workspaceSlug.toString(),
+   draggingLabelId,
+   droppedParentId,
+   droppedLabelId,
+   dropAtEndOfList
+ );
web/core/components/issues/issue-layouts/properties/label-dropdown.tsx (2)

86-87: Avoid reassigning the final list if storeLabels is absent.
Currently, the code sets labels = storeLabels only if storeLabels is nonempty; otherwise, it sticks with defaultOptions. Ensure this logic won’t cause unexpected fallback to default options if the store is temporarily empty (e.g., first load).

-let labels: IIssueLabel[] = defaultOptions;
-if (storeLabels && storeLabels.length > 0) labels = storeLabels;
+const labels: IIssueLabel[] = storeLabels?.length
+  ? storeLabels
+  : defaultOptions;

127-130: Handle concurrent dropdown openings and fetch operations.
When !storeLabels and workspaceSlug are present, the code triggers a new fetch operation, then marks isLoading as false on success. If the user reopens the dropdown in rapid succession, multiple parallel fetches might occur. Consider gating parallel requests or storing a loading flag in the store to prevent redundant fetches.

apiserver/plane/app/views/workspace/label.py (3)

60-61: Update error message to reflect workspace scope.
The error message still refers to “project” even though this code is now workspace-based. Replace “in the project” with “in the workspace” for clarity.

{"error": "Label with the same name already exists in the project"}
+{"error": "Label with the same name already exists in the workspace"}

67-68: Revise comment to align with workspace-based logic.
The inline comment still mentions “label name is unique within the project.” Replace references to “project” with “workspace” to keep the docstrings consistent.


13-13: Fix line length for readability.
Line 13 is more than 88 characters, which triggers the linter. You can break it into multiple lines for better readability and to comply with coding style.

-from plane.app.permissions.workspace import WorkSpaceBasePermission, WorkspaceViewerPermission
+from plane.app.permissions.workspace import (
+    WorkSpaceBasePermission,
+    WorkspaceViewerPermission,
+)
🧰 Tools
🪛 Ruff (0.8.2)

13-13: Line too long (94 > 88)

(E501)

web/core/store/label.store.ts (4)

107-119: Mitigate potential concurrent fetches.
_getLabels() automatically triggers fetchLabels(...) if labels aren’t fetched yet. Consider preventing multiple in-flight fetches for the same workspace. For example, maintain a loading or promise reference in fetchedMap so subsequent calls return the same pending promise or skip the redundant fetch.


139-145: Potential duplication between fetchLabels and fetchWorkspaceLabels.
Both methods retrieve labels from the server, with slightly different underlying API calls. Evaluate whether you can unify them to reduce the overhead of maintaining two near-identical endpoints.


192-207: Improve error handling flow in updateLabel.
If the PATCH request fails, you restore the original label and log to the console. Consider shifting logs to a logger or user-facing notification. This approach is good for rollback, but you might want to ensure the UI receives explicit failure feedback.


202-202: Avoid console logging production errors.
Production code typically uses a logger or an alerting system rather than console.log. This ensures errors are visible and trackable.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8e16d8f and f39ee13.

📒 Files selected for processing (53)
  • apiserver/plane/api/serializers/issue.py (0 hunks)
  • apiserver/plane/app/serializers/issue.py (1 hunks)
  • apiserver/plane/app/urls/issue.py (0 hunks)
  • apiserver/plane/app/urls/workspace.py (2 hunks)
  • apiserver/plane/app/views/__init__.py (2 hunks)
  • apiserver/plane/app/views/issue/label.py (1 hunks)
  • apiserver/plane/app/views/workspace/label.py (2 hunks)
  • apiserver/plane/db/migrations/0093_manage_label_project.py (1 hunks)
  • apiserver/plane/db/migrations/0094_remove_label_unique_name_when_project_null_and_not_deleted_and_more.py (1 hunks)
  • apiserver/plane/db/models/label.py (3 hunks)
  • apiserver/plane/space/serializer/issue.py (1 hunks)
  • docker-compose-local.yml (4 hunks)
  • packages/constants/src/workspace.ts (2 hunks)
  • web/app/[workspaceSlug]/(projects)/labels/header.tsx (1 hunks)
  • web/app/[workspaceSlug]/(projects)/labels/layout.tsx (1 hunks)
  • web/app/[workspaceSlug]/(projects)/labels/page.tsx (2 hunks)
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(detail)/header.tsx (2 hunks)
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(detail)/mobile-header.tsx (2 hunks)
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/draft-issues/header.tsx (2 hunks)
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(list)/mobile-header.tsx (2 hunks)
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(detail)/header.tsx (2 hunks)
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(detail)/mobile-header.tsx (2 hunks)
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(detail)/[viewId]/header.tsx (2 hunks)
  • web/ce/components/workspace/sidebar/helper.tsx (2 hunks)
  • web/ce/constants/project/settings/tabs.ts (0 hunks)
  • web/core/components/inbox/inbox-filter/filters/filter-selection.tsx (2 hunks)
  • web/core/components/inbox/sidebar/inbox-list-item.tsx (2 hunks)
  • web/core/components/issues/archived-issues-header.tsx (2 hunks)
  • web/core/components/issues/filters.tsx (2 hunks)
  • web/core/components/issues/issue-detail/label/create-label.tsx (1 hunks)
  • web/core/components/issues/issue-detail/label/root.tsx (2 hunks)
  • web/core/components/issues/issue-detail/label/select/label-select.tsx (4 hunks)
  • web/core/components/issues/issue-layouts/filters/applied-filters/roots/archived-issue.tsx (2 hunks)
  • web/core/components/issues/issue-layouts/filters/applied-filters/roots/cycle-root.tsx (2 hunks)
  • web/core/components/issues/issue-layouts/filters/applied-filters/roots/draft-issue.tsx (2 hunks)
  • web/core/components/issues/issue-layouts/filters/applied-filters/roots/module-root.tsx (2 hunks)
  • web/core/components/issues/issue-layouts/filters/applied-filters/roots/project-root.tsx (2 hunks)
  • web/core/components/issues/issue-layouts/filters/applied-filters/roots/project-view-root.tsx (2 hunks)
  • web/core/components/issues/issue-layouts/properties/label-dropdown.tsx (4 hunks)
  • web/core/components/issues/issue-layouts/properties/labels.tsx (5 hunks)
  • web/core/components/issues/issue-layouts/utils.tsx (1 hunks)
  • web/core/components/issues/select/label.tsx (4 hunks)
  • web/core/components/labels/create-update-label-inline.tsx (1 hunks)
  • web/core/components/labels/delete-label-modal.tsx (2 hunks)
  • web/core/components/labels/index.ts (1 hunks)
  • web/core/components/labels/setting-label-group.tsx (3 hunks)
  • web/core/components/labels/setting-label-item.tsx (2 hunks)
  • web/core/components/labels/setting-label-list.tsx (5 hunks)
  • web/core/components/labels/stores/index.ts (1 hunks)
  • web/core/components/views/form.tsx (3 hunks)
  • web/core/services/issue/issue_label.service.ts (1 hunks)
  • web/core/store/issue/issue-details/sub_issues.store.ts (1 hunks)
  • web/core/store/label.store.ts (7 hunks)
💤 Files with no reviewable changes (3)
  • web/ce/constants/project/settings/tabs.ts
  • apiserver/plane/api/serializers/issue.py
  • apiserver/plane/app/urls/issue.py
✅ Files skipped from review due to trivial changes (7)
  • web/core/components/issues/filters.tsx
  • web/core/components/issues/issue-layouts/filters/applied-filters/roots/cycle-root.tsx
  • apiserver/plane/space/serializer/issue.py
  • packages/constants/src/workspace.ts
  • web/core/components/labels/setting-label-group.tsx
  • web/core/components/labels/index.ts
  • docker-compose-local.yml
🚧 Files skipped from review as they are similar to previous changes (36)
  • web/core/components/issues/issue-layouts/filters/applied-filters/roots/draft-issue.tsx
  • web/core/components/labels/create-update-label-inline.tsx
  • web/core/components/issues/issue-layouts/filters/applied-filters/roots/module-root.tsx
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(detail)/mobile-header.tsx
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(detail)/header.tsx
  • web/core/components/issues/archived-issues-header.tsx
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(detail)/mobile-header.tsx
  • web/core/components/issues/issue-layouts/properties/labels.tsx
  • apiserver/plane/app/views/init.py
  • apiserver/plane/db/migrations/0094_remove_label_unique_name_when_project_null_and_not_deleted_and_more.py
  • web/ce/components/workspace/sidebar/helper.tsx
  • web/core/components/labels/delete-label-modal.tsx
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(detail)/[viewId]/header.tsx
  • web/core/components/issues/issue-layouts/filters/applied-filters/roots/archived-issue.tsx
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(list)/mobile-header.tsx
  • web/app/[workspaceSlug]/(projects)/labels/page.tsx
  • web/app/[workspaceSlug]/(projects)/labels/layout.tsx
  • web/core/components/issues/issue-layouts/filters/applied-filters/roots/project-view-root.tsx
  • web/core/components/issues/issue-layouts/filters/applied-filters/roots/project-root.tsx
  • web/core/components/issues/issue-detail/label/root.tsx
  • web/core/components/inbox/sidebar/inbox-list-item.tsx
  • web/core/components/issues/select/label.tsx
  • apiserver/plane/app/urls/workspace.py
  • web/app/[workspaceSlug]/(projects)/labels/header.tsx
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(detail)/header.tsx
  • web/core/components/inbox/inbox-filter/filters/filter-selection.tsx
  • web/core/components/labels/setting-label-item.tsx
  • apiserver/plane/db/models/label.py
  • web/core/components/issues/issue-layouts/utils.tsx
  • web/core/components/issues/issue-detail/label/select/label-select.tsx
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/draft-issues/header.tsx
  • apiserver/plane/app/serializers/issue.py
  • web/core/components/labels/stores/index.ts
  • web/core/store/issue/issue-details/sub_issues.store.ts
  • web/core/components/views/form.tsx
  • web/core/services/issue/issue_label.service.ts
🧰 Additional context used
🧬 Code Graph Analysis (3)
web/core/components/issues/issue-layouts/properties/label-dropdown.tsx (5)
packages/hooks/src/use-platform-os.tsx (1)
  • usePlatformOS (3-34)
web/core/services/issue/issue_label.service.ts (1)
  • getLabels (20-26)
web/core/hooks/store/user/user-permissions.ts (1)
  • useUserPermissions (7-12)
web/core/store/router.store.ts (2)
  • workspaceSlug (69-71)
  • projectId (85-87)
web/core/store/label.store.ts (1)
  • labels (95-97)
web/core/store/label.store.ts (3)
packages/types/src/issues.d.ts (1)
  • IIssueLabelTree (125-127)
web/core/store/router.store.ts (1)
  • workspaceSlug (69-71)
web/core/services/issue/issue_label.service.ts (1)
  • IssueLabelService (7-51)
web/core/components/labels/setting-label-list.tsx (7)
web/core/hooks/store/user/user-permissions.ts (1)
  • useUserPermissions (7-12)
web/core/hooks/use-resolved-asset-path.tsx (1)
  • useResolvedAssetPath (10-26)
web/core/components/labels/create-update-label-inline.tsx (2)
  • TLabelOperationsCallbacks (14-17)
  • CreateUpdateLabelInline (33-222)
web/core/components/labels/stores/index.ts (1)
  • labelFormState (25-25)
web/core/store/label.store.ts (2)
  • labels (95-97)
  • labelsTree (102-105)
web/core/components/labels/setting-label-group.tsx (1)
  • SettingLabelGroup (32-157)
web/core/components/labels/setting-label-item.tsx (1)
  • SettingLabelItem (30-115)
🪛 Ruff (0.8.2)
apiserver/plane/app/views/issue/label.py

14-14: plane.app.permissions.WorkSpaceBasePermission imported but unused

Remove unused import: plane.app.permissions.WorkSpaceBasePermission

(F401)


15-15: plane.db.models.Workspace imported but unused

Remove unused import: plane.db.models.Workspace

(F401)

apiserver/plane/app/views/workspace/label.py

13-13: Line too long (94 > 88)

(E501)

apiserver/plane/db/migrations/0093_manage_label_project.py

37-37: Line too long (90 > 88)

(E501)

🪛 Biome (1.9.4)
web/core/components/labels/setting-label-list.tsx

[error] 99-99: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

🔇 Additional comments (10)
web/core/components/issues/issue-detail/label/create-label.tsx (2)

70-70: Consistent with workspace-scoped label management

The removal of projectId from the createLabel call aligns with the PR's objective of migrating labels from project scope to workspace scope. This change ensures labels are created at the workspace level.


68-72:

✅ Verification successful

Verify transition strategy for project reference

While the label creation has been moved to workspace scope, the component still checks for projectId in the guard clause and uses it when updating the issue. This appears to be an intentional transition strategy where issues remain project-scoped while labels are workspace-scoped.


🏁 Script executed:

#!/bin/bash
# Check if other files in the codebase still reference projectId with issues
rg "updateIssue.*workspaceSlug.*projectId" --type ts

Length of output: 6230


Transition Strategy Verified: Project Reference Usage is Consistent
After verifying the occurrences of updateIssue throughout the codebase, it’s clear that the use of both workspaceSlug and projectId is intentional and consistent. The guard clause in create-label.tsx that checks for projectId is aligned with the overall strategy where labels are workspace-scoped while issues remain project-scoped. No changes are needed based on the current transition strategy.

  • The rg search confirms that all update operations across the repository pass both workspaceSlug and projectId, including in the file under review.
  • The guard clause (if (!workspaceSlug || !projectId || isSubmitting) return;) correctly prevents unwanted updates when the project context is missing.
apiserver/plane/db/migrations/0093_manage_label_project.py (1)

7-29: Well-structured migration for label transition

This migration effectively handles the transition from project-scoped to workspace-scoped labels by:

  1. Keeping one unique label per name at workspace level
  2. Transferring associations from duplicate labels to the kept label
  3. Cleaning up redundant labels

This is a critical migration that enables the shift to workspace-scoped labels while preserving existing data relationships.

web/core/components/labels/setting-label-list.tsx (3)

6-6: Improved state management with centralized store

Using labelFormState from a centralized store rather than local component state is a good improvement. This makes state management more predictable and easier to maintain.

Also applies to: 71-82


25-25: Consistent renaming for workspace-scoped labels

The component renaming from ProjectSettingsLabelList to SettingsLabelList and updating accessor methods to use workspace-level operations (labels instead of projectLabels) maintains consistency with the project's migration to workspace-scoped labels.

Also applies to: 35-43


38-38: Broadened permission scope appropriately

Permissions have been appropriately broadened to include both ADMIN and MEMBER roles at the workspace level, which aligns with making labels a workspace-level resource.

web/core/components/issues/issue-layouts/properties/label-dropdown.tsx (2)

79-81: Ensure proper error handling for label fetching.
While this code correctly calls fetchLabels to populate the store, it does not handle rejected promises within this component. Consider adding a catch block or an error-handling callback to manage potential errors, for example, by showing a notification or fallback UI.

Would you like me to generate a script to verify how errors are handled, or are you confident this is addressed elsewhere?


84-84: Confirm workspace-level role checks.
allowPermissions([EUserProjectRoles.ADMIN, EUserProjectRoles.MEMBER], EUserPermissionsLevel.WORKSPACE, workspaceSlug) looks appropriate for validating creation permissions at the workspace level. Just verify that older code paths do not rely on project-level roles.

web/core/store/label.store.ts (2)

21-22: Validate computed properties.
labels and labelsTree both rely on _getLabels(). Check for possible race conditions if _getLabels() is already fetching while another component accesses labelsTree. This is generally safe under MobX but confirm the final user experience if multiple fetch attempts occur.


276-284: Cascading label deletions.
Deleting a label removes it from labelMap and calls syncIssuesWithDeletedLabels. Confirm that all references to the deleted label are removed across the UI state, especially if you rely on labelsTree or other caches.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🔭 Outside diff range comments (1)
apiserver/plane/app/views/issue/label.py (1)

19-45: 💡 Verification agent

🧩 Analysis chain

Consider migrating BulkCreateIssueLabelsEndpoint to be workspace-scoped.

This endpoint still creates project-scoped labels by setting both project_id and workspace_id. Since the PR is migrating label management from project to workspace level, this endpoint seems inconsistent with that goal.

Let me check if there are any other references to this endpoint:


🏁 Script executed:

#!/bin/bash
# Check if this endpoint is still used or referenced anywhere in the codebase
rg -A 2 -B 2 "BulkCreateIssueLabelsEndpoint" --glob "!apiserver/plane/app/views/issue/label.py"

Length of output: 1101


Action: Migrate BulkCreateIssueLabelsEndpoint to align with workspace-level label management

The endpoint currently creates labels with both project_id and workspace_id (derived via the project), and it’s still referenced in project-scoped URLs (e.g. in apiserver/plane/app/urls/issue.py with a path containing /projects/<uuid:project_id>/bulk-create-labels/). Given that the PR’s direction is to migrate label management to the workspace level, please consider revising this endpoint to remove the dependency on a project context. In particular:

  • Update the endpoint to derive or directly receive a workspace identifier, rather than relying on a project lookup.
  • Adjust the URL routing (and any related documentation) to reflect workspace scoping.
  • Confirm that any business logic tied to project-level label handling is migrated to operate solely within the workspace scope.
🧹 Nitpick comments (7)
apiserver/plane/app/views/issue/label.py (1)

14-15: Remove unused imports.

The imports WorkSpaceBasePermission and Workspace are imported but not used in this file.

-from plane.app.permissions import allow_permission, WorkSpaceBasePermission, ROLE
-from plane.db.models import Project, Label, Workspace
+from plane.app.permissions import allow_permission, ROLE
+from plane.db.models import Project, Label
🧰 Tools
🪛 Ruff (0.8.2)

14-14: plane.app.permissions.WorkSpaceBasePermission imported but unused

Remove unused import: plane.app.permissions.WorkSpaceBasePermission

(F401)


15-15: plane.db.models.Workspace imported but unused

Remove unused import: plane.db.models.Workspace

(F401)

web/app/[workspaceSlug]/(projects)/labels/page.tsx (1)

21-34: Unused scrollable container reference.

You're setting up a scrollable container reference and auto-scroll functionality, but the reference isn't attached to any element after removing the scrollable container div that previously wrapped the label list.

Either remove the unused reference and auto-scroll functionality or reattach it to the appropriate element:

-  const scrollableContainerRef = useRef<HTMLDivElement | null>(null);
-
-  // Enable Auto Scroll for Labels list
-  useEffect(() => {
-    const element = scrollableContainerRef.current;
-
-    if (!element) return;
-
-    return combine(
-      autoScrollForElements({
-        element,
-      })
-    );
-  }, [scrollableContainerRef?.current]);
apiserver/plane/db/migrations/0093_manage_label_project.py (1)

15-30: Consider adding progress logging to the migration.

This migration processes all labels and potentially transfers many issues between labels, which could be time-consuming for large workspaces. Adding progress logging would help administrators monitor the migration progress.

 def clear_label_project(apps, schema_editor):
     Label = apps.get_model('db', 'Label')
+    total_labels = Label.objects.count()
+    print(f"Starting label migration for {total_labels} labels...")
+    processed = 0
     for label in Label.objects.all():
+        processed += 1
+        if processed % 100 == 0:
+            print(f"Processed {processed}/{total_labels} labels...")
         existing = Label.objects.filter(name=label.name, project_id=None).first()
         if existing is None:
             label.project = None
             label.save()
         else:
             ### Need to move all the current's label issues to existing one.
             # Process issues in batches to avoid long-running transactions
             issues_to_move = list(Issue.objects.filter(labels__id=label.id).all())
+            if issues_to_move:
+                print(f"Moving {len(issues_to_move)} issues from label '{label.name}' (ID: {label.id}) to existing label (ID: {existing.id})")
             for batch in [issues_to_move[i:i+100] for i in range(0, len(issues_to_move), 100)]:
                 with transaction.atomic():
                     for issue_to_move in batch:
                         issue_to_move.labels.remove(label.id)
                         l_connection = IssueLabel.objects.create(
                             issue=issue_to_move,
                             label_id=existing.id,
                             workspace=issue_to_move.workspace,
                             project=issue_to_move.project,
                         )
                         issue_to_move.save()
                         l_connection.save()
             label.delete()
+    print(f"Label migration completed. Processed {total_labels} labels.")
🧰 Tools
🪛 Ruff (0.8.2)

18-18: Line too long (95 > 88)

(E501)

web/core/components/labels/setting-label-list.tsx (2)

99-114: Use optional chaining for label.children.

There's a potential null reference when accessing label.children in the condition check. You should use optional chaining for better safety.

-                  if (label.children && label.children.length) {
+                  if (label.children?.length) {
🧰 Tools
🪛 Biome (1.9.4)

[error] 99-99: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


6-6: Consider maintaining separation of concerns.

The component is now importing and directly using labelFormState from a local store. This creates a tight coupling between the component and the store. Consider passing the state as props for better reusability and testability.

-import { labelFormState } from "./stores";
+import { useLabelFormState } from "./hooks";

// Inside component
+const labelFormState = useLabelFormState();
apiserver/plane/app/views/workspace/label.py (2)

58-62: Error message still references "project" instead of "workspace"

The error message still mentions "project" even though the labels have been migrated to the workspace level.

-                {"error": "Label with the same name already exists in the project"},
+                {"error": "Label with the same name already exists in the workspace"},

67-68: Comment still references "project" instead of "workspace"

The comment still refers to "project" uniqueness when checking for label names, but it should reference "workspace" for consistency with the migration to workspace-scoped labels.

-        # Check if the label name is unique within the project
+        # Check if the label name is unique within the workspace
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f39ee13 and 7ae82e7.

📒 Files selected for processing (53)
  • apiserver/plane/api/serializers/issue.py (0 hunks)
  • apiserver/plane/app/serializers/issue.py (1 hunks)
  • apiserver/plane/app/urls/issue.py (0 hunks)
  • apiserver/plane/app/urls/workspace.py (2 hunks)
  • apiserver/plane/app/views/__init__.py (2 hunks)
  • apiserver/plane/app/views/issue/label.py (1 hunks)
  • apiserver/plane/app/views/workspace/label.py (2 hunks)
  • apiserver/plane/db/migrations/0093_manage_label_project.py (1 hunks)
  • apiserver/plane/db/migrations/0094_remove_label_unique_name_when_project_null_and_not_deleted_and_more.py (1 hunks)
  • apiserver/plane/db/models/label.py (3 hunks)
  • apiserver/plane/space/serializer/issue.py (1 hunks)
  • docker-compose-local.yml (4 hunks)
  • packages/constants/src/workspace.ts (2 hunks)
  • web/app/[workspaceSlug]/(projects)/labels/header.tsx (1 hunks)
  • web/app/[workspaceSlug]/(projects)/labels/layout.tsx (1 hunks)
  • web/app/[workspaceSlug]/(projects)/labels/page.tsx (2 hunks)
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(detail)/header.tsx (2 hunks)
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(detail)/mobile-header.tsx (2 hunks)
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/draft-issues/header.tsx (2 hunks)
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(list)/mobile-header.tsx (2 hunks)
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(detail)/header.tsx (2 hunks)
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(detail)/mobile-header.tsx (2 hunks)
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(detail)/[viewId]/header.tsx (2 hunks)
  • web/ce/components/workspace/sidebar/helper.tsx (2 hunks)
  • web/ce/constants/project/settings/tabs.ts (0 hunks)
  • web/core/components/inbox/inbox-filter/filters/filter-selection.tsx (2 hunks)
  • web/core/components/inbox/sidebar/inbox-list-item.tsx (2 hunks)
  • web/core/components/issues/archived-issues-header.tsx (2 hunks)
  • web/core/components/issues/filters.tsx (2 hunks)
  • web/core/components/issues/issue-detail/label/create-label.tsx (1 hunks)
  • web/core/components/issues/issue-detail/label/root.tsx (2 hunks)
  • web/core/components/issues/issue-detail/label/select/label-select.tsx (4 hunks)
  • web/core/components/issues/issue-layouts/filters/applied-filters/roots/archived-issue.tsx (2 hunks)
  • web/core/components/issues/issue-layouts/filters/applied-filters/roots/cycle-root.tsx (2 hunks)
  • web/core/components/issues/issue-layouts/filters/applied-filters/roots/draft-issue.tsx (2 hunks)
  • web/core/components/issues/issue-layouts/filters/applied-filters/roots/module-root.tsx (2 hunks)
  • web/core/components/issues/issue-layouts/filters/applied-filters/roots/project-root.tsx (2 hunks)
  • web/core/components/issues/issue-layouts/filters/applied-filters/roots/project-view-root.tsx (2 hunks)
  • web/core/components/issues/issue-layouts/properties/label-dropdown.tsx (4 hunks)
  • web/core/components/issues/issue-layouts/properties/labels.tsx (5 hunks)
  • web/core/components/issues/issue-layouts/utils.tsx (1 hunks)
  • web/core/components/issues/select/label.tsx (4 hunks)
  • web/core/components/labels/create-update-label-inline.tsx (1 hunks)
  • web/core/components/labels/delete-label-modal.tsx (2 hunks)
  • web/core/components/labels/index.ts (1 hunks)
  • web/core/components/labels/setting-label-group.tsx (3 hunks)
  • web/core/components/labels/setting-label-item.tsx (2 hunks)
  • web/core/components/labels/setting-label-list.tsx (5 hunks)
  • web/core/components/labels/stores/index.ts (1 hunks)
  • web/core/components/views/form.tsx (3 hunks)
  • web/core/services/issue/issue_label.service.ts (1 hunks)
  • web/core/store/issue/issue-details/sub_issues.store.ts (1 hunks)
  • web/core/store/label.store.ts (7 hunks)
💤 Files with no reviewable changes (3)
  • apiserver/plane/api/serializers/issue.py
  • web/ce/constants/project/settings/tabs.ts
  • apiserver/plane/app/urls/issue.py
✅ Files skipped from review due to trivial changes (4)
  • web/core/components/issues/issue-layouts/filters/applied-filters/roots/cycle-root.tsx
  • apiserver/plane/app/views/init.py
  • web/core/components/labels/setting-label-group.tsx
  • web/core/components/labels/index.ts
🚧 Files skipped from review as they are similar to previous changes (41)
  • web/core/components/issues/filters.tsx
  • web/core/components/issues/archived-issues-header.tsx
  • web/core/components/issues/issue-layouts/filters/applied-filters/roots/draft-issue.tsx
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(detail)/mobile-header.tsx
  • web/core/store/issue/issue-details/sub_issues.store.ts
  • web/app/[workspaceSlug]/(projects)/labels/layout.tsx
  • web/core/components/labels/create-update-label-inline.tsx
  • apiserver/plane/db/migrations/0094_remove_label_unique_name_when_project_null_and_not_deleted_and_more.py
  • web/core/components/issues/issue-layouts/utils.tsx
  • web/core/components/labels/setting-label-item.tsx
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(detail)/mobile-header.tsx
  • web/core/components/issues/issue-detail/label/root.tsx
  • apiserver/plane/app/serializers/issue.py
  • web/core/components/labels/delete-label-modal.tsx
  • web/core/components/issues/issue-layouts/properties/labels.tsx
  • web/ce/components/workspace/sidebar/helper.tsx
  • web/core/components/issues/issue-layouts/filters/applied-filters/roots/archived-issue.tsx
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/draft-issues/header.tsx
  • web/core/components/issues/select/label.tsx
  • apiserver/plane/space/serializer/issue.py
  • web/core/components/issues/issue-layouts/filters/applied-filters/roots/project-view-root.tsx
  • web/core/components/issues/issue-detail/label/create-label.tsx
  • web/app/[workspaceSlug]/(projects)/labels/header.tsx
  • web/core/components/issues/issue-layouts/filters/applied-filters/roots/module-root.tsx
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(list)/mobile-header.tsx
  • apiserver/plane/db/models/label.py
  • web/core/components/inbox/inbox-filter/filters/filter-selection.tsx
  • packages/constants/src/workspace.ts
  • web/core/components/views/form.tsx
  • web/core/components/labels/stores/index.ts
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(detail)/[viewId]/header.tsx
  • web/core/components/issues/issue-layouts/filters/applied-filters/roots/project-root.tsx
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(detail)/header.tsx
  • web/core/components/issues/issue-layouts/properties/label-dropdown.tsx
  • web/core/components/issues/issue-detail/label/select/label-select.tsx
  • docker-compose-local.yml
  • web/core/services/issue/issue_label.service.ts
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(detail)/header.tsx
  • web/core/components/inbox/sidebar/inbox-list-item.tsx
  • apiserver/plane/app/urls/workspace.py
  • web/core/store/label.store.ts
🧰 Additional context used
🪛 Ruff (0.8.2)
apiserver/plane/app/views/workspace/label.py

13-13: Line too long (94 > 88)

(E501)

apiserver/plane/app/views/issue/label.py

14-14: plane.app.permissions.WorkSpaceBasePermission imported but unused

Remove unused import: plane.app.permissions.WorkSpaceBasePermission

(F401)


15-15: plane.db.models.Workspace imported but unused

Remove unused import: plane.db.models.Workspace

(F401)

apiserver/plane/db/migrations/0093_manage_label_project.py

18-18: Line too long (95 > 88)

(E501)


39-39: Line too long (90 > 88)

(E501)

🪛 Biome (1.9.4)
web/core/components/labels/setting-label-list.tsx

[error] 99-99: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

🔇 Additional comments (4)
apiserver/plane/db/migrations/0093_manage_label_project.py (1)

38-40: Add reverse migration for robustness.

The current migration uses migrations.RunPython.noop for the reverse operation, making it impossible to rollback this migration. Consider implementing a safer approach that would preserve data for rollbacks.

While full reversal may be complex, a warning notice could be added:

-    operations = [
-        migrations.RunPython(clear_label_project, reverse_code=migrations.RunPython.noop),
-    ]
+    operations = [
+        migrations.RunPython(
+            clear_label_project,
+            reverse_code=lambda apps, schema_editor: print("WARNING: Cannot reverse label project clearing. Manual data restoration needed.")
+        ),
+    ]
🧰 Tools
🪛 Ruff (0.8.2)

39-39: Line too long (90 > 88)

(E501)

web/core/components/labels/setting-label-list.tsx (1)

38-39:

✅ Verification successful

Verify permission checks across the application.

You've updated the permission check to allow both ADMIN and MEMBER roles at the workspace level. Ensure this change is consistently applied across all label-related components and APIs.


🏁 Script executed:

#!/bin/bash
# Check permission checks in label-related files
rg -A 2 -B 2 "EUserPermissions.ADMIN.*WORKSPACE" --type ts

Length of output: 10879


Permission Check Update Verified

I've confirmed that the update to include both ADMIN and MEMBER roles for workspace-level permission checks in the label components is consistently applied. Specifically:

  • In web/core/components/labels/setting-label-list.tsx, the isEditable constant correctly uses allowPermissions([EUserPermissions.ADMIN, EUserPermissions.MEMBER], EUserPermissionsLevel.WORKSPACE).
  • In web/app/[workspaceSlug]/(projects)/labels/header.tsx, you correctly see the same updated check.

There appears to be no inconsistency across the label-related components. Please also ensure that any related label API endpoints adopt a similar approach if applicable.

apiserver/plane/app/views/workspace/label.py (2)

30-45: Well-structured new LabelViewSet implementation!

The LabelViewSet is designed appropriately with proper serializer, model, and permission classes. The get_queryset method efficiently filters by workspace slug and uses select_related for performance optimization.


84-87:

❓ Verification inconclusive

Consider checking for label usage before deletion

The destroy method allows admins to delete labels without checking if they're currently used by issues, which might leave orphaned references. Consider adding a check or handling this case gracefully.


🏁 Script executed:

#!/bin/bash
# Search for any references to the Label model that might create orphaned references
# when a label is deleted

# Look for models that might have ForeignKey or ManyToMany relationships with Label
rg -A 3 "ForeignKey\(.*Label" --glob "*.py"
rg -A 3 "ManyToManyField\(.*Label" --glob "*.py"

# Look for related_name attributes on the Label model
rg "related_name" apiserver/plane/db/models/label.py -A 2 -B 2

# Check if there's any cascading behavior defined for label deletion
rg "on_delete" --glob "*.py" | grep -i label

Length of output: 33492


Action: Revisit Label Deletion Safety

The current implementation in apiserver/plane/app/views/workspace/label.py (lines 84–87) calls the superclass’s destroy method without verifying if the label is actively used. Our investigation shows that the Label model is referenced in several relationships across the codebase (e.g., in issues, pages, integrations) with on_delete set to either CASCADE or SET_NULL. While these settings prevent actual orphaned references at the database level, they can lead to unintended cascading deletions or data updates if a label in use is removed. Please verify that this behavior is intended. If preserving associated records is important, consider adding a pre-deletion check or confirmation step to ensure that a label isn’t inadvertently removed when it’s still linked to active entities.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
apiserver/plane/app/views/workspace/label.py (1)

68-76: Correctly implemented workspace filter in partial update

The workspace filter has been correctly added to the uniqueness check, which will properly scope label name uniqueness to the current workspace.

🧹 Nitpick comments (7)
apiserver/plane/app/views/issue/label.py (1)

14-15: Remove unused imports.

The following imports are not being used in this file:

  • WorkSpaceBasePermission from plane.app.permissions
  • Workspace from plane.db.models

These appear to be leftover from the refactoring where label management was moved from project scope to workspace scope.

-from plane.app.permissions import allow_permission, WorkSpaceBasePermission, ROLE
-from plane.db.models import Project, Label, Workspace
+from plane.app.permissions import allow_permission, ROLE
+from plane.db.models import Project, Label
🧰 Tools
🪛 Ruff (0.8.2)

14-14: plane.app.permissions.WorkSpaceBasePermission imported but unused

Remove unused import: plane.app.permissions.WorkSpaceBasePermission

(F401)


15-15: plane.db.models.Workspace imported but unused

Remove unused import: plane.db.models.Workspace

(F401)

web/core/components/issues/issue-layouts/properties/label-dropdown.tsx (2)

146-152: Remove unnecessary projectId check in handleAddLabel.

The function still checks if projectId exists before proceeding, but since label creation is now workspace-scoped and doesn't use projectId, this check may be unnecessary.

const handleAddLabel = async (labelName: string) => {
-  if (!projectId) return;
+  if (!workspaceSlug) return;
  setSubmitting(true);
  const label = await createLabel(workspaceSlug, { name: labelName, color: getRandomLabelColor() });
  onChange([...value, label.id]);
  setQuery("");
  setSubmitting(false);
};

130-130: Update dependency array in onOpen useCallback.

The dependency array still includes projectId even though it's no longer used in the function body. Update it to reflect the actual dependencies.

}, [storeLabels, workspaceSlug, projectId, fetchLabels]);
+}, [storeLabels, workspaceSlug, fetchLabels]);
web/core/components/labels/setting-label-list.tsx (3)

98-99: Consider using optional chaining for better code safety

When iterating through the label tree, using optional chaining for label.children would make the code more robust.

- {labelsTree.map((label, index) => {
-   if (label.children && label.children.length) {
+ {labelsTree.map((label, index) => {
+   if (label.children?.length) {
🧰 Tools
🪛 Biome (1.9.4)

[error] 99-99: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


41-42: Consider removing redundant toString() calls

The workspaceSlug parameter is likely already a string, making the .toString() calls redundant.

- createLabel: (data: Partial<IIssueLabel>) => createLabel(workspaceSlug?.toString(), data),
- updateLabel: (labelId: string, data: Partial<IIssueLabel>) => updateLabel(workspaceSlug?.toString(), labelId, data),
+ createLabel: (data: Partial<IIssueLabel>) => createLabel(workspaceSlug as string, data),
+ updateLabel: (labelId: string, data: Partial<IIssueLabel>) => updateLabel(workspaceSlug as string, labelId, data),

53-53: Remove redundant toString() conversion

The workspaceSlug is already handled as a string in the condition check above.

- workspaceSlug?.toString(),
+ workspaceSlug,
apiserver/plane/app/views/workspace/label.py (1)

60-60: Update error message to reference workspace instead of project

The error message still mentions "project" even though the code has been refactored to use workspace-level label management.

- {"error": "Label with the same name already exists in the project"},
+ {"error": "Label with the same name already exists in the workspace"},
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5de45a1 and a69fe55.

📒 Files selected for processing (52)
  • apiserver/plane/api/serializers/issue.py (0 hunks)
  • apiserver/plane/app/serializers/issue.py (1 hunks)
  • apiserver/plane/app/urls/issue.py (0 hunks)
  • apiserver/plane/app/urls/workspace.py (2 hunks)
  • apiserver/plane/app/views/__init__.py (2 hunks)
  • apiserver/plane/app/views/issue/label.py (1 hunks)
  • apiserver/plane/app/views/workspace/label.py (2 hunks)
  • apiserver/plane/db/migrations/0093_manage_label_project.py (1 hunks)
  • apiserver/plane/db/migrations/0094_remove_label_unique_name_when_project_null_and_not_deleted_and_more.py (1 hunks)
  • apiserver/plane/db/models/label.py (3 hunks)
  • apiserver/plane/space/serializer/issue.py (1 hunks)
  • packages/constants/src/workspace.ts (2 hunks)
  • web/app/[workspaceSlug]/(projects)/labels/header.tsx (1 hunks)
  • web/app/[workspaceSlug]/(projects)/labels/layout.tsx (1 hunks)
  • web/app/[workspaceSlug]/(projects)/labels/page.tsx (2 hunks)
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(detail)/header.tsx (2 hunks)
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(detail)/mobile-header.tsx (2 hunks)
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/draft-issues/header.tsx (2 hunks)
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(list)/mobile-header.tsx (2 hunks)
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(detail)/header.tsx (2 hunks)
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(detail)/mobile-header.tsx (2 hunks)
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(detail)/[viewId]/header.tsx (2 hunks)
  • web/ce/components/workspace/sidebar/helper.tsx (2 hunks)
  • web/ce/constants/project/settings/tabs.ts (0 hunks)
  • web/core/components/inbox/inbox-filter/filters/filter-selection.tsx (2 hunks)
  • web/core/components/inbox/sidebar/inbox-list-item.tsx (2 hunks)
  • web/core/components/issues/archived-issues-header.tsx (2 hunks)
  • web/core/components/issues/filters.tsx (2 hunks)
  • web/core/components/issues/issue-detail/label/create-label.tsx (1 hunks)
  • web/core/components/issues/issue-detail/label/root.tsx (2 hunks)
  • web/core/components/issues/issue-detail/label/select/label-select.tsx (4 hunks)
  • web/core/components/issues/issue-layouts/filters/applied-filters/roots/archived-issue.tsx (2 hunks)
  • web/core/components/issues/issue-layouts/filters/applied-filters/roots/cycle-root.tsx (2 hunks)
  • web/core/components/issues/issue-layouts/filters/applied-filters/roots/draft-issue.tsx (2 hunks)
  • web/core/components/issues/issue-layouts/filters/applied-filters/roots/module-root.tsx (2 hunks)
  • web/core/components/issues/issue-layouts/filters/applied-filters/roots/project-root.tsx (2 hunks)
  • web/core/components/issues/issue-layouts/filters/applied-filters/roots/project-view-root.tsx (2 hunks)
  • web/core/components/issues/issue-layouts/properties/label-dropdown.tsx (4 hunks)
  • web/core/components/issues/issue-layouts/properties/labels.tsx (5 hunks)
  • web/core/components/issues/issue-layouts/utils.tsx (1 hunks)
  • web/core/components/issues/select/label.tsx (4 hunks)
  • web/core/components/labels/create-update-label-inline.tsx (1 hunks)
  • web/core/components/labels/delete-label-modal.tsx (2 hunks)
  • web/core/components/labels/index.ts (1 hunks)
  • web/core/components/labels/setting-label-group.tsx (3 hunks)
  • web/core/components/labels/setting-label-item.tsx (2 hunks)
  • web/core/components/labels/setting-label-list.tsx (5 hunks)
  • web/core/components/labels/stores/index.ts (1 hunks)
  • web/core/components/views/form.tsx (3 hunks)
  • web/core/services/issue/issue_label.service.ts (1 hunks)
  • web/core/store/issue/issue-details/sub_issues.store.ts (1 hunks)
  • web/core/store/label.store.ts (7 hunks)
💤 Files with no reviewable changes (3)
  • apiserver/plane/api/serializers/issue.py
  • apiserver/plane/app/urls/issue.py
  • web/ce/constants/project/settings/tabs.ts
✅ Files skipped from review due to trivial changes (5)
  • web/core/components/issues/issue-layouts/filters/applied-filters/roots/archived-issue.tsx
  • web/core/store/issue/issue-details/sub_issues.store.ts
  • web/ce/components/workspace/sidebar/helper.tsx
  • web/core/components/issues/issue-layouts/filters/applied-filters/roots/module-root.tsx
  • web/core/components/labels/setting-label-group.tsx
🚧 Files skipped from review as they are similar to previous changes (38)
  • web/core/components/issues/issue-layouts/filters/applied-filters/roots/cycle-root.tsx
  • web/core/components/issues/filters.tsx
  • web/core/components/labels/create-update-label-inline.tsx
  • web/core/components/issues/archived-issues-header.tsx
  • web/core/components/issues/issue-layouts/filters/applied-filters/roots/draft-issue.tsx
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(list)/mobile-header.tsx
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(detail)/[viewId]/header.tsx
  • web/core/components/issues/issue-layouts/utils.tsx
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(detail)/mobile-header.tsx
  • apiserver/plane/app/serializers/issue.py
  • web/core/components/inbox/sidebar/inbox-list-item.tsx
  • apiserver/plane/db/migrations/0094_remove_label_unique_name_when_project_null_and_not_deleted_and_more.py
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(detail)/mobile-header.tsx
  • web/core/components/labels/setting-label-item.tsx
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(detail)/header.tsx
  • web/core/components/issues/issue-detail/label/create-label.tsx
  • web/core/components/labels/delete-label-modal.tsx
  • web/core/components/issues/issue-layouts/filters/applied-filters/roots/project-root.tsx
  • web/core/components/issues/select/label.tsx
  • apiserver/plane/app/urls/workspace.py
  • web/core/components/views/form.tsx
  • apiserver/plane/app/views/init.py
  • apiserver/plane/db/models/label.py
  • web/app/[workspaceSlug]/(projects)/labels/header.tsx
  • web/core/components/issues/issue-layouts/properties/labels.tsx
  • apiserver/plane/space/serializer/issue.py
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(detail)/header.tsx
  • packages/constants/src/workspace.ts
  • web/app/[workspaceSlug]/(projects)/labels/page.tsx
  • web/core/components/labels/stores/index.ts
  • web/core/components/inbox/inbox-filter/filters/filter-selection.tsx
  • web/app/[workspaceSlug]/(projects)/labels/layout.tsx
  • web/core/components/labels/index.ts
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/draft-issues/header.tsx
  • web/core/components/issues/issue-layouts/filters/applied-filters/roots/project-view-root.tsx
  • web/core/components/issues/issue-detail/label/root.tsx
  • web/core/services/issue/issue_label.service.ts
  • web/core/store/label.store.ts
🧰 Additional context used
🧬 Code Graph Analysis (2)
web/core/components/issues/issue-layouts/properties/label-dropdown.tsx (5)
packages/hooks/src/use-platform-os.tsx (1)
  • usePlatformOS (3-34)
web/core/services/issue/issue_label.service.ts (1)
  • getLabels (20-26)
web/core/hooks/store/user/user-permissions.ts (1)
  • useUserPermissions (7-12)
web/core/store/router.store.ts (2)
  • workspaceSlug (69-71)
  • projectId (85-87)
web/core/store/label.store.ts (1)
  • labels (95-97)
web/core/components/issues/issue-detail/label/select/label-select.tsx (3)
web/core/components/issues/select/label.tsx (1)
  • IssueLabelSelect (33-263)
web/core/store/label.store.ts (1)
  • labels (95-97)
web/core/services/issue/issue_label.service.ts (1)
  • getLabels (20-26)
🪛 Ruff (0.8.2)
apiserver/plane/app/views/issue/label.py

14-14: plane.app.permissions.WorkSpaceBasePermission imported but unused

Remove unused import: plane.app.permissions.WorkSpaceBasePermission

(F401)


15-15: plane.db.models.Workspace imported but unused

Remove unused import: plane.db.models.Workspace

(F401)

apiserver/plane/app/views/workspace/label.py

13-13: Line too long (94 > 88)

(E501)

apiserver/plane/db/migrations/0093_manage_label_project.py

18-18: Line too long (95 > 88)

(E501)


39-39: Line too long (90 > 88)

(E501)

🪛 Biome (1.9.4)
web/core/components/labels/setting-label-list.tsx

[error] 99-99: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

🔇 Additional comments (17)
apiserver/plane/db/migrations/0093_manage_label_project.py (1)

1-40: Migration approach for label consolidation looks solid.

This migration correctly handles the transition from project-scoped to workspace-scoped labels by:

  1. Preserving unique labels by removing project associations
  2. Consolidating duplicate labels by transferring their issue associations
  3. Using atomic transactions to ensure data consistency
  4. Processing issues in batches to avoid long-running transactions

The transaction handling you've implemented addresses the previous review comment and will ensure database consistency during migration.

🧰 Tools
🪛 Ruff (0.8.2)

18-18: Line too long (95 > 88)

(E501)


39-39: Line too long (90 > 88)

(E501)

web/core/components/issues/issue-detail/label/select/label-select.tsx (6)

22-22: Interface updated to reflect workspace-scoped label operations.

The onAddLabel callback signature is correctly updated to remove the projectId parameter, aligning with the workspace-level label management approach.


30-30: Label fetching functions updated to use workspace scope.

Properly replaced project-scoped fetchProjectLabels and getProjectLabels with workspace-scoped fetchLabels and getLabels.


39-39: Permission check updated for workspace-level label management.

Correctly updated permission check to allow both ADMIN and MEMBER roles at the workspace level, which is consistent with the workspace-scoped label approach.


45-50: Label fetching function includes error handling.

Good implementation of error handling that prevents the component from getting stuck in a loading state if the API call fails, as suggested in a previous review.


112-112: Updated label creation to use workspace-level API.

The label creation handler now correctly passes only the workspace slug to onAddLabel, consistent with the new workspace-scoped API.


134-134: Simplified condition for fetching labels.

Properly updated the condition to fetch labels based only on the existence of labels and workspaceSlug, removing the project dependency.

web/core/components/issues/issue-layouts/properties/label-dropdown.tsx (4)

79-79: Updated import hooks for workspace-scoped label operations.

Correctly replaced project-scoped fetchProjectLabels and getProjectLabels with workspace-scoped fetchLabels and getLabels.


84-84: Permission check updated for workspace-level label management.

Correctly updated permission check to allow both ADMIN and MEMBER roles at the workspace level, which is consistent with the workspace-scoped label approach.


86-87: Variables renamed to reflect workspace-level scope.

Good choice of variable names that clearly reflect the workspace-level scope of labels.


128-130: Updated label fetching to use workspace scope.

Properly updated the condition to fetch labels based on workspace slug only, removing the project dependency.

web/core/components/labels/setting-label-list.tsx (3)

38-38: Expanded permissions for label management noted

The permission check has been broadened from project-level ADMIN to workspace-level ADMIN and MEMBER roles, which aligns with the workspace-scoped label management approach.


6-6: Good refactoring to centralized state management

Moving from local component state to a centralized MobX store improves code maintainability and consistency across components. This approach makes the label form state more predictable and easier to manage.

Also applies to: 71-82


27-27: Well-executed migration to workspace scope

The refactoring from project-specific to workspace-level label management is consistently implemented with appropriate variable renaming from projectLabels to labels and removing projectId dependencies.

Also applies to: 35-35

apiserver/plane/app/views/workspace/label.py (3)

49-50: Appropriate permission levels for label operations

The permission model is well-designed:

  • ADMIN and MEMBER can create and update labels (collaborative)
  • Only ADMIN can delete labels (restricted)

This aligns with best practices for workspace-level collaborative tools.

Also applies to: 65-66, 85-86


46-48: Effective cache invalidation strategy

The cache invalidation paths and strategy are properly implemented for workspace-level operations, ensuring that the label list is refreshed after mutations.

Also applies to: 64-64, 84-84


32-44: Well-structured queryset with appropriate filtering

The queryset is well-designed with:

  • Proper filtering by workspace slug
  • Useful related fields preloading (workspace, parent)
  • Correct ordering by sort_order
  • Distinctness to avoid duplicates

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

♻️ Duplicate comments (2)
web/core/store/label.store.ts (2)

108-114: Same explicit-return issue as above

Please apply the return undefined; pattern here as well to silence the linter and improve readability.

🧰 Tools
🪛 Biome (1.9.4)

[error] 110-110: This return should return a value because it is located in a getter.

(lint/suspicious/useGetterReturn)


124-129: Same explicit-return issue as above

projectLabels getter needs the same fix (return undefined;).

🧰 Tools
🪛 Biome (1.9.4)

[error] 125-125: This return should return a value because it is located in a getter.

(lint/suspicious/useGetterReturn)

🧹 Nitpick comments (11)
web/app/[workspaceSlug]/(projects)/labels/page.tsx (1)

36-38: Misleading error view parameter

The isProjectView prop is set to true in the NotAuthorizedView component, but this is a workspace-level view, not a project view.

- return <NotAuthorizedView section="settings" isProjectView />;
+ return <NotAuthorizedView section="settings" isWorkspaceView />;

Make sure the NotAuthorizedView component supports the isWorkspaceView prop.

apiserver/plane/db/migrations/0093_manage_label_project.py (1)

15-30: Consider adding error handling during issue migration

While the transaction handling is good, there's no explicit error handling to log or report issues during migration. Consider adding try/except blocks to capture and log any errors that might occur during the migration process.

 ### Need to move all the current's label issues to existing one.
 # Process issues in batches to avoid long-running transactions
 issues_to_move = list(Issue.objects.filter(labels__id=label.id).all())
 for batch in [issues_to_move[i:i+100] for i in range(0, len(issues_to_move), 100)]:
     with transaction.atomic():
+        try:
             for issue_to_move in batch:
                 issue_to_move.labels.remove(label.id)
                 l_connection = IssueLabel.objects.create(
                     issue=issue_to_move,
                     label_id=existing.id,
                     workspace=issue_to_move.workspace,
                     project=issue_to_move.project,
                 )
                 issue_to_move.save()
                 l_connection.save()
+        except Exception as e:
+            print(f"Error migrating issues for label {label.id} ({label.name}): {str(e)}")
+            raise  # Re-raise to trigger transaction rollback
 label.delete()
🧰 Tools
🪛 Ruff (0.8.2)

18-18: Line too long (95 > 88)

(E501)

web/core/components/issues/issue-layouts/properties/label-dropdown.tsx (1)

78-82: Stale comment & minor naming glitches

  1. The inline comment //hooks33 looks like a leftover artefact from an edit – it’s confusing for new readers.
  2. storeLabels is immediately dereferenced via getLabels() yet you keep the raw store value in the same identifier (storeLabels). Renaming to something like cachedLabels would read better and avoid the mental “store vs. in-store” confusion.

These are only cosmetic but tightening them up keeps the file tidy and self-explanatory.

web/core/components/labels/setting-label-list.tsx (1)

118-128: Use optional chaining to reduce nesting

Biome rightly flags this: the repeated label.children && label.children.length check can be collapsed:

- if (label.children && label.children.length) {
+ if (label.children?.length) {

Keeps the code succinct and avoids a redundant truthy check.

🧰 Tools
🪛 Biome (1.9.4)

[error] 118-118: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

apiserver/plane/app/views/workspace/label.py (2)

46-63: Error message still mentions “project”

The create handler is scoped to workspace labels, yet the response says “already exists in the project.
Update the wording to avoid confusion.

- {"error": "Label with the same name already exists in the project"},
+ {"error": "Label with the same name already exists in the workspace"},

12-13: Long import line exceeds 88 chars

PEP 8 warns at > 88 chars; consider breaking:

-from plane.utils.cache import cache_response, invalidate_cache
+from plane.utils.cache import (
+    cache_response,
+    invalidate_cache,
+)
🧰 Tools
🪛 Ruff (0.8.2)

13-13: Line too long (94 > 88)

(E501)

web/core/services/issue/issue_label.service.ts (2)

36-43: Use strong typing for the data argument

createLabel() accepts data: any, losing all compile-time checks you already get in the store layer (Partial<IIssueLabel>).

-async createLabel(workspaceSlug: string, projectId: string | undefined, data: any): Promise<IIssueLabel> {
+async createLabel(
+  workspaceSlug: string,
+  projectId: string | undefined,
+  data: Partial<IIssueLabel>
+): Promise<IIssueLabel> {

Apply the same change to patchIssueLabel() for consistency.


45-56: Minor readability nit – extract URL builder

The string-concatenation pattern is duplicated across create*, patch*, and delete*.
A tiny helper avoids repetition and guards against accidental path typos:

private buildIssueLabelUrl(workspaceSlug: string, projectId?: string, labelId?: string) {
  const base = `/api/workspaces/${workspaceSlug}${projectId ? `/projects/${projectId}` : ""}/issue-labels`;
  return labelId ? `${base}/${labelId}/` : `${base}/`;
}
web/core/store/label.store.ts (3)

14-14: Remove unused import

TYPOGRAPHY_ITEMS isn’t referenced anywhere in this file.
Dead imports increase bundle size and may confuse future readers.

-import { TYPOGRAPHY_ITEMS } from "@plane/editor";

22-32: API surface is confusing – labels getter vs getLabels() computedFn

You now expose both a labels computed getter and a memoised getLabels() that return the same dataset.
This duality makes it unclear which one callers should prefer and risks divergence later.

Consider de-duplicating:

  1. Keep only the getter and drop getLabels(); or
  2. Rename getLabels() to something explicit like selectLabels(workspaceSlug, projectId?).

269-276: Parameter naming inconsistency

updateLabel() uses project_id (snake-case) while the rest of the file uses projectId.
Stay consistent to avoid accidental shadowing and align with TypeScript conventions.

-updateLabel = async (workspaceSlug: string, project_id: string | undefined, labelId: string, data: Partial<IIssueLabel>) => {
+updateLabel = async (workspaceSlug: string, projectId: string | undefined, labelId: string, data: Partial<IIssueLabel>) => {-  const response = await this.issueLabelService.patchIssueLabel(workspaceSlug, project_id, labelId, data);
+  const response = await this.issueLabelService.patchIssueLabel(workspaceSlug, projectId, labelId, data);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a69fe55 and 4e3eb14.

📒 Files selected for processing (49)
  • apiserver/bin/docker-entrypoint-api-local.sh (1 hunks)
  • apiserver/plane/app/urls/workspace.py (2 hunks)
  • apiserver/plane/app/views/__init__.py (1 hunks)
  • apiserver/plane/app/views/workspace/label.py (2 hunks)
  • apiserver/plane/db/migrations/0093_manage_label_project.py (1 hunks)
  • apiserver/plane/db/migrations/0093_page_moved_to_page_page_moved_to_project_and_more.py (0 hunks)
  • apiserver/plane/space/serializer/issue.py (1 hunks)
  • packages/constants/src/workspace.ts (2 hunks)
  • packages/types/src/issues.d.ts (1 hunks)
  • web/app/[workspaceSlug]/(projects)/labels/header.tsx (1 hunks)
  • web/app/[workspaceSlug]/(projects)/labels/layout.tsx (1 hunks)
  • web/app/[workspaceSlug]/(projects)/labels/page.tsx (1 hunks)
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(detail)/header.tsx (2 hunks)
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(detail)/mobile-header.tsx (2 hunks)
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/draft-issues/header.tsx (2 hunks)
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(list)/mobile-header.tsx (2 hunks)
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(detail)/header.tsx (2 hunks)
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(detail)/mobile-header.tsx (2 hunks)
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/settings/(with-sidebar)/labels/page.tsx (2 hunks)
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(detail)/[viewId]/header.tsx (2 hunks)
  • web/app/[workspaceSlug]/(projects)/workspace-views/header.tsx (2 hunks)
  • web/ce/components/workspace/sidebar/helper.tsx (2 hunks)
  • web/core/components/inbox/inbox-filter/filters/filter-selection.tsx (2 hunks)
  • web/core/components/inbox/sidebar/inbox-list-item.tsx (2 hunks)
  • web/core/components/issues/archived-issues-header.tsx (2 hunks)
  • web/core/components/issues/filters.tsx (2 hunks)
  • web/core/components/issues/issue-detail/label/select/label-select.tsx (3 hunks)
  • web/core/components/issues/issue-layouts/filters/applied-filters/roots/archived-issue.tsx (2 hunks)
  • web/core/components/issues/issue-layouts/filters/applied-filters/roots/cycle-root.tsx (2 hunks)
  • web/core/components/issues/issue-layouts/filters/applied-filters/roots/draft-issue.tsx (2 hunks)
  • web/core/components/issues/issue-layouts/filters/applied-filters/roots/module-root.tsx (2 hunks)
  • web/core/components/issues/issue-layouts/filters/applied-filters/roots/project-root.tsx (2 hunks)
  • web/core/components/issues/issue-layouts/filters/applied-filters/roots/project-view-root.tsx (2 hunks)
  • web/core/components/issues/issue-layouts/properties/label-dropdown.tsx (3 hunks)
  • web/core/components/issues/issue-layouts/properties/labels.tsx (5 hunks)
  • web/core/components/issues/issue-layouts/utils.tsx (1 hunks)
  • web/core/components/issues/select/label.tsx (4 hunks)
  • web/core/components/labels/create-update-label-inline.tsx (1 hunks)
  • web/core/components/labels/delete-label-modal.tsx (1 hunks)
  • web/core/components/labels/index.ts (1 hunks)
  • web/core/components/labels/label-drag-n-drop-HOC.tsx (1 hunks)
  • web/core/components/labels/setting-label-group.tsx (3 hunks)
  • web/core/components/labels/setting-label-item.tsx (2 hunks)
  • web/core/components/labels/setting-label-list.tsx (6 hunks)
  • web/core/components/labels/stores/index.ts (1 hunks)
  • web/core/components/views/form.tsx (3 hunks)
  • web/core/services/issue/issue_label.service.ts (1 hunks)
  • web/core/store/issue/issue-details/sub_issues.store.ts (1 hunks)
  • web/core/store/label.store.ts (8 hunks)
💤 Files with no reviewable changes (1)
  • apiserver/plane/db/migrations/0093_page_moved_to_page_page_moved_to_project_and_more.py
✅ Files skipped from review due to trivial changes (6)
  • web/core/components/issues/issue-layouts/filters/applied-filters/roots/module-root.tsx
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/settings/(with-sidebar)/labels/page.tsx
  • web/core/components/issues/issue-layouts/filters/applied-filters/roots/cycle-root.tsx
  • web/core/store/issue/issue-details/sub_issues.store.ts
  • web/core/components/labels/setting-label-group.tsx
  • web/core/components/labels/index.ts
🚧 Files skipped from review as they are similar to previous changes (29)
  • web/core/components/issues/filters.tsx
  • web/core/components/issues/issue-layouts/filters/applied-filters/roots/archived-issue.tsx
  • web/core/components/issues/issue-layouts/filters/applied-filters/roots/draft-issue.tsx
  • web/core/components/labels/create-update-label-inline.tsx
  • web/core/components/issues/issue-layouts/utils.tsx
  • web/core/components/labels/setting-label-item.tsx
  • web/core/components/views/form.tsx
  • web/app/[workspaceSlug]/(projects)/labels/header.tsx
  • web/core/components/issues/issue-layouts/properties/labels.tsx
  • web/core/components/issues/issue-layouts/filters/applied-filters/roots/project-view-root.tsx
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(detail)/mobile-header.tsx
  • web/core/components/inbox/sidebar/inbox-list-item.tsx
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(detail)/mobile-header.tsx
  • apiserver/plane/app/views/init.py
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(list)/mobile-header.tsx
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(detail)/header.tsx
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(detail)/[viewId]/header.tsx
  • web/app/[workspaceSlug]/(projects)/labels/layout.tsx
  • web/core/components/labels/stores/index.ts
  • web/core/components/issues/issue-layouts/filters/applied-filters/roots/project-root.tsx
  • web/core/components/issues/issue-detail/label/select/label-select.tsx
  • web/core/components/issues/select/label.tsx
  • web/core/components/issues/archived-issues-header.tsx
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(detail)/header.tsx
  • web/core/components/inbox/inbox-filter/filters/filter-selection.tsx
  • packages/constants/src/workspace.ts
  • apiserver/plane/app/urls/workspace.py
  • web/ce/components/workspace/sidebar/helper.tsx
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/draft-issues/header.tsx
🧰 Additional context used
🧬 Code Graph Analysis (6)
web/app/[workspaceSlug]/(projects)/workspace-views/header.tsx (1)
web/core/store/label.store.ts (1)
  • allWorkspaceLabels (107-119)
web/core/components/labels/delete-label-modal.tsx (1)
web/core/store/router.store.ts (2)
  • workspaceSlug (69-71)
  • projectId (85-87)
web/app/[workspaceSlug]/(projects)/labels/page.tsx (4)
web/core/hooks/store/user/user-permissions.ts (1)
  • useUserPermissions (7-12)
admin/core/hooks/store/use-workspace.tsx (1)
  • useWorkspace (6-10)
web/core/store/workspace/index.ts (1)
  • currentWorkspace (98-103)
web/core/components/labels/setting-label-list.tsx (1)
  • SettingsLabelList (25-164)
web/core/components/issues/issue-layouts/properties/label-dropdown.tsx (6)
packages/hooks/src/use-platform-os.tsx (1)
  • usePlatformOS (3-34)
web/core/services/issue/issue_label.service.ts (1)
  • getLabels (28-34)
web/core/hooks/store/user/user-permissions.ts (1)
  • useUserPermissions (7-12)
web/core/store/router.store.ts (2)
  • projectId (85-87)
  • workspaceSlug (69-71)
web/core/store/label.store.ts (1)
  • labels (139-141)
packages/types/src/issues.d.ts (1)
  • IIssueLabel (115-123)
web/core/store/label.store.ts (3)
packages/types/src/issues.d.ts (2)
  • IIssueLabel (115-123)
  • IIssueLabelTree (125-127)
web/core/store/router.store.ts (2)
  • workspaceSlug (69-71)
  • projectId (85-87)
web/core/services/issue/issue_label.service.ts (1)
  • IssueLabelService (7-62)
apiserver/plane/space/serializer/issue.py (2)
apiserver/plane/app/serializers/issue.py (16)
  • Meta (46-59)
  • Meta (65-68)
  • Meta (94-104)
  • Meta (286-288)
  • Meta (292-295)
  • Meta (299-310)
  • Meta (314-316)
  • Meta (320-323)
  • Meta (344-367)
  • Meta (386-409)
  • Meta (415-417)
  • Meta (421-431)
  • Meta (437-447)
  • Meta (451-461)
  • Meta (467-477)
  • Meta (483-494)
apiserver/plane/db/models/label.py (1)
  • Label (7-55)
🪛 Ruff (0.8.2)
apiserver/plane/app/views/workspace/label.py

13-13: Line too long (94 > 88)

(E501)

apiserver/plane/db/migrations/0093_manage_label_project.py

18-18: Line too long (95 > 88)

(E501)


39-39: Line too long (90 > 88)

(E501)

apiserver/plane/space/serializer/issue.py

44-44: Line too long (93 > 88)

(E501)

🪛 Biome (1.9.4)
web/core/store/label.store.ts

[error] 96-96: This return should return a value because it is located in a getter.

(lint/suspicious/useGetterReturn)


[error] 110-110: This return should return a value because it is located in a getter.

(lint/suspicious/useGetterReturn)


[error] 125-125: This return should return a value because it is located in a getter.

(lint/suspicious/useGetterReturn)

web/core/components/labels/setting-label-list.tsx

[error] 118-118: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

🔇 Additional comments (11)
web/app/[workspaceSlug]/(projects)/workspace-views/header.tsx (1)

33-33: Properly updated label data source for workspace-level label support.

The change from workspaceLabels to allWorkspaceLabels aligns with the broader refactoring to support workspace-scoped labels. The allWorkspaceLabels getter properly fetches and sorts all labels for the current workspace, regardless of project association.

Also applies to: 127-127

packages/types/src/issues.d.ts (1)

119-119: Appropriate type change to support workspace-level labels.

Changing the project_id property type from string to string | null enables labels to exist without being associated with a specific project. This is a necessary modification for enabling workspace-scoped labels that can be applied across multiple projects.

web/core/components/labels/delete-label-modal.tsx (1)

34-34: Updated deletion handler to support both workspace and project labels.

The changes appropriately refactor the label deletion process to work with either workspace-scoped or project-scoped labels:

  1. Removed projectId from the guard condition since it's now optional
  2. Made projectId optional in the deleteLabel call using optional chaining

Also applies to: 38-38

web/core/components/labels/label-drag-n-drop-HOC.tsx (1)

64-64: Great permission logic enhancement for label scope support

The permission check now properly determines editability based on label context - checking PROJECT level permissions if the label has a project_id, or WORKSPACE level otherwise. This aligns perfectly with the workspace-level labels feature being implemented.

apiserver/plane/space/serializer/issue.py (2)

44-44: Important serializer update to support nullable project associations

Adding allow_null=True to the project_detail field is necessary to support workspace-level labels that don't belong to any specific project.

🧰 Tools
🪛 Ruff (0.8.2)

44-44: Line too long (93 > 88)

(E501)


49-49:

❓ Verification inconclusive

Verify field validation behavior after removing project from read_only_fields

Removing "project" from read_only_fields could allow project reassignment for labels. Ensure this aligns with the intended behavior for workspace-level labels and doesn't create unintended side effects.


🏁 Script executed:

#!/bin/bash
# Check if the Label model's save method has any validation logic for project changes
cat apiserver/plane/db/models/label.py | grep -A20 "def save"

# Check if there are any API endpoints that allow updating a label's project 
rg -A 3 -B 3 "label.*update" --glob "apiserver/plane/app/views/**/*.py"

Length of output: 645


Let’s inspect the LabelSerializer in context to see which fields are exposed and confirm if project is indeed writable:


🏁 Script executed:

#!/bin/bash
# Locate the serializer class for labels
grep -n "class .*LabelSerializer" -n apiserver/plane/space/serializer/issue.py || \
grep -n "class Label" -n apiserver/plane/space/serializer/issue.py

# Show its Meta class to see which fields are declared
grep -n "class Meta" -n apiserver/plane/space/serializer/issue.py -A5

# Display surrounding lines around read_only_fields
grep -n "read_only_fields" -n apiserver/plane/space/serializer/issue.py -A5 -B5

Length of output: 8399


Confirm project writability on LabelSerializer after change

I verified that in apiserver/plane/space/serializer/issue.py (Meta at lines 47–49), read_only_fields is now only ["workspace"], so project becomes a writable field on LabelSerializer. The Label.save() method has no checks to prevent changing project, and I didn’t find any API‐level guards around project updates for labels.

Please confirm that allowing labels to be reassigned between projects within the same workspace is intended. If not, either re-add "project" to read_only_fields or introduce validation (in the serializer or model) to enforce immutability.

  • Serializer: apiserver/plane/space/serializer/issue.py, Meta.read_only_fields
web/app/[workspaceSlug]/(projects)/labels/page.tsx (1)

1-49: Well-structured workspace label management page

This new page implements workspace-level label management with proper permission checks and UI components. The auto-scroll behavior for the drag-and-drop functionality is a nice usability enhancement.

A few observations:

  • Good use of MobX observer for reactive state management
  • Proper permission handling with useUserPermissions
  • The page correctly renders the workspace name in the title
  • Reuses the generalized SettingsLabelList component that works for both project and workspace contexts
apiserver/plane/db/migrations/0093_manage_label_project.py (1)

7-30: Well-structured migration for workspace labels with proper transaction handling

The migration correctly handles the consolidation of labels from project-specific to workspace-wide, with appropriate transaction management for batch processing.

The code properly:

  1. Loops through all labels
  2. For labels with no workspace equivalent, converts them to workspace labels
  3. For labels with existing workspace equivalents, transfers all associated issues to the workspace label
  4. Uses atomic transactions in batches of 100 to avoid long-running transactions
🧰 Tools
🪛 Ruff (0.8.2)

18-18: Line too long (95 > 88)

(E501)

web/core/components/issues/issue-layouts/properties/label-dropdown.tsx (1)

90-108: Missing dependencies for options memo

options relies on projectId, yet the dependency array only lists labels.
When the component is mounted once and the user switches projects through routing, the dropdown will still display the options of the previous project.

-  ), [labels]
+  ), [labels, projectId]
web/core/components/labels/setting-label-list.tsx (1)

44-46: Avoid calling services with an undefined slug

Both createLabel and updateLabel are invoked even if workspaceSlug is undefined (e.g. while the router is hydrating).
Depending on the service implementation this may lead to ...//api/workspaces/undefined/... network calls.

Consider guarding the callbacks:

createLabel: (data) =>
-  createLabel(workspaceSlug?.toString(), projectId?.toString(), data),
+  workspaceSlug ? createLabel(workspaceSlug, projectId?.toString(), data) : Promise.reject(),

updateLabel: (id, data) =>
-  updateLabel(workspaceSlug?.toString(), projectId?.toString(), id, data),
+  workspaceSlug ? updateLabel(workspaceSlug, projectId?.toString(), id, data) : Promise.reject(),
web/core/services/issue/issue_label.service.ts (1)

28-34: Potentially redundant endpoint – clarify getLabels() vs getWorkspaceIssueLabels()

getLabels() calls /issue-labels/ while the older getWorkspaceIssueLabels() (above) calls /labels/.
Keeping two public methods that return (presumably) the same payload invites confusion and increases maintenance cost.

  1. Verify with the backend team whether both routes are still required.
  2. If /labels/ is deprecated, delete getWorkspaceIssueLabels() and migrate callers.
  3. Otherwise, rename one of the methods to make the distinction explicit (e.g., getWorkspaceLegacyLabels).

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (3)
apiserver/plane/app/views/workspace/label.py (1)

68-76: Name-uniqueness check still ignores project labels
(duplicate of earlier feedback)

The filter should exclude project-scoped labels, otherwise a label rename can be blocked by an unrelated project label.

            Label.objects.filter(
                 workspace__slug=kwargs["slug"],
+                project__isnull=True,
                 name=request.data["name"]
             )
web/core/store/label.store.ts (2)

95-101: Explicitly return undefined in getters

Getters such as workspaceLabels exit early with bare return;, triggering useGetterReturn again.

-if (!workspaceSlug || !currentWorkspaceDetails) {
-  return;
-}
+if (!workspaceSlug || !currentWorkspaceDetails) {
+  return undefined;
+}

Apply the same fix to allWorkspaceLabels and projectLabels.

🧰 Tools
🪛 Biome (1.9.4)

[error] 96-96: This return should return a value because it is located in a getter.

(lint/suspicious/useGetterReturn)


170-172: Filtered array is discarded – labels filtering bug reintroduced

sort.filter(...) is evaluated for side effects only; the result isn’t stored.

-if (projectId) {
-  sort.filter(label => !label.project_id || label.project_id == projectId);
-}
+if (projectId) {
+  sort = sort.filter(
+    (label) => !label.project_id || label.project_id === projectId
+  );
+}

Without this fix, project pages will show workspace-level labels incorrectly.

🧹 Nitpick comments (4)
apiserver/plane/space/serializer/issue.py (1)

44-44: Long line & redundant allow_null on a read-only field

project_detail is declared as read-only, so allow_null=True has no practical effect, and the 93-character line violates Ruff E501.

-project_detail = ProjectLiteSerializer(source="project", read_only=True, allow_null=True)
+project_detail = ProjectLiteSerializer(
+    source="project",
+    read_only=True,  # allow_null is redundant for read-only fields
+)

Consider dropping allow_null (or the read_only flag if the intent is to accept null input) and wrapping the assignment to satisfy the 88-char limit.

🧰 Tools
🪛 Ruff (0.8.2)

44-44: Line too long (93 > 88)

(E501)

web/core/components/labels/setting-label-list.tsx (1)

118-118: Micro-nit: prefer optional chaining to silence Biome warning

label.children && label.children.length can be shortened:

-if (label.children && label.children.length) {
+if (label.children?.length) {

Eliminates the useOptionalChain lint error.

🧰 Tools
🪛 Biome (1.9.4)

[error] 118-118: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

apiserver/plane/app/views/workspace/label.py (2)

60-62: Error message mentions “project” instead of “workspace”

The create-path error text is outdated and may confuse API consumers.

-{"error": "Label with the same name already exists in the project"}
+{"error": "Label with the same name already exists in the workspace"}

13-13: Line length exceeds 88 characters

Split the import over two lines to satisfy Ruff E501.

🧰 Tools
🪛 Ruff (0.8.2)

13-13: Line too long (94 > 88)

(E501)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4e3eb14 and dbc8a46.

📒 Files selected for processing (46)
  • apiserver/plane/app/urls/workspace.py (2 hunks)
  • apiserver/plane/app/views/__init__.py (1 hunks)
  • apiserver/plane/app/views/workspace/label.py (2 hunks)
  • apiserver/plane/space/serializer/issue.py (1 hunks)
  • packages/constants/src/workspace.ts (2 hunks)
  • packages/types/src/issues.d.ts (1 hunks)
  • web/app/[workspaceSlug]/(projects)/labels/header.tsx (1 hunks)
  • web/app/[workspaceSlug]/(projects)/labels/layout.tsx (1 hunks)
  • web/app/[workspaceSlug]/(projects)/labels/page.tsx (1 hunks)
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(detail)/header.tsx (2 hunks)
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(detail)/mobile-header.tsx (2 hunks)
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/draft-issues/header.tsx (2 hunks)
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(list)/mobile-header.tsx (2 hunks)
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(detail)/header.tsx (2 hunks)
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(detail)/mobile-header.tsx (2 hunks)
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/settings/(with-sidebar)/labels/page.tsx (2 hunks)
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(detail)/[viewId]/header.tsx (2 hunks)
  • web/app/[workspaceSlug]/(projects)/workspace-views/header.tsx (2 hunks)
  • web/ce/components/workspace/sidebar/helper.tsx (2 hunks)
  • web/core/components/inbox/inbox-filter/filters/filter-selection.tsx (2 hunks)
  • web/core/components/inbox/sidebar/inbox-list-item.tsx (2 hunks)
  • web/core/components/issues/archived-issues-header.tsx (2 hunks)
  • web/core/components/issues/filters.tsx (2 hunks)
  • web/core/components/issues/issue-detail/label/select/label-select.tsx (3 hunks)
  • web/core/components/issues/issue-layouts/filters/applied-filters/roots/archived-issue.tsx (2 hunks)
  • web/core/components/issues/issue-layouts/filters/applied-filters/roots/cycle-root.tsx (2 hunks)
  • web/core/components/issues/issue-layouts/filters/applied-filters/roots/draft-issue.tsx (2 hunks)
  • web/core/components/issues/issue-layouts/filters/applied-filters/roots/module-root.tsx (2 hunks)
  • web/core/components/issues/issue-layouts/filters/applied-filters/roots/project-root.tsx (2 hunks)
  • web/core/components/issues/issue-layouts/filters/applied-filters/roots/project-view-root.tsx (2 hunks)
  • web/core/components/issues/issue-layouts/properties/label-dropdown.tsx (3 hunks)
  • web/core/components/issues/issue-layouts/properties/labels.tsx (5 hunks)
  • web/core/components/issues/issue-layouts/utils.tsx (1 hunks)
  • web/core/components/issues/select/label.tsx (4 hunks)
  • web/core/components/labels/create-update-label-inline.tsx (1 hunks)
  • web/core/components/labels/delete-label-modal.tsx (1 hunks)
  • web/core/components/labels/index.ts (1 hunks)
  • web/core/components/labels/label-drag-n-drop-HOC.tsx (1 hunks)
  • web/core/components/labels/setting-label-group.tsx (3 hunks)
  • web/core/components/labels/setting-label-item.tsx (2 hunks)
  • web/core/components/labels/setting-label-list.tsx (6 hunks)
  • web/core/components/labels/stores/index.ts (1 hunks)
  • web/core/components/views/form.tsx (3 hunks)
  • web/core/services/issue/issue_label.service.ts (1 hunks)
  • web/core/store/issue/issue-details/sub_issues.store.ts (1 hunks)
  • web/core/store/label.store.ts (8 hunks)
✅ Files skipped from review due to trivial changes (5)
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/settings/(with-sidebar)/labels/page.tsx
  • web/core/components/issues/issue-layouts/filters/applied-filters/roots/cycle-root.tsx
  • web/app/[workspaceSlug]/(projects)/workspace-views/header.tsx
  • web/core/components/labels/setting-label-group.tsx
  • apiserver/plane/app/views/init.py
🚧 Files skipped from review as they are similar to previous changes (37)
  • web/core/components/issues/archived-issues-header.tsx
  • web/core/components/issues/issue-layouts/filters/applied-filters/roots/draft-issue.tsx
  • web/core/components/issues/issue-layouts/filters/applied-filters/roots/archived-issue.tsx
  • web/core/components/issues/filters.tsx
  • web/core/components/labels/create-update-label-inline.tsx
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(list)/mobile-header.tsx
  • web/core/components/views/form.tsx
  • web/core/components/issues/issue-layouts/properties/labels.tsx
  • web/core/store/issue/issue-details/sub_issues.store.ts
  • web/core/components/issues/issue-layouts/filters/applied-filters/roots/module-root.tsx
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(detail)/[viewId]/header.tsx
  • web/core/components/issues/issue-layouts/utils.tsx
  • web/ce/components/workspace/sidebar/helper.tsx
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(detail)/mobile-header.tsx
  • web/app/[workspaceSlug]/(projects)/labels/page.tsx
  • packages/types/src/issues.d.ts
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/draft-issues/header.tsx
  • packages/constants/src/workspace.ts
  • web/core/components/labels/label-drag-n-drop-HOC.tsx
  • web/app/[workspaceSlug]/(projects)/labels/layout.tsx
  • web/core/components/labels/stores/index.ts
  • web/core/components/labels/index.ts
  • web/core/components/issues/select/label.tsx
  • web/core/components/issues/issue-layouts/filters/applied-filters/roots/project-view-root.tsx
  • web/core/components/inbox/sidebar/inbox-list-item.tsx
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(detail)/header.tsx
  • web/core/components/issues/issue-detail/label/select/label-select.tsx
  • web/app/[workspaceSlug]/(projects)/labels/header.tsx
  • web/core/components/inbox/inbox-filter/filters/filter-selection.tsx
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(detail)/mobile-header.tsx
  • web/core/components/issues/issue-layouts/filters/applied-filters/roots/project-root.tsx
  • web/core/components/labels/delete-label-modal.tsx
  • apiserver/plane/app/urls/workspace.py
  • web/core/components/issues/issue-layouts/properties/label-dropdown.tsx
  • web/core/components/labels/setting-label-item.tsx
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(detail)/header.tsx
  • web/core/services/issue/issue_label.service.ts
🧰 Additional context used
🧬 Code Graph Analysis (1)
apiserver/plane/space/serializer/issue.py (2)
apiserver/plane/app/serializers/issue.py (16)
  • Meta (46-59)
  • Meta (65-68)
  • Meta (94-104)
  • Meta (286-288)
  • Meta (292-295)
  • Meta (299-310)
  • Meta (314-316)
  • Meta (320-323)
  • Meta (344-367)
  • Meta (386-409)
  • Meta (415-417)
  • Meta (421-431)
  • Meta (437-447)
  • Meta (451-461)
  • Meta (467-477)
  • Meta (483-494)
apiserver/plane/db/models/label.py (1)
  • Label (7-55)
🪛 Biome (1.9.4)
web/core/store/label.store.ts

[error] 96-96: This return should return a value because it is located in a getter.

(lint/suspicious/useGetterReturn)


[error] 110-110: This return should return a value because it is located in a getter.

(lint/suspicious/useGetterReturn)


[error] 125-125: This return should return a value because it is located in a getter.

(lint/suspicious/useGetterReturn)

web/core/components/labels/setting-label-list.tsx

[error] 118-118: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

🪛 Ruff (0.8.2)
apiserver/plane/app/views/workspace/label.py

13-13: Line too long (94 > 88)

(E501)

apiserver/plane/space/serializer/issue.py

44-44: Line too long (93 > 88)

(E501)

🔇 Additional comments (2)
apiserver/plane/space/serializer/issue.py (1)

49-49: project was removed from read_only_fields – verify API impact

Making project writable means clients can now move a label between projects/workspace via this serializer.
Ensure:

  1. Corresponding permission checks exist server-side.
  2. Business rules (e.g., uniqueness constraints shown in label.py) remain satisfied.

If moving labels is not a supported use-case, re-add "project" to read_only_fields.

web/core/components/labels/setting-label-list.tsx (1)

80-88: Workspace-scoped settings page has no “Add label” entry point

<Button variant="primary"> is only rendered when projectScope === true.
On the new workspace labels page this component is reused, leaving administrators with no way to create workspace-level labels (unless another wrapper provides the action).

Confirm that the surrounding page supplies an alternative “Add label” control; otherwise expose the button for !projectScope && isEditable.

@YurkinPark YurkinPark force-pushed the preview branch 2 times, most recently from 022ac8b to 6e4cbf6 Compare April 28, 2025 10:17
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🔭 Outside diff range comments (1)
apiserver/plane/db/models/label.py (1)

36-46: 💡 Verification agent

🧩 Analysis chain

Consider updating label sort_order for workspace-level labels

The save method calculates the sort order only for project-specific labels. For workspace-level labels (where project is null), this logic won't work correctly as it filters by self.project.


🏁 Script executed:

#!/bin/bash
# Checking how workspace-level labels handle sort_order

# Look for any existing implementation of workspace-level label sort order
rg -A 10 "sort_order.*workspace" --glob "*.py" | grep -v "project"

# Check for any calls that might be creating workspace-level labels
rg "Label\.objects\.create\(" --glob "*.py" -A 5 | grep -B 5 -A 5 "project=None"

Length of output: 1270


🏁 Script executed:

#!/bin/bash
# Display the Label model implementation around the save() method for full context
sed -n '1,200p' apiserver/plane/db/models/label.py

Length of output: 1708


🏁 Script executed:

#!/bin/bash
sed -n '1,200p' apiserver/plane/db/models/workspace.py

Length of output: 5733


🏁 Script executed:

#!/bin/bash
# Locate the Project model to verify its fields
rg -n "class Project" -n apiserver/plane/db/models

Length of output: 830


Handle sort_order for workspace-level labels

The current save() logic in apiserver/plane/db/models/label.py only filters by project, so when project=None it aggregates across all workspace-level labels in every workspace. You should also scope to self.workspace:

• File: apiserver/plane/db/models/label.py
Lines: ~36–46

Suggested change:

     def save(self, *args, **kwargs):
-        if self._state.adding:
-            last_id = Label.objects.filter(project=self.project).aggregate(
-                largest=models.Max("sort_order")
-            )["largest"]
-            if last_id is not None:
-                self.sort_order = last_id + 10000
+        if self._state.adding:
+            qs = Label.objects.filter(workspace=self.workspace)
+            if self.project is not None:
+                qs = qs.filter(project=self.project)
+            else:
+                qs = qs.filter(project__isnull=True)
+            last_id = qs.aggregate(largest=models.Max("sort_order"))["largest"]
+            if last_id is not None:
+                self.sort_order = last_id + 10000

This ensures both project- and workspace-level labels are ordered within their own workspace.

♻️ Duplicate comments (2)
web/core/store/label.store.ts (2)

124-126: Same issue in projectLabels getter

The early return is untyped and fails the same linter rule.

-if (!projectId || !workspaceSlug) {
-  return;
-}
+if (!projectId || !workspaceSlug) {
+  return undefined;
+}
🧰 Tools
🪛 Biome (1.9.4)

[error] 125-125: This return should return a value because it is located in a getter.

(lint/suspicious/useGetterReturn)


107-111: ⚠️ Potential issue

Getter returns nothing — triggers Biome useGetterReturn

allWorkspaceLabels exits early with bare return;, which implicitly returns undefined and violates the rule you already fixed elsewhere.

-if (!currentWorkspaceDetails) {
-  return;
-}
+if (!currentWorkspaceDetails) {
+  return undefined;
+}
🧰 Tools
🪛 Biome (1.9.4)

[error] 110-110: This return should return a value because it is located in a getter.

(lint/suspicious/useGetterReturn)

🧹 Nitpick comments (6)
web/core/components/labels/setting-label-list.tsx (1)

117-118: Consider using optional chaining for label.children

For better safety against null/undefined errors, consider using optional chaining when checking label.children.

-{labelsTree.map((label, index) => {
-  if (label.children && label.children.length) {
+{labelsTree.map((label, index) => {
+  if (label.children?.length) {
🧰 Tools
🪛 Biome (1.9.4)

[error] 118-118: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

apiserver/plane/app/views/workspace/label.py (3)

61-63: Error message references “project” instead of “workspace”

The validation intentionally scopes name-uniqueness to the workspace, but the response still says “…exists in the project”, which can mislead API consumers.

-{"error": "Label with the same name already exists in the project"},
+{"error": "Label with the same name already exists in the workspace"},

68-74: Update comment to reflect workspace scope

The inline comment still says “within the project”, while the code enforces workspace-level uniqueness. Keeping comments accurate prevents future confusion.

-# Check if the label name is unique within the project
+# Check if the label name is unique within the workspace

14-14: Line exceeds 88-char Ruff limit

Ruff flagged E501 here. Wrapping the import list keeps linters green without affecting behaviour.

-from plane.app.permissions.workspace import WorkSpaceBasePermission, WorkspaceViewerPermission
+from plane.app.permissions.workspace import (
+    WorkSpaceBasePermission,
+    WorkspaceViewerPermission,
+)
🧰 Tools
🪛 Ruff (0.8.2)

14-14: Line too long (94 > 88)

(E501)

web/core/store/label.store.ts (2)

14-14: Unused import TYPOGRAPHY_ITEMS

TYPOGRAPHY_ITEMS is imported but never referenced, generating dead code and failing strict build setups.

-import { TYPOGRAPHY_ITEMS } from "@plane/editor";
+// import removed – no usages found

161-170: Potential race: async fetch kicked off but value immediately used

Inside _getLabels, this.fetchLabels(workspaceSlug) is invoked without await, so the subsequent filtering runs on a possibly empty labelMap, yielding incomplete results on first access.
Consider awaiting the promise or returning early until data is loaded, similar to the pattern you adopted with fetchedMap.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dbc8a46 and 6e4cbf6.

📒 Files selected for processing (48)
  • apiserver/plane/app/urls/workspace.py (2 hunks)
  • apiserver/plane/app/views/__init__.py (1 hunks)
  • apiserver/plane/app/views/workspace/label.py (2 hunks)
  • apiserver/plane/db/migrations/0094_remove_label_unique_name_when_project_null_and_not_deleted_and_more.py (1 hunks)
  • apiserver/plane/db/models/label.py (1 hunks)
  • apiserver/plane/space/serializer/issue.py (1 hunks)
  • packages/constants/src/workspace.ts (2 hunks)
  • packages/types/src/issues.d.ts (1 hunks)
  • web/app/[workspaceSlug]/(projects)/labels/header.tsx (1 hunks)
  • web/app/[workspaceSlug]/(projects)/labels/layout.tsx (1 hunks)
  • web/app/[workspaceSlug]/(projects)/labels/page.tsx (1 hunks)
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(detail)/header.tsx (2 hunks)
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(detail)/mobile-header.tsx (2 hunks)
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/draft-issues/header.tsx (2 hunks)
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(list)/mobile-header.tsx (2 hunks)
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(detail)/header.tsx (2 hunks)
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(detail)/mobile-header.tsx (2 hunks)
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/settings/(with-sidebar)/labels/page.tsx (2 hunks)
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(detail)/[viewId]/header.tsx (2 hunks)
  • web/app/[workspaceSlug]/(projects)/workspace-views/header.tsx (2 hunks)
  • web/ce/components/workspace/sidebar/helper.tsx (2 hunks)
  • web/core/components/inbox/inbox-filter/filters/filter-selection.tsx (2 hunks)
  • web/core/components/inbox/sidebar/inbox-list-item.tsx (2 hunks)
  • web/core/components/issues/archived-issues-header.tsx (2 hunks)
  • web/core/components/issues/filters.tsx (2 hunks)
  • web/core/components/issues/issue-detail/label/select/label-select.tsx (3 hunks)
  • web/core/components/issues/issue-layouts/filters/applied-filters/roots/archived-issue.tsx (2 hunks)
  • web/core/components/issues/issue-layouts/filters/applied-filters/roots/cycle-root.tsx (2 hunks)
  • web/core/components/issues/issue-layouts/filters/applied-filters/roots/draft-issue.tsx (2 hunks)
  • web/core/components/issues/issue-layouts/filters/applied-filters/roots/module-root.tsx (2 hunks)
  • web/core/components/issues/issue-layouts/filters/applied-filters/roots/project-root.tsx (2 hunks)
  • web/core/components/issues/issue-layouts/filters/applied-filters/roots/project-view-root.tsx (2 hunks)
  • web/core/components/issues/issue-layouts/properties/label-dropdown.tsx (3 hunks)
  • web/core/components/issues/issue-layouts/properties/labels.tsx (5 hunks)
  • web/core/components/issues/issue-layouts/utils.tsx (1 hunks)
  • web/core/components/issues/select/label.tsx (4 hunks)
  • web/core/components/labels/create-update-label-inline.tsx (1 hunks)
  • web/core/components/labels/delete-label-modal.tsx (1 hunks)
  • web/core/components/labels/index.ts (1 hunks)
  • web/core/components/labels/label-drag-n-drop-HOC.tsx (1 hunks)
  • web/core/components/labels/setting-label-group.tsx (3 hunks)
  • web/core/components/labels/setting-label-item.tsx (2 hunks)
  • web/core/components/labels/setting-label-list.tsx (6 hunks)
  • web/core/components/labels/stores/index.ts (1 hunks)
  • web/core/components/views/form.tsx (3 hunks)
  • web/core/services/issue/issue_label.service.ts (1 hunks)
  • web/core/store/issue/issue-details/sub_issues.store.ts (1 hunks)
  • web/core/store/label.store.ts (8 hunks)
✅ Files skipped from review due to trivial changes (5)
  • web/core/components/issues/issue-layouts/filters/applied-filters/roots/archived-issue.tsx
  • web/core/store/issue/issue-details/sub_issues.store.ts
  • web/core/components/labels/setting-label-group.tsx
  • web/core/components/issues/issue-layouts/filters/applied-filters/roots/module-root.tsx
  • web/core/components/labels/index.ts
🚧 Files skipped from review as they are similar to previous changes (37)
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/settings/(with-sidebar)/labels/page.tsx
  • web/core/components/issues/archived-issues-header.tsx
  • packages/types/src/issues.d.ts
  • apiserver/plane/app/views/init.py
  • web/core/components/labels/delete-label-modal.tsx
  • web/core/components/views/form.tsx
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(detail)/mobile-header.tsx
  • web/core/components/issues/issue-layouts/filters/applied-filters/roots/cycle-root.tsx
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(detail)/mobile-header.tsx
  • web/app/[workspaceSlug]/(projects)/workspace-views/header.tsx
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(detail)/[viewId]/header.tsx
  • web/core/components/issues/issue-layouts/filters/applied-filters/roots/draft-issue.tsx
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(detail)/header.tsx
  • web/ce/components/workspace/sidebar/helper.tsx
  • web/core/components/issues/filters.tsx
  • web/app/[workspaceSlug]/(projects)/labels/layout.tsx
  • packages/constants/src/workspace.ts
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/draft-issues/header.tsx
  • web/core/components/labels/create-update-label-inline.tsx
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(list)/mobile-header.tsx
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(detail)/header.tsx
  • web/core/components/labels/label-drag-n-drop-HOC.tsx
  • web/core/components/issues/issue-layouts/filters/applied-filters/roots/project-root.tsx
  • web/app/[workspaceSlug]/(projects)/labels/page.tsx
  • web/core/components/labels/setting-label-item.tsx
  • web/core/components/issues/issue-layouts/utils.tsx
  • web/app/[workspaceSlug]/(projects)/labels/header.tsx
  • web/core/components/labels/stores/index.ts
  • web/core/components/issues/issue-layouts/properties/label-dropdown.tsx
  • web/core/components/inbox/sidebar/inbox-list-item.tsx
  • web/core/components/issues/select/label.tsx
  • web/core/components/issues/issue-layouts/filters/applied-filters/roots/project-view-root.tsx
  • web/core/components/issues/issue-layouts/properties/labels.tsx
  • apiserver/plane/app/urls/workspace.py
  • web/core/services/issue/issue_label.service.ts
  • web/core/components/inbox/inbox-filter/filters/filter-selection.tsx
  • web/core/components/issues/issue-detail/label/select/label-select.tsx
🧰 Additional context used
🧬 Code Graph Analysis (1)
apiserver/plane/space/serializer/issue.py (1)
apiserver/plane/db/models/label.py (1)
  • Label (7-49)
🪛 Ruff (0.8.2)
apiserver/plane/db/migrations/0094_remove_label_unique_name_when_project_null_and_not_deleted_and_more.py

23-23: Line too long (170 > 88)

(E501)

apiserver/plane/app/views/workspace/label.py

14-14: Line too long (94 > 88)

(E501)

apiserver/plane/space/serializer/issue.py

44-44: Line too long (93 > 88)

(E501)

🪛 Biome (1.9.4)
web/core/components/labels/setting-label-list.tsx

[error] 118-118: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

web/core/store/label.store.ts

[error] 110-110: This return should return a value because it is located in a getter.

(lint/suspicious/useGetterReturn)


[error] 125-125: This return should return a value because it is located in a getter.

(lint/suspicious/useGetterReturn)

🔇 Additional comments (14)
apiserver/plane/db/models/label.py (1)

24-30: Constraint updated to support workspace-level labels

The unique constraint on project and name has been modified to only check that deleted_at is null, without requiring project to be non-null. This is a good change that allows labels to exist at both workspace and project levels while maintaining uniqueness within each scope.

🧰 Tools
🪛 Ruff (0.8.2)

24-24: Line too long (100 > 88)

(E501)

apiserver/plane/space/serializer/issue.py (2)

43-44: Allow null project association for labels

Good change to support workspace-level labels by allowing the project_detail field to accept null values. This is a crucial part of enabling labels to be associated with a workspace without requiring a project.

🧰 Tools
🪛 Ruff (0.8.2)

44-44: Line too long (93 > 88)

(E501)


48-49: Removed project from read-only fields

Removing "project" from the read-only fields list allows labels to be created and updated without requiring a project association, which is essential for workspace-level labels.

apiserver/plane/db/migrations/0094_remove_label_unique_name_when_project_null_and_not_deleted_and_more.py (2)

13-16: Removing constraint for unique names when project is null

This correctly removes the previous constraint that enforced uniqueness of label names when a project was not associated, which is no longer needed with the unified approach to label uniqueness.


17-24: Updating unique constraint for labels

The migration properly removes the old constraint and adds a new one that enforces uniqueness of the (project, name) pair for all non-deleted labels, regardless of whether a project is associated. This aligns with the model changes and supports both workspace and project-level labels.

🧰 Tools
🪛 Ruff (0.8.2)

23-23: Line too long (170 > 88)

(E501)

web/core/components/labels/setting-label-list.tsx (8)

5-6: Using centralized label form state

Good addition of the centralized labelFormState store for managing label form visibility and update state, which helps maintain consistent state across components.


17-18: Renamed component for broader scope

Appropriate renaming of components from project-specific to more generic names (ProjectSettingsLabelListSettingsLabelList, ProjectSettingLabelGroupSettingLabelGroup, etc.) to reflect their ability to handle both workspace and project scopes.

Also applies to: 25-25


35-38: Conditional selection of labels based on scope

Good implementation of conditional selection between workspace and project labels based on whether a projectId is defined. This cleanly handles both scopes with minimal code duplication.


40-41: Adaptive permission checking based on scope

The permission check now correctly determines the permission level (PROJECT or WORKSPACE) based on whether a project is in scope, ensuring that appropriate permissions are enforced for both workspace and project-level labels.


45-45: Updated operations to handle optional projectId

The label operations (updateLabel, updateLabelPosition) have been updated to work with an optional projectId, allowing them to operate on both workspace and project-level labels.

Also applies to: 59-70


79-88: Conditional rendering for project-specific UI

Good implementation of conditional rendering for the "Add Label" button and header, showing them only for project-scoped labels. This helps maintain a clean UI that's appropriate for each context.


90-104: Using centralized form state for better consistency

The component now uses the centralized labelFormState for managing form visibility and update state, replacing local state management. This is a good practice for maintaining consistent state across related components.


105-160: Improved rendering logic for different label scopes

The rendering logic has been updated to handle both workspace and project-scoped labels, conditionally selecting the appropriate data source based on the current scope. The component also appropriately handles loading states and empty states.

🧰 Tools
🪛 Biome (1.9.4)

[error] 118-118: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

apiserver/plane/app/views/workspace/label.py (1)

47-49: Cache-invalidation path appears outdated

The new CRUD endpoints are exposed under /issue-labels/, yet the invalidate_cache decorator still targets /api/workspaces/:slug/labels/.
If consumers call only the new route, stale data could be served because its cache key is never evicted.

-@invalidate_cache(
-    path="/api/workspaces/:slug/labels/", url_params=True, user=False, multiple=True
-)
+# Consider also (or instead) invalidating the new route:
+@invalidate_cache(
+    path="/api/workspaces/:slug/issue-labels/",
+    url_params=True,
+    user=False,
+    multiple=True,
+)

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (5)
apiserver/plane/db/migrations/0097_remove_label_unique_name_when_project_null_and_not_deleted_and_more.py (1)

23-23: Fix line length formatting issue.

The line exceeds the 88-character limit. Consider breaking it into multiple lines for better readability.

-        migrations.AddConstraint(
-            model_name='label',
-            constraint=models.UniqueConstraint(condition=models.Q(('deleted_at__isnull', True)), fields=('project', 'name'), name='unique_project_name_when_not_deleted'),
-        ),
+        migrations.AddConstraint(
+            model_name='label',
+            constraint=models.UniqueConstraint(
+                condition=models.Q(('deleted_at__isnull', True)),
+                fields=('project', 'name'),
+                name='unique_project_name_when_not_deleted'
+            ),
+        ),
🧰 Tools
🪛 Ruff (0.11.9)

23-23: Line too long (170 > 88)

(E501)

apiserver/plane/space/serializer/issue.py (1)

44-44: Fix line length formatting issue.

The line exceeds the 88-character limit. Consider breaking it for better readability.

-    project_detail = ProjectLiteSerializer(source="project", read_only=True, allow_null=True)
+    project_detail = ProjectLiteSerializer(
+        source="project", read_only=True, allow_null=True
+    )
🧰 Tools
🪛 Ruff (0.11.9)

44-44: Line too long (93 > 88)

(E501)

apiserver/plane/app/views/workspace/label.py (3)

14-14: Fix line length formatting issue.

The import line exceeds the 88-character limit.

-from plane.app.permissions.workspace import WorkSpaceBasePermission, WorkspaceViewerPermission
+from plane.app.permissions.workspace import (
+    WorkSpaceBasePermission,
+    WorkspaceViewerPermission,
+)
🧰 Tools
🪛 Ruff (0.11.9)

14-14: Line too long (94 > 88)

(E501)


61-61: Update error message to reference workspace instead of project.

The error message incorrectly mentions "project" when dealing with workspace labels.

-                {"error": "Label with the same name already exists in the project"},
+                {"error": "Label with the same name already exists in the workspace"},

68-68: Update comment to reference workspace instead of project.

The comment incorrectly mentions "project" when checking uniqueness within a workspace.

-        # Check if the label name is unique within the project
+        # Check if the label name is unique within the workspace
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6e4cbf6 and 9912afc.

📒 Files selected for processing (7)
  • apiserver/plane/app/urls/workspace.py (2 hunks)
  • apiserver/plane/app/views/__init__.py (1 hunks)
  • apiserver/plane/app/views/workspace/label.py (2 hunks)
  • apiserver/plane/db/migrations/0097_remove_label_unique_name_when_project_null_and_not_deleted_and_more.py (1 hunks)
  • apiserver/plane/db/models/label.py (1 hunks)
  • apiserver/plane/space/serializer/issue.py (1 hunks)
  • packages/constants/src/workspace.ts (2 hunks)
✅ Files skipped from review due to trivial changes (1)
  • apiserver/plane/app/views/init.py
🚧 Files skipped from review as they are similar to previous changes (3)
  • apiserver/plane/db/models/label.py
  • packages/constants/src/workspace.ts
  • apiserver/plane/app/urls/workspace.py
🧰 Additional context used
🧬 Code Graph Analysis (1)
apiserver/plane/space/serializer/issue.py (1)
apiserver/plane/db/models/label.py (2)
  • Meta (22-34)
  • Label (7-49)
🪛 Ruff (0.11.9)
apiserver/plane/db/migrations/0097_remove_label_unique_name_when_project_null_and_not_deleted_and_more.py

23-23: Line too long (170 > 88)

(E501)

apiserver/plane/app/views/workspace/label.py

14-14: Line too long (94 > 88)

(E501)

apiserver/plane/space/serializer/issue.py

44-44: Line too long (93 > 88)

(E501)

🔇 Additional comments (2)
apiserver/plane/space/serializer/issue.py (1)

44-49: LGTM! Serializer changes support workspace-scoped labels.

The addition of allow_null=True for project_detail and removal of "project" from read_only_fields correctly enables labels to exist at the workspace level without requiring a project association.

🧰 Tools
🪛 Ruff (0.11.9)

44-44: Line too long (93 > 88)

(E501)

apiserver/plane/app/views/workspace/label.py (1)

31-89: LGTM! Well-implemented workspace label viewset.

The WorkspaceLabelViewSet provides comprehensive CRUD operations for workspace-scoped labels with:

  • Proper permission controls using role-based decorators
  • Correct filtering to exclude project-scoped labels
  • Appropriate error handling for integrity constraints
  • Cache invalidation for performance optimization
  • Proper uniqueness validation in the update method

The implementation correctly addresses the requirements for workspace-level label management.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant