[CSM Portal] gate customer replies on case work-state; add engineer assignment - #893
Conversation
…ssignment
Adopts the case-write contract on the rich CaseView.
work-state + comment gate:
- Thread `workState` (ongoing/paused) through the case read views, the list
row, and both mappers; show an ongoing/paused chip on the case header.
- Gate the COMMENT composer's public-reply path on work_in_progress+ongoing
(new tested util `caseWorkState.ts`). Internal work notes are allowed in any
state: when a customer reply isn't allowed the composer locks to work-note
mode (toggle forced on + disabled) and shows the reason, instead of blocking
the whole input. Attachment-only sends are unaffected.
assign engineer:
- New AssignEngineerDialog (internal-user search via /users/search, plus an
Assign-to-me shortcut from the signed-in email) opened from the action-bar
'Assign / reassign engineer' item; assigns via PATCH /cases/{id}
{assigneeEmail} and refetches the detail. Extend the update payload and fix
the PATCH hook response type. Each PATCH sends exactly one field.
Verified: tsc -b, vitest (89), eslint, vite build all green.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (4)
🚧 Files skipped from review as they are similar to previous changes (4)
📝 WalkthroughWalkthroughThe PR adds a ChangesCase Work State, Engineer Assignment, and Comment Gating
Sequence DiagramssequenceDiagram
rect rgba(70, 130, 180, 0.5)
Note over User,CsmCaseCommentInput: Public reply gating flow
User->>CsmCaseDetailPage: opens case detail
CsmCaseDetailPage->>publicCommentGateReason: compute(state, workState)
publicCommentGateReason-->>CsmCaseDetailPage: reason string | null
CsmCaseDetailPage->>CsmCaseCommentInput: publicCommentDisabledReason=reason
User->>CsmCaseCommentInput: attempts public reply with text
CsmCaseCommentInput->>CsmCaseCommentInput: setError(reason), abort submit
end
rect rgba(60, 179, 113, 0.5)
Note over User,usePatchCsmCase: Engineer reassignment flow
User->>CsmCaseDetailPage: clicks "Assign / reassign engineer…"
CsmCaseDetailPage->>AssignEngineerDialog: open (assignOpen=true)
User->>AssignEngineerDialog: searches and selects engineer email
AssignEngineerDialog->>CsmCaseDetailPage: onAssign(email)
CsmCaseDetailPage->>usePatchCsmCase: mutate({ assigneeEmail: email })
usePatchCsmCase-->>CsmCaseDetailPage: BeUpdateCaseResponse
CsmCaseDetailPage->>AssignEngineerDialog: close dialog
CsmCaseDetailPage->>CsmCaseDetailPage: show "Case reassigned." feedback
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/csm-portal/webapp/src/api/backend/types.ts`:
- Around line 190-202: Replace the BeCaseUpdatePayload interface with a
discriminated union type that enforces exactly one field constraint at compile
time. Create separate union members where each represents a single valid payload
variant (one with only state, one with only priority, one with only
assigneeEmail, one with only watchList), ensuring all other fields are
explicitly omitted or set to never. This type-safe union pattern will prevent
callers from creating invalid payloads with zero or multiple fields and will
catch the latent bug in usePatchCsmCase.ts where the mock implementation
unconditionally accesses input.state and input.priority without checking which
field is actually set.
In
`@apps/csm-portal/webapp/src/features/csm-cases/components/CsmCaseCommentInput.tsx`:
- Around line 154-160: The current condition starting at line 154 only blocks
public text sends when publicCommentDisabledReason is set, but still allows
internal text sends (work notes) which the backend will reject. Add an
additional check to also block internal text sends by modifying the condition to
prevent sending any text (both public and internal) when
publicCommentDisabledReason exists and html is not empty, regardless of the
internal flag. This ensures the UI properly prevents all text-based sends that
would fail on the backend, not just public comments, and prevents the guaranteed
failure path that currently exists for work notes.
In `@apps/csm-portal/webapp/src/features/csm-cases/utils/caseWorkState.ts`:
- Around line 47-50: The error messages in the conditional return statements and
the final return statement are making definitive promises about internal work
note fallback behavior ("will be saved as an internal work note") that the
backend does not yet support for non-in-progress cases. Adjust both the message
returned when state is "work_in_progress" and workState is "paused", and the
final fallback message, to use conditional or non-committal language that does
not guarantee the internal work note behavior until the backend implementation
is complete. This will prevent misleading users about functionality that is not
yet fully implemented.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 649fd4f9-97e8-488a-8827-136ac7d11f18
📒 Files selected for processing (13)
apps/csm-portal/webapp/src/api/backend/types.tsapps/csm-portal/webapp/src/features/csm-cases/api/mocks/casesMocks.tsapps/csm-portal/webapp/src/features/csm-cases/api/useGetCsmCaseDetail.tsapps/csm-portal/webapp/src/features/csm-cases/api/useGetCsmCases.tsapps/csm-portal/webapp/src/features/csm-cases/api/usePatchCsmCase.tsapps/csm-portal/webapp/src/features/csm-cases/components/AssignEngineerDialog.tsxapps/csm-portal/webapp/src/features/csm-cases/components/CaseActionBar.tsxapps/csm-portal/webapp/src/features/csm-cases/components/CsmCaseCommentInput.tsxapps/csm-portal/webapp/src/features/csm-cases/pages/CsmCaseDetailPage.tsxapps/csm-portal/webapp/src/features/csm-cases/types/csmCases.tsapps/csm-portal/webapp/src/features/csm-cases/utils/caseWorkState.test.tsapps/csm-portal/webapp/src/features/csm-cases/utils/caseWorkState.tsapps/csm-portal/webapp/src/features/csm-dashboard/types/abtDashboard.ts
…tal gate copy
- Encode the exactly-one-field PATCH contract as a discriminated union on
BeCaseUpdatePayload (each variant ?: never's the others), so the constraint
is enforced at compile time. Simplify the mock to return only { id } (the
success handler ignores the body and refetches).
- Drop the 'will be saved as an internal work note' promise from the gate copy;
whether a work note is accepted in a non-in-progress state depends on a
backend follow-up, so the message stays non-committal.
tsc -b, vitest (90), eslint all green.
|
@coderabbitai review |
✅ Action performedReview finished.
|
Summary
Adopts the recently-landed case-write contract on the rich
CaseView: surfaces the case work sub-state (ongoing/paused), gates the customer-reply path of the comment composer on it, and adds engineer assignment from the case detail.What changed
Work-state + comment gate
workState(ongoing|paused| null) through the case read views, the list row type, and both case mappers.caseWorkState.ts(caseAcceptsPublicComments/publicCommentGateReason).work_in_progress+ongoing. Internal work notes remain allowed in any state — when a customer reply isn't allowed the composer locks to work-note mode (toggle forced on and disabled) and shows the reason, rather than disabling the whole input. Attachment-only sends are unaffected.Assign engineer
AssignEngineerDialog: searches internal users viaPOST /users/searchand offers an "Assign to me" shortcut from the signed-in email claim.PATCH /cases/{id}{ assigneeEmail }and refetches the detail so the assignee updates.Behaviour notes
type=work_note); until then a work note on a non-in-progress case is rejected upstream even though the UI allows it.Testing
pnpm build(tsc -b + vite build) — greenpnpm test(vitest) — 89 passed, incl. newcaseWorkStatetestspnpm lint(eslint) — cleanSummary by CodeRabbit