[WEB-4828] refactor: remove legacy project-level draft work items components and code (replaced by workspace drafts)#7694
Conversation
…ponents and code (replaced by workspace drafts)
WalkthroughRemoves project “draft issues” feature across routes, UI components, stores, hooks, services, and types. Eliminates draft-specific pages and layouts, quick actions, filters, services, and store variants. Simplifies issue fetching by removing draft/default mode and related props, types, and control flow. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor UI as UI
participant Modal as CreateUpdateIssueModal
participant Store as IssueDetail Store
participant Service as IssueService
rect rgb(245,248,255)
note over UI,Modal: Before (draft-aware)
UI->>Modal: Open (isDraft? flag)
Modal->>Store: fetchIssue(ws, proj, id, status)
alt status == "DRAFT"
Store->>Service: retrieveDraftIssue(...)
Service-->>Store: Draft Issue
else
Store->>Service: retrieveIssue(...)
Service-->>Store: Issue
end
Store-->>Modal: Issue data
end
rect rgb(242,255,245)
note over UI,Modal: After (single path)
UI->>Modal: Open
Modal->>Store: fetchIssue(ws, proj, id)
Store->>Service: retrieveIssue(ws, proj, id)
Service-->>Store: Issue
Store-->>Modal: Issue data
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
✨ Finishing Touches
🧪 Generate unit tests
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. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
|
Pull Request Linked with Plane Work Items Comment Automatically Generated by Plane |
There was a problem hiding this comment.
Actionable comments posted: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
apps/web/core/store/issue/issue-details/issue.store.ts (1)
91-117: Bug: network fetch result is ignored, leading to erroneous “Work item not found”.await this.issueService.retrieve(...) isn’t assigned, so if local DB miss occurs, you throw even after a successful network fetch. Also, you add the issue twice.
// actions - fetchIssue = async (workspaceSlug: string, projectId: string, issueId: string) => { + fetchIssue = async (workspaceSlug: string, projectId: string, issueId: string) => { const query = { expand: "issue_reactions,issue_attachments,issue_link,parent", }; let issue: TIssue | undefined; // fetch issue from local db if (this.serviceType === EIssueServiceType.ISSUES) { issue = await persistence.getIssue(issueId); } this.fetchingIssueDetails = issueId; if (issue) { this.addIssueToStore(issue); this.localDBIssueDescription = issueId; } - await this.issueService.retrieve(workspaceSlug, projectId, issueId, query); + // Always fetch fresh from API + issue = await this.issueService.retrieve(workspaceSlug, projectId, issueId, query); if (!issue) throw new Error("Work item not found"); const issuePayload = this.addIssueToStore(issue); this.localDBIssueDescription = undefined; - this.rootIssueDetailStore.rootIssueStore.issues.addIssue([issuePayload]); + // already added by addIssueToStoreapps/web/core/components/issues/issue-layouts/kanban/headers/group-by-card.tsx (1)
53-53: Fix potential TypeError in optional chaining on collapsedGroups.group_by.includes
collapsedGroups?.group_by.includeswill throw ifgroup_byis undefined. Chain the method call and default tofalse.- const verticalAlignPosition = sub_group_by ? false : collapsedGroups?.group_by.includes(column_id); + const verticalAlignPosition = + sub_group_by ? false : (collapsedGroups?.group_by?.includes(column_id) ?? false);apps/web/core/components/issues/issue-modal/base.tsx (1)
324-335: Avoid mutating props (delete on data); sanitize payload insteadDirectly deleting
data.sourceIssueIdmutates props and can cause subtle state bugs. Strip the field from the payload locally.- // remove sourceIssueId from payload since it is not needed - if (data?.sourceIssueId) delete data.sourceIssueId; - - let response: TIssue | undefined = undefined; + // sanitize payload without mutating props + const { sourceIssueId: _sourceIssueId, ...cleanedPayload } = (payload as any) ?? {}; + let response: TIssue | undefined = undefined; @@ - if (beforeFormSubmit) await beforeFormSubmit(); - if (!data?.id) response = await handleCreateIssue(payload, is_draft_issue); - else response = await handleUpdateIssue(payload); + if (beforeFormSubmit) await beforeFormSubmit(); + if (!data?.id) response = await handleCreateIssue(cleanedPayload, is_draft_issue); + else response = await handleUpdateIssue(cleanedPayload);
🧹 Nitpick comments (15)
apps/web/core/store/issue/helpers/base-issues.store.ts (3)
326-326: Use enum instead of string literal for layout check.Prevents drift/typos and matches usage above.
- return displayFilters?.layout === "kanban" ? displayFilters?.sub_group_by : undefined; + return displayFilters?.layout === EIssueLayoutTypes.KANBAN ? displayFilters?.sub_group_by : undefined;
232-232: Typo in method name: onfetchNexIssues → onFetchNextIssues (add alias).Non-blocking; add a correctly spelled alias to ease future cleanup without breaking callers.
onfetchIssues: action.bound, onfetchNexIssues: action.bound, + onFetchNextIssues: action.bound,onfetchNexIssues(issuesResponse: TIssuesResponse, groupId?: string, subGroupId?: string) { // existing implementation... } + + // Temporary alias with correct spelling; safe to remove after callers migrate. + onFetchNextIssues(issuesResponse: TIssuesResponse, groupId?: string, subGroupId?: string) { + return this.onfetchNexIssues(issuesResponse, groupId, subGroupId); + }Also applies to: 518-535
624-624: Fix comment typos (“Male API call” → “Make API call”).Keeps comments professional.
- // Male API call + // Make API callAlso applies to: 647-647
apps/web/core/components/workspace/sidebar/quick-actions.tsx (6)
68-75: Consider deriving fetch behavior inside the modal instead of a new prop.Let the modal infer fetching to reduce API surface (e.g., fetch only when not draft and an id exists).
Example in modal.tsx:
const shouldFetch = props.fetchIssueDetails ?? (!props.isDraft && !!props.issueId);
72-72: Inline the function reference for onSubmit.Slight cleanup; preserves behavior and avoids a new closure.
- onSubmit={() => removeWorkspaceDraftIssue()} + onSubmit={removeWorkspaceDraftIssue}
59-64: Avoid mutating the storedValue object when deleting a draft.Create a new object to prevent accidental shared-state mutation.
- const removeWorkspaceDraftIssue = () => { - const draftIssues = storedValue ?? {}; - if (workspaceSlug && draftIssues[workspaceSlug]) delete draftIssues[workspaceSlug]; - setValue(draftIssues); - return Promise.resolve(); - }; + const removeWorkspaceDraftIssue = () => { + if (!workspaceSlug) return Promise.resolve(); + const { [workspaceSlug]: _removed, ...rest } = storedValue ?? {}; + setValue(rest); + return Promise.resolve(); + };
24-25: Remove unused hover state and handlers (isDraftButtonOpen).State is written but never read; the handlers only toggle this state.
- const [isDraftButtonOpen, setIsDraftButtonOpen] = useState(false); + // (removed) isDraftButtonOpen – unused - const handleMouseEnter = () => { - if (timeoutRef?.current) { - clearTimeout(timeoutRef.current); - } - setIsDraftButtonOpen(true); - }; - - const handleMouseLeave = () => { - timeoutRef.current = setTimeout(() => { - setIsDraftButtonOpen(false); - }, 300); - }; + // (removed) hover handlers – unused - onMouseEnter={handleMouseEnter} - onMouseLeave={handleMouseLeave} + // hover handlers removedAlso applies to: 45-57, 89-90
26-28: Type the timeout ref precisely; drop any + eslint override.Cleaner types and lint.
- // eslint-disable-next-line @typescript-eslint/no-explicit-any - const timeoutRef = useRef<any>(); + const timeoutRef = useRef<number | null>(null);
83-83: Trim trailing space in className.Micro-nit for consistency.
- "cursor-not-allowed opacity-50 ": disabled, + "cursor-not-allowed opacity-50": disabled,apps/web/core/components/issues/issue-layouts/list/headers/group-by-card.tsx (1)
77-87: Context-aware toast copy: say “module” vs “cycle”.Messages hardcode “cycle”, but this header runs for modules too. Make the text context-aware.
Apply:
- setToast({ - type: TOAST_TYPE.SUCCESS, - title: "Success!", - message: "Work items added to the cycle successfully.", - }); + const targetName = moduleId ? "module" : "cycle"; + setToast({ + type: TOAST_TYPE.SUCCESS, + title: "Success!", + message: `Work items added to the ${targetName} successfully.`, + }); ... - setToast({ - type: TOAST_TYPE.ERROR, - title: "Error!", - message: "Selected work items could not be added to the cycle. Please try again.", - }); + const targetName = moduleId ? "module" : "cycle"; + setToast({ + type: TOAST_TYPE.ERROR, + title: "Error!", + message: `Selected work items could not be added to the ${targetName}. Please try again.`, + });apps/web/core/store/issue/issue-details/issue.store.ts (2)
8-8: Remove unused WorkspaceDraftService import.No references after draft flow removal.
-import { IssueArchiveService, WorkspaceDraftService, IssueService } from "@/services/issue"; +import { IssueArchiveService, IssueService } from "@/services/issue";
50-65: Drop unused draftWorkItemService field.Dead field/instantiation.
- draftWorkItemService; + // (removed) draftWorkItemService; ... - this.issueArchiveService = new IssueArchiveService(serviceType); - this.draftWorkItemService = new WorkspaceDraftService(); + this.issueArchiveService = new IssueArchiveService(serviceType);apps/web/core/components/issues/issue-layouts/kanban/headers/group-by-card.tsx (2)
70-83: Align success/error copy with context (cycle vs module)Toast messages hardcode “cycle” even when adding to a module. Make the copy context-aware.
- setToast({ - type: TOAST_TYPE.SUCCESS, - title: "Success!", - message: "Work items added to the cycle successfully.", - }); + setToast({ + type: TOAST_TYPE.SUCCESS, + title: "Success!", + message: `Work items added to the ${moduleId ? "module" : "cycle"} successfully.`, + }); ... - setToast({ - type: TOAST_TYPE.ERROR, - title: "Error!", - message: "Selected work items could not be added to the cycle. Please try again.", - }); + setToast({ + type: TOAST_TYPE.ERROR, + title: "Error!", + message: `Selected work items could not be added to the ${moduleId ? "module" : "cycle"}. Please try again.`, + });
62-64: Prefer boolean for renderExistingIssueModalUse an explicit boolean to avoid accidental reliance on string truthiness.
- const renderExistingIssueModal = moduleId || cycleId; + const renderExistingIssueModal = Boolean(moduleId || cycleId);apps/web/core/components/issues/peek-overview/root.tsx (1)
5-6: Remove unused route dependency and prop from memo deps
usePathname()andis_draftare unused in the memoized logic; keeping them in deps causes unnecessary recomputation.-import { usePathname } from "next/navigation"; +// import { usePathname } from "next/navigation"; // no longer needed @@ - const pathname = usePathname(); + // const pathname = usePathname(); @@ - [fetchIssue, is_draft, issues, fetchActivities, pathname, removeRoutePeekId, restoreIssue] + [fetchIssue, issues, fetchActivities, removeRoutePeekId, restoreIssue]Also applies to: 31-31, 281-282
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (38)
apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/draft-issues/header.tsx(0 hunks)apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/draft-issues/layout.tsx(0 hunks)apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/draft-issues/page.tsx(0 hunks)apps/web/ce/components/command-palette/modals/issue-level.tsx(1 hunks)apps/web/core/components/issues/issue-layouts/empty-states/draft-issues.tsx(0 hunks)apps/web/core/components/issues/issue-layouts/empty-states/index.tsx(0 hunks)apps/web/core/components/issues/issue-layouts/filters/applied-filters/roots/draft-issue.tsx(0 hunks)apps/web/core/components/issues/issue-layouts/kanban/base-kanban-root.tsx(0 hunks)apps/web/core/components/issues/issue-layouts/kanban/headers/group-by-card.tsx(1 hunks)apps/web/core/components/issues/issue-layouts/kanban/roots/draft-issue-root.tsx(0 hunks)apps/web/core/components/issues/issue-layouts/list/base-list-root.tsx(0 hunks)apps/web/core/components/issues/issue-layouts/list/headers/group-by-card.tsx(1 hunks)apps/web/core/components/issues/issue-layouts/list/roots/draft-issue-root.tsx(0 hunks)apps/web/core/components/issues/issue-layouts/quick-action-dropdowns/all-issue.tsx(0 hunks)apps/web/core/components/issues/issue-layouts/quick-action-dropdowns/cycle-issue.tsx(0 hunks)apps/web/core/components/issues/issue-layouts/quick-action-dropdowns/draft-issue.tsx(0 hunks)apps/web/core/components/issues/issue-layouts/quick-action-dropdowns/helper.tsx(0 hunks)apps/web/core/components/issues/issue-layouts/quick-action-dropdowns/index.ts(0 hunks)apps/web/core/components/issues/issue-layouts/quick-action-dropdowns/issue-detail.tsx(0 hunks)apps/web/core/components/issues/issue-layouts/quick-action-dropdowns/module-issue.tsx(0 hunks)apps/web/core/components/issues/issue-layouts/quick-action-dropdowns/project-issue.tsx(1 hunks)apps/web/core/components/issues/issue-layouts/roots/draft-issue-layout-root.tsx(0 hunks)apps/web/core/components/issues/issue-modal/base.tsx(1 hunks)apps/web/core/components/issues/peek-overview/root.tsx(1 hunks)apps/web/core/components/workspace/sidebar/quick-actions.tsx(1 hunks)apps/web/core/hooks/store/use-issues.ts(0 hunks)apps/web/core/hooks/use-group-dragndrop.ts(0 hunks)apps/web/core/hooks/use-issues-actions.tsx(0 hunks)apps/web/core/services/issue/index.ts(0 hunks)apps/web/core/services/issue/issue_draft.service.ts(0 hunks)apps/web/core/store/issue/draft/filter.store.ts(0 hunks)apps/web/core/store/issue/draft/index.ts(0 hunks)apps/web/core/store/issue/draft/issue.store.ts(0 hunks)apps/web/core/store/issue/helpers/base-issues.store.ts(1 hunks)apps/web/core/store/issue/issue-details/issue.store.ts(5 hunks)apps/web/core/store/issue/issue-details/root.store.ts(1 hunks)apps/web/core/store/issue/root.store.ts(0 hunks)packages/types/src/issues/issue.ts(0 hunks)
💤 Files with no reviewable changes (28)
- packages/types/src/issues/issue.ts
- apps/web/core/components/issues/issue-layouts/quick-action-dropdowns/module-issue.tsx
- apps/web/core/store/issue/draft/index.ts
- apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/draft-issues/layout.tsx
- apps/web/core/services/issue/issue_draft.service.ts
- apps/web/core/services/issue/index.ts
- apps/web/core/components/issues/issue-layouts/list/roots/draft-issue-root.tsx
- apps/web/core/components/issues/issue-layouts/quick-action-dropdowns/all-issue.tsx
- apps/web/core/components/issues/issue-layouts/kanban/base-kanban-root.tsx
- apps/web/core/components/issues/issue-layouts/empty-states/index.tsx
- apps/web/core/components/issues/issue-layouts/filters/applied-filters/roots/draft-issue.tsx
- apps/web/core/components/issues/issue-layouts/roots/draft-issue-layout-root.tsx
- apps/web/core/components/issues/issue-layouts/quick-action-dropdowns/helper.tsx
- apps/web/core/components/issues/issue-layouts/list/base-list-root.tsx
- apps/web/core/components/issues/issue-layouts/empty-states/draft-issues.tsx
- apps/web/core/components/issues/issue-layouts/quick-action-dropdowns/cycle-issue.tsx
- apps/web/core/hooks/use-group-dragndrop.ts
- apps/web/core/hooks/use-issues-actions.tsx
- apps/web/core/store/issue/draft/filter.store.ts
- apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/draft-issues/page.tsx
- apps/web/core/components/issues/issue-layouts/quick-action-dropdowns/index.ts
- apps/web/core/store/issue/root.store.ts
- apps/web/core/components/issues/issue-layouts/quick-action-dropdowns/draft-issue.tsx
- apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/draft-issues/header.tsx
- apps/web/core/components/issues/issue-layouts/kanban/roots/draft-issue-root.tsx
- apps/web/core/store/issue/draft/issue.store.ts
- apps/web/core/hooks/store/use-issues.ts
- apps/web/core/components/issues/issue-layouts/quick-action-dropdowns/issue-detail.tsx
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-06-16T07:23:39.497Z
Learnt from: vamsikrishnamathala
PR: makeplane/plane#7214
File: web/core/store/issue/helpers/base-issues.store.ts:117-117
Timestamp: 2025-06-16T07:23:39.497Z
Learning: In the updateIssueDates method of BaseIssuesStore (web/core/store/issue/helpers/base-issues.store.ts), the projectId parameter is intentionally made optional to support override implementations in subclasses. The base implementation requires projectId and includes an early return check, but making it optional allows derived classes to override the method with different parameter requirements.
Applied to files:
apps/web/core/store/issue/issue-details/root.store.tsapps/web/core/components/issues/issue-modal/base.tsxapps/web/core/store/issue/helpers/base-issues.store.tsapps/web/core/store/issue/issue-details/issue.store.tsapps/web/core/components/issues/peek-overview/root.tsx
🧬 Code graph analysis (1)
apps/web/core/store/issue/issue-details/issue.store.ts (1)
packages/types/src/issues/issue.ts (1)
TIssue(83-97)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Build and lint web apps
- GitHub Check: Analyze (javascript)
🔇 Additional comments (13)
apps/web/core/store/issue/helpers/base-issues.store.ts (2)
777-783: updateIssueDates optional projectId guard is correct.Early-return preserves base-class contract while allowing subclass overrides, matching prior guidance.
44-44: Draft service removal verified – no stale references found. Search forIssueDraftService,issueDraftService,createDraftIssue,updateDraftIssue, andEIssueServiceType.DRAFTreturned no matches.apps/web/core/components/workspace/sidebar/quick-actions.tsx (2)
73-73: Disable detail fetch for workspace drafts — LGTM.Passing fetchIssueDetails={false} for draft modals avoids unnecessary network calls and aligns with the legacy draft removal.
68-75: Audit all CreateUpdateIssueModal usages for fetchIssueDetails default
CreateUpdateIssueModalBase destructuresfetchIssueDetails = true, so any invocation without an explicitfetchIssueDetailsprop will auto-fetch. Verify each<CreateUpdateIssueModal>call site—especially those omittingfetchIssueDetails—to ensure no unintended data fetch is introduced.apps/web/core/components/issues/issue-layouts/list/headers/group-by-card.tsx (2)
5-5: LGTM on removing route-based draft detection.Importing only useParams is consistent with removing isDraft plumbing.
171-179: Confirm correct key for cycle filter
TProjectIssuesSearchParams likely expects a cycle identifier (e.g.cycle_id), not a booleancycle; verify thatexistingIssuesListModalPayloaduses the right property name to prefilter by cycle.apps/web/ce/components/command-palette/modals/issue-level.tsx (1)
3-3: LGTM: isDraft removal is consistent and safe here.CreateUpdateIssueModal invocation aligns with the new API.
apps/web/core/store/issue/issue-details/root.store.ts (1)
275-276: All fetchIssue call sites updated; no four-argument usages or issueStatus references remain.apps/web/core/components/issues/issue-layouts/quick-action-dropdowns/project-issue.tsx (1)
73-81: Verify CreateUpdateIssueModal sanitizes server-controlled fields
Ensure the payload submitted by CreateUpdateIssueModal omits all read-only fields (e.g.id,sequence_id,created_at,updated_at,completed_at,archived_at) to prevent carrying over timestamps or sequence values.apps/web/core/components/issues/issue-layouts/kanban/headers/group-by-card.tsx (1)
92-98: LGTM: Removed draft prop when opening CreateUpdateIssueModalThe modal opens without the legacy
isDraftprop; defaults in the modal base cover this. No functional regression expected.apps/web/core/components/issues/issue-modal/base.tsx (2)
89-91: LGTM: fetchIssue signature updated to three argsThe call matches the new API (
workspaceSlug, projectId, issueId). Looks correct.
89-91: No remaining four-argument fetchIssue calls or legacy draft props found
All call sites have been migrated off the fourth fetchIssue argument and CreateUpdateIssueModal no longer receives legacy draft props.apps/web/core/components/issues/peek-overview/root.tsx (1)
67-68: LGTM: Updated fetchIssue to new signatureThree-argument call is correct and consistent with the refactor.
Description
This PR removes all legacy and deprecated code related to project-level draft work items, which have been replaced by workspace drafts ( PR #5772 ). This change ensures that we no longer need to maintain this broken code for new features and also cleans up the codebase.
Type of Change
Summary by CodeRabbit
Refactor
Chores