-
Notifications
You must be signed in to change notification settings - Fork 2.3k
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
base: preview
Are you sure you want to change the base?
Workspace labels #3101 #6917
Conversation
WalkthroughThis 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
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
Suggested labels
Suggested reviewers
Poem
✨ Finishing Touches
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed 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)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 6
🔭 Outside diff range comments (3)
web/core/components/issues/issue-detail/label/root.tsx (1)
80-82
: 🛠️ Refactor suggestionUpdate 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 suggestionPotential 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 suggestionUpdate 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 BulkCreateIssueLabelsEndpointWith 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 codeThere'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 recommendationsAccording 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 importsThe imported
WorkSpaceBasePermission
andWorkspace
are not used in this file following the removal of theLabelViewSet
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 unusedRemove unused import:
plane.app.permissions.WorkSpaceBasePermission
(F401)
15-15:
plane.db.models.Workspace
imported but unusedRemove 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 handlingIntegrityError
, 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
📒 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"
fromread_only_fields
aligns with workspace-level label handling. Please verify that no references toproject
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 theread_only_fields
ofapiserver/plane/space/serializer/issue.py
appears consistent with workspace-level label handling. Our initial search within the context ofLabelSerializer
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
UsingBaseModel
instead ofWorkspaceBaseModel
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 toclass 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 managementThe change from
projectLabels
tolabels
aligns with the PR objective of centralizing labels at the workspace level instead of being project-specific. This correctly uses thelabels
getter from the label store.
85-85
: Consistent variable name updateThe 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 managementThe change from
projectLabels
tolabels
aligns with the PR objective of centralizing labels at the workspace level instead of being project-specific. This correctly uses thelabels
getter from the label store.
70-70
: Consistent variable name updateThe 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 scopeThe component name change from
ProjectSettingLabelItem
toSettingLabelItem
aligns with the broader refactoring to transition from project-specific label handling to workspace-level operations.
45-45
: Removed project dependency from parameter destructuringRemoving
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 contextThe 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 componentThe new
WorkspaceLabelLayout
component is appropriately structured with a clean implementation. It properly integrates theAppHeader
component with theWorkspaceLabelHeader
and wraps content in theContentWrapper
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
: RenamingprojectLabels
tolabels
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 fromprojectLabels
tolabels
.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
: RenamingprojectLabels
tolabels
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 fromprojectLabels
tolabels
.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
: RenamingprojectLabels
tolabels
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 renamedlabels
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 renamedlabels
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
: RenamingprojectLabels
tolabels
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 renamedlabels
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 labelsThe changes from
projectLabels
tolabels
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 labelsThe removal of the
"project_id"
field from thefields
list and"project"
from theread_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 navigationThe 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 labelsThe changes from
projectLabels
tolabels
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 labelsThe change from
projectLabels
tolabels
aligns with the PR objective of transitioning from project-specific labels to workspace-specific labels.
235-235
: Prop name updated to match the renamed variableThis 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 operationsThe removal of
projectId
from theuseParams
hook aligns with the transition to workspace-level label management.
34-34
: Updated condition to remove project-level dependencyThe conditional check has been simplified to only verify
workspaceSlug
anddata
, removing the dependency onprojectId
which is no longer needed for workspace-level operations.
38-38
: Updated method call to match new API signatureThe call to
deleteLabel
has been updated to excludeprojectId
, passing onlyworkspaceSlug
anddata.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 labelsThe destructuring now includes both
workspaceLabels
andlabels
to support the transition to workspace-level label management.
238-239
: Renamed variable to avoid naming collisionThe variable
labels
has been renamed tollabels
to avoid naming collision with the destructuredlabels
. 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 variableThe
.map()
call has been updated to usellabels
instead of the previouslabels
, maintaining consistency with the renamed variable.web/core/components/inbox/sidebar/inbox-list-item.tsx (2)
34-34
: Updated variable name for workspace-level labelsThe destructuring has been updated from
projectLabels
tolabels
to align with the workspace-level label management approach.
99-99
: Updated reference to use the renamed variableThe find method now correctly uses
labels
instead ofprojectLabels
to match the renamed variable.apiserver/plane/app/views/__init__.py (1)
64-64
: LGTM: Import updated to reflect workspace-level label managementThe 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 labelsSuccessfully updated from
projectLabels
tolabels
to align with the workspace-level approach to label management.
86-86
: LGTM: Prop name updated to match the variable name changeThe 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 creationGood 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 theIssueLabel
model properly enforces all required constraints—such as usingunique_together
orUniqueConstraint
—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
orUniqueConstraint
) 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.pyLength 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 inapiserver/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., viaunique_together
orUniqueConstraint
).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 labelsThe variable has been appropriately renamed from
projectLabels
tolabels
to reflect the shift from project-specific to workspace-level label management.
160-160
: Consistent prop naming with workspace labelsThe
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 managementThis 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 importsThe import change correctly adds the LabelViewSet which is needed for the new URL patterns below.
165-181
: New workspace-level label API endpointsThese 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 labelsThe method call has been correctly updated to fetch labels at the workspace level instead of the project level, removing the
projectId
parameter and using thefetchLabels
method instead offetchProjectLabels
.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 labelsThe renaming of exports from project-specific to more general names (
project-setting-*
tosetting-*
) 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 approachThe variable rename from
projectLabels
tolabels
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 labelsCorrectly 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 componentImport 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 scopeComponent has been renamed from
ProjectSettingLabelGroup
toSettingLabelGroup
, properly aligning with the PR objective of moving from project-specific to workspace-level label management.
131-131
: Updated child component referenceThe child component reference has been correctly updated to use
SettingLabelItem
instead ofProjectSettingLabelItem
, maintaining consistency with the renaming pattern.apiserver/plane/app/views/issue/label.py (1)
19-45
:❓ Verification inconclusive
Verify migration of
BulkCreateIssueLabelsEndpoint
implementationThe
BulkCreateIssueLabelsEndpoint
class still referencesproject_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 aproject
usingproject_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 extractingworkspace_id
without relying onproject_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 deriveworkspace_id
or if it should be replaced with a directworkspace_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 labelsThis 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 propsThe
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 getterUpdated to use the workspace-level
getLabels
method instead of project-specificgetProjectLabels
, aligning with the PR objective of centralizing labels at the workspace level.
69-69
: Approve: Removed project ID parameter from label retrievalThe 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 consistencyRenamed
projectLabels
tolabels
throughout the component to reflect the shift from project-specific to workspace-level labels.
128-132
: Approve: Updated tooltip content referenceUpdated the label reference in the tooltip content to use the workspace-level labels.
141-141
: Approve: Updated dependency arrayUpdated the dependency array in the
useMemo
hook to reference the newlabels
variable instead ofprojectLabels
.
182-183
: Approve: Updated labels reference in rendering logicThe 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 labelsUpdated 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 propsThe
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 labelsUpdated 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 propsThe
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 codeThe 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 operationsThe component correctly uses
useUserPermissions
hook with permissions check at the workspace level for editing labels. ThenewLabel
function properly interacts with the label form state.
33-56
: Clean UI implementation following design patternsThe 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 operationsThe 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 structureThe variable has been appropriately renamed from
projectLabels
tolabels
to reflect that labels are now workspace-scoped instead of project-scoped.
73-73
: Updated fetch call to use workspace-level APIThe 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 variableThe 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 renderingThe 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 accessThe destructured property from the
useLabel
hook has been updated fromprojectLabels
tolabels
, aligning with the workspace-level label structure introduced in this PR.
285-285
: Prop name updated consistently in component usageThe
labels
prop passed to theFilterSelection
component has been updated to use the renamedlabels
variable instead ofprojectLabels
, maintaining consistent naming throughout the application.web/app/[workspaceSlug]/(projects)/labels/page.tsx (5)
10-12
: Updated imports to use workspace-level componentsThe import has been updated from
ProjectSettingsLabelList
toSettingsLabelList
anduseProject
has been replaced withuseWorkspace
, reflecting the shift to workspace-level label management.
17-17
: Store hook updated to workspace contextThe component now correctly uses
currentWorkspace
from theuseWorkspace
hook instead ofcurrentProjectDetails
fromuseProject
, aligning with the workspace-level data management approach.
19-19
: Page title updated to reflect workspace contextThe 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 checkThe permission check has been simplified to only verify if
workspaceUserInfo
exists, which is appropriate for workspace-level access control.
43-43
: Updated component renderingThe 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)
togetLabels()
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
toSettingsLabelList
and params simplified to only includeworkspaceSlug
, 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 importingIntegrityError
, as it's properly used to handle duplicate name conflicts.
10-10
: Base classes import
The addition ofBaseAPIView
andBaseViewSet
import is consistent with the rest of the code.
30-34
: Introduction of LabelViewSet
Defining the serializer and referencing theLabel
model with workspace-level permissions is a solid approach to handle label operations.
35-45
: Workspace scoping in get_queryset
Theget_queryset
method correctly filters byworkspace__slug
and optimizes queries withselect_related
, ensuring proper workspace-level label retrieval.
46-48
: Correct cache invalidation
Usinginvalidate_cache
on the workspace label path is a suitable choice for keeping responses consistent.
83-86
: Destroy method override
Overridingdestroy
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 toissue-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.
web/core/components/issues/issue-detail/label/select/label-select.tsx
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 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 bothproject_id
andworkspace_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 pyLength 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 importsThe imports for
WorkSpaceBasePermission
andWorkspace
are not used in this file after the removal of theLabelViewSet
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 unusedRemove unused import:
plane.app.permissions.WorkSpaceBasePermission
(F401)
15-15:
plane.db.models.Workspace
imported but unusedRemove unused import:
plane.db.models.Workspace
(F401)
apiserver/plane/db/migrations/0093_manage_label_project.py (1)
37-37
: Minor style issue: line lengthThe 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 checkThe 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 logicThe 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 setslabels = storeLabels
only ifstoreLabels
is nonempty; otherwise, it sticks withdefaultOptions
. 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
andworkspaceSlug
are present, the code triggers a new fetch operation, then marksisLoading
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 triggersfetchLabels(...)
if labels aren’t fetched yet. Consider preventing multiple in-flight fetches for the same workspace. For example, maintain a loading or promise reference infetchedMap
so subsequent calls return the same pending promise or skip the redundant fetch.
139-145
: Potential duplication betweenfetchLabels
andfetchWorkspaceLabels
.
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 inupdateLabel
.
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 thanconsole.log
. This ensures errors are visible and trackable.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 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 managementThe removal of
projectId
from thecreateLabel
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 tsLength of output: 6230
Transition Strategy Verified: Project Reference Usage is Consistent
After verifying the occurrences ofupdateIssue
throughout the codebase, it’s clear that the use of bothworkspaceSlug
andprojectId
is intentional and consistent. The guard clause increate-label.tsx
that checks forprojectId
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 bothworkspaceSlug
andprojectId
, 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 transitionThis migration effectively handles the transition from project-scoped to workspace-scoped labels by:
- Keeping one unique label per name at workspace level
- Transferring associations from duplicate labels to the kept label
- 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 storeUsing
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 labelsThe component renaming from
ProjectSettingsLabelList
toSettingsLabelList
and updating accessor methods to use workspace-level operations (labels
instead ofprojectLabels
) maintains consistency with the project's migration to workspace-scoped labels.Also applies to: 35-43
38-38
: Broadened permission scope appropriatelyPermissions have been appropriately broadened to include both
ADMIN
andMEMBER
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 callsfetchLabels
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
andlabelsTree
both rely on_getLabels()
. Check for possible race conditions if_getLabels()
is already fetching while another component accesseslabelsTree
. 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 fromlabelMap
and callssyncIssuesWithDeletedLabels
. Confirm that all references to the deleted label are removed across the UI state, especially if you rely onlabelsTree
or other caches.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 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
andworkspace_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
andworkspace_id
(derived via the project), and it’s still referenced in project-scoped URLs (e.g. inapiserver/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
andWorkspace
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 unusedRemove unused import:
plane.app.permissions.WorkSpaceBasePermission
(F401)
15-15:
plane.db.models.Workspace
imported but unusedRemove 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
📒 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 tsLength 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
, theisEditable
constant correctly usesallowPermissions([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 labelLength 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) withon_delete
set to eitherCASCADE
orSET_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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (1)
apiserver/plane/app/views/workspace/label.py (1)
68-76
: Correctly implemented workspace filter in partial updateThe 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
fromplane.app.permissions
Workspace
fromplane.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 unusedRemove unused import:
plane.app.permissions.WorkSpaceBasePermission
(F401)
15-15:
plane.db.models.Workspace
imported but unusedRemove 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 useprojectId
, 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 safetyWhen 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() callsThe
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() conversionThe
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 projectThe 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
📒 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:
- Preserving unique labels by removing project associations
- Consolidating duplicate labels by transferring their issue associations
- Using atomic transactions to ensure data consistency
- 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 theprojectId
parameter, aligning with the workspace-level label management approach.
30-30
: Label fetching functions updated to use workspace scope.Properly replaced project-scoped
fetchProjectLabels
andgetProjectLabels
with workspace-scopedfetchLabels
andgetLabels
.
39-39
: Permission check updated for workspace-level label management.Correctly updated permission check to allow both
ADMIN
andMEMBER
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
andworkspaceSlug
, 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
andgetProjectLabels
with workspace-scopedfetchLabels
andgetLabels
.
84-84
: Permission check updated for workspace-level label management.Correctly updated permission check to allow both
ADMIN
andMEMBER
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 notedThe 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 managementMoving 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 scopeThe refactoring from project-specific to workspace-level label management is consistently implemented with appropriate variable renaming from
projectLabels
tolabels
and removingprojectId
dependencies.Also applies to: 35-35
apiserver/plane/app/views/workspace/label.py (3)
49-50
: Appropriate permission levels for label operationsThe 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 strategyThe 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 filteringThe 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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 5
♻️ Duplicate comments (2)
web/core/store/label.store.ts (2)
108-114
: Same explicit-return issue as abovePlease 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 parameterThe
isProjectView
prop is set totrue
in theNotAuthorizedView
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 theisWorkspaceView
prop.apiserver/plane/db/migrations/0093_manage_label_project.py (1)
15-30
: Consider adding error handling during issue migrationWhile 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
- The inline comment
//hooks33
looks like a leftover artefact from an edit – it’s confusing for new readers.storeLabels
is immediately dereferenced viagetLabels()
yet you keep the raw store value in the same identifier (storeLabels
). Renaming to something likecachedLabels
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 nestingBiome 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 charsPEP 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 thedata
argument
createLabel()
acceptsdata: 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 builderThe string-concatenation pattern is duplicated across
create*
,patch*
, anddelete*
.
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 vsgetLabels()
computedFnYou now expose both a
labels
computed getter and a memoisedgetLabels()
that return the same dataset.
This duality makes it unclear which one callers should prefer and risks divergence later.Consider de-duplicating:
- Keep only the getter and drop
getLabels()
; or- Rename
getLabels()
to something explicit likeselectLabels(workspaceSlug, projectId?)
.
269-276
: Parameter naming inconsistency
updateLabel()
usesproject_id
(snake-case) while the rest of the file usesprojectId
.
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
📒 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
toallWorkspaceLabels
aligns with the broader refactoring to support workspace-scoped labels. TheallWorkspaceLabels
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 fromstring
tostring | 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:
- Removed
projectId
from the guard condition since it's now optional- Made
projectId
optional in thedeleteLabel
call using optional chainingAlso applies to: 38-38
web/core/components/labels/label-drag-n-drop-HOC.tsx (1)
64-64
: Great permission logic enhancement for label scope supportThe 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 associationsAdding
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 ifproject
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 -B5Length 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"]
, soproject
becomes a writable field onLabelSerializer
. TheLabel.save()
method has no checks to prevent changingproject
, 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"
toread_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 pageThis 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 contextsapiserver/plane/db/migrations/0093_manage_label_project.py (1)
7-30
: Well-structured migration for workspace labels with proper transaction handlingThe migration correctly handles the consolidation of labels from project-specific to workspace-wide, with appropriate transaction management for batch processing.
The code properly:
- Loops through all labels
- For labels with no workspace equivalent, converts them to workspace labels
- For labels with existing workspace equivalents, transfers all associated issues to the workspace label
- 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 foroptions
memo
options
relies onprojectId
, yet the dependency array only listslabels
.
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 slugBoth
createLabel
andupdateLabel
are invoked even ifworkspaceSlug
isundefined
(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 – clarifygetLabels()
vsgetWorkspaceIssueLabels()
getLabels()
calls/issue-labels/
while the oldergetWorkspaceIssueLabels()
(above) calls/labels/
.
Keeping two public methods that return (presumably) the same payload invites confusion and increases maintenance cost.
- Verify with the backend team whether both routes are still required.
- If
/labels/
is deprecated, deletegetWorkspaceIssueLabels()
and migrate callers.- Otherwise, rename one of the methods to make the distinction explicit (e.g.,
getWorkspaceLegacyLabels
).
web/core/components/issues/issue-layouts/properties/label-dropdown.tsx
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 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 returnundefined
in gettersGetters such as
workspaceLabels
exit early with barereturn;
, triggeringuseGetterReturn
again.-if (!workspaceSlug || !currentWorkspaceDetails) { - return; -} +if (!workspaceSlug || !currentWorkspaceDetails) { + return undefined; +}Apply the same fix to
allWorkspaceLabels
andprojectLabels
.🧰 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 & redundantallow_null
on a read-only field
project_detail
is declared as read-only, soallow_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 theread_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 charactersSplit 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
📒 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 fromread_only_fields
– verify API impactMaking
project
writable means clients can now move a label between projects/workspace via this serializer.
Ensure:
- Corresponding permission checks exist server-side.
- Business rules (e.g., uniqueness constraints shown in
label.py
) remain satisfied.If moving labels is not a supported use-case, re-add
"project"
toread_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 whenprojectScope === 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
.
022ac8b
to
6e4cbf6
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 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 (whereproject
is null), this logic won't work correctly as it filters byself.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.pyLength of output: 1708
🏁 Script executed:
#!/bin/bash sed -n '1,200p' apiserver/plane/db/models/workspace.pyLength of output: 5733
🏁 Script executed:
#!/bin/bash # Locate the Project model to verify its fields rg -n "class Project" -n apiserver/plane/db/modelsLength of output: 830
Handle sort_order for workspace-level labels
The current
save()
logic inapiserver/plane/db/models/label.py
only filters byproject
, so whenproject=None
it aggregates across all workspace-level labels in every workspace. You should also scope toself.workspace
:• File:
apiserver/plane/db/models/label.py
Lines: ~36–46Suggested 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 + 10000This 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 inprojectLabels
getterThe 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 issueGetter returns nothing — triggers Biome
useGetterReturn
allWorkspaceLabels
exits early with barereturn;
, which implicitly returnsundefined
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.childrenFor 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 scopeThe 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 limitRuff 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 importTYPOGRAPHY_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 usedInside
_getLabels
,this.fetchLabels(workspaceSlug)
is invoked withoutawait
, so the subsequent filtering runs on a possibly emptylabelMap
, yielding incomplete results on first access.
Consider awaiting the promise or returning early until data is loaded, similar to the pattern you adopted withfetchedMap
.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 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 labelsThe unique constraint on
project
andname
has been modified to only check thatdeleted_at
is null, without requiringproject
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 labelsGood 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 fieldsRemoving
"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 nullThis 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 labelsThe 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 stateGood 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 scopeAppropriate renaming of components from project-specific to more generic names (
ProjectSettingsLabelList
→SettingsLabelList
,ProjectSettingLabelGroup
→SettingLabelGroup
, 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 scopeGood 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 scopeThe 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 projectIdThe 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 UIGood 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 consistencyThe 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 scopesThe 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 outdatedThe new CRUD endpoints are exposed under
/issue-labels/
, yet theinvalidate_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, +)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 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
📒 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
forproject_detail
and removal of"project"
fromread_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.
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
Test Scenarios
[workspace]/labels
uri: all the labels should be there, duplicated labels must be removed, issues updated accordinglyReferences
#3101
Summary by CodeRabbit
New Features
Improvements
Bug Fixes
Refactor
Chores