Skip to content

[CSM Portal] gate customer replies on case work-state; add engineer assignment - #893

Merged
dilshanfardil merged 2 commits into
wso2-open-operations:v2from
rksk:csm-case-write-contract
Jun 24, 2026
Merged

[CSM Portal] gate customer replies on case work-state; add engineer assignment#893
dilshanfardil merged 2 commits into
wso2-open-operations:v2from
rksk:csm-case-write-contract

Conversation

@rksk

@rksk rksk commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

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

  • Thread workState (ongoing | paused | null) through the case read views, the list row type, and both case mappers.
  • Show an ongoing/paused chip on the case-detail header.
  • New tested util caseWorkState.ts (caseAcceptsPublicComments / publicCommentGateReason).
  • The comment composer's public-reply path is gated on 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

  • New AssignEngineerDialog: searches internal users via POST /users/search and offers an "Assign to me" shortcut from the signed-in email claim.
  • Opened from the action-bar "Assign / reassign engineer…" item (previously disabled). Assigns via PATCH /cases/{id} { assigneeEmail } and refetches the detail so the assignee updates.
  • Extended the update payload and corrected the PATCH hook's response type. Each PATCH sends exactly one field (the endpoint requires exactly-one-of state/priority/assigneeEmail/watchList).

Behaviour notes

  • Assignment is a ServiceNow-source capability; on a Postgres-sourced case the backend rejects it and the error surfaces to the user.
  • Watchers UI is intentionally not included here — it needs a read path for the current watch list, which the case GET does not yet return (raised as a backend follow-up).
  • A separate backend follow-up is needed so the comment guard exempts internal work 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) — green
  • pnpm test (vitest) — 89 passed, incl. new caseWorkState tests
  • pnpm lint (eslint) — clean

Summary by CodeRabbit

  • New Features
    • Case assignment: Users can assign and reassign engineers using a dedicated dialog.
    • Work state tracking: In-progress cases now show a “Ongoing” or “Paused” sub-state in case details.
    • Comment gating: Customer-visible replies are disabled when a case is paused; internal work notes and attachments remain available.
  • Bug Fixes / UX Improvements
    • Reassign action in the case action menu is now enabled and relabeled to “Assign / reassign engineer…”.

…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.
@coderabbitai

coderabbitai Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: e9572cc2-8ad8-47d2-ac84-6f96cd1ac6bc

📥 Commits

Reviewing files that changed from the base of the PR and between 80ae1c5 and 7a251ac.

📒 Files selected for processing (4)
  • apps/csm-portal/webapp/src/api/backend/types.ts
  • apps/csm-portal/webapp/src/features/csm-cases/api/usePatchCsmCase.ts
  • apps/csm-portal/webapp/src/features/csm-cases/utils/caseWorkState.test.ts
  • apps/csm-portal/webapp/src/features/csm-cases/utils/caseWorkState.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • apps/csm-portal/webapp/src/features/csm-cases/utils/caseWorkState.ts
  • apps/csm-portal/webapp/src/features/csm-cases/api/usePatchCsmCase.ts
  • apps/csm-portal/webapp/src/api/backend/types.ts
  • apps/csm-portal/webapp/src/features/csm-cases/utils/caseWorkState.test.ts

📝 Walkthrough

Walkthrough

The PR adds a workState sub-state (ongoing/paused) to cases, propagates it through backend types, frontend types, and API hooks, and gates public customer comments based on work state. It also enables engineer reassignment via a new AssignEngineerDialog and expands the PATCH /cases/{id} contract to support assigneeEmail and watchList.

Changes

Case Work State, Engineer Assignment, and Comment Gating

Layer / File(s) Summary
Backend and frontend type contracts
src/api/backend/types.ts, src/features/csm-dashboard/types/abtDashboard.ts, src/features/csm-cases/types/csmCases.ts
Adds BeCaseWorkState/CaseWorkState union types, extends BeCaseView/BeCaseSearchView/CsmCaseRow with optional workState, and expands BeCaseUpdatePayload with assigneeEmail/watchList plus new BeWatchListUser, BeUpdatedCase, and BeUpdateCaseResponse interfaces.
Work state gating utility and tests
src/features/csm-cases/utils/caseWorkState.ts, src/features/csm-cases/utils/caseWorkState.test.ts
New module exports caseAcceptsPublicComments, publicCommentGateReason, and WORK_STATE_LABEL; Vitest suite covers all state/workState combinations for both functions.
API hooks and mocks: workState propagation and PATCH response update
src/features/csm-cases/api/useGetCsmCases.ts, src/features/csm-cases/api/useGetCsmCaseDetail.ts, src/features/csm-cases/api/usePatchCsmCase.ts, src/features/csm-cases/api/mocks/casesMocks.ts
Maps workState into CsmCaseRow and CsmCaseDetail from backend responses; updates usePatchCsmCase generic to BeUpdateCaseResponse; seeds mock case-2003 with workState: "paused" and adds hydration defaults.
AssignEngineerDialog component
src/features/csm-cases/components/AssignEngineerDialog.tsx, src/features/csm-cases/components/CaseActionBar.tsx
New dialog debounces search input, queries useSearchUsers, filters to internal users with email, and calls onAssign(email); enables the previously disabled reassign_engineer action bar item with updated label.
CsmCaseCommentInput public reply locking
src/features/csm-cases/components/CsmCaseCommentInput.tsx
Adds publicCommentDisabledReason prop; enforces internal mode via useEffect, blocks text submission when locked, disables internal-note toggle, and updates caption and send-button disabled state.
CsmCaseDetailPage integration
src/features/csm-cases/pages/CsmCaseDetailPage.tsx
Wires assignOpen state and onAssign PATCH callback for AssignEngineerDialog, computes publicReplyGateReason and passes it to CsmCaseCommentInput, renders work state chip in the header, and adjusts composer label/helper text based on gating.

Sequence Diagrams

sequenceDiagram
  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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • wso2-open-operations/cs-tools#890: Backend PR that defines the OpenAPI contract and handler changes for workState guarding and the UpdateCaseResponse shape that this frontend PR directly implements and consumes.
  • wso2-open-operations/cs-tools#892: Backend PR that exempts work_note from work-state comment gating, directly aligned with this PR's frontend client-side public reply blocking.
  • wso2-open-operations/cs-tools#896: Backend PR adding workState to case update requests/responses that this frontend PR's typing and UI directly correspond to.

Suggested labels

Type/Improvement, App/CSM Portal

Suggested reviewers

  • v15a1
  • cloby99

Poem

🐰 Hop hop, the cases now know when they pause,
A work state chip shines under bunny paws.
Paused replies locked, no public note slips through,
Reassign a case? The dialog sees it through!
The patch returns a beacon, message bright—
All wired and tested, everything feels right. ✨

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description is detailed and well-structured, covering purpose, changes, behavior notes, and testing results. However, it does not follow the required template structure with sections like Goals, Approach, User Stories, Release Note, Documentation, etc. Restructure the description to match the required template: add Goals, Approach, User Stories, Release Note, Documentation, Training, Certification, Marketing, Automation Tests (unit & integration), Security Checks, Samples, Related PRs, Migrations, Test Environment, and Learning sections.
Docstring Coverage ⚠️ Warning Docstring coverage is 58.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the two main changes: work-state gating for customer replies and engineer assignment functionality, which directly matches the changeset.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with 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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

📥 Commits

Reviewing files that changed from the base of the PR and between 57a1884 and 80ae1c5.

📒 Files selected for processing (13)
  • apps/csm-portal/webapp/src/api/backend/types.ts
  • apps/csm-portal/webapp/src/features/csm-cases/api/mocks/casesMocks.ts
  • apps/csm-portal/webapp/src/features/csm-cases/api/useGetCsmCaseDetail.ts
  • apps/csm-portal/webapp/src/features/csm-cases/api/useGetCsmCases.ts
  • apps/csm-portal/webapp/src/features/csm-cases/api/usePatchCsmCase.ts
  • apps/csm-portal/webapp/src/features/csm-cases/components/AssignEngineerDialog.tsx
  • apps/csm-portal/webapp/src/features/csm-cases/components/CaseActionBar.tsx
  • apps/csm-portal/webapp/src/features/csm-cases/components/CsmCaseCommentInput.tsx
  • apps/csm-portal/webapp/src/features/csm-cases/pages/CsmCaseDetailPage.tsx
  • apps/csm-portal/webapp/src/features/csm-cases/types/csmCases.ts
  • apps/csm-portal/webapp/src/features/csm-cases/utils/caseWorkState.test.ts
  • apps/csm-portal/webapp/src/features/csm-cases/utils/caseWorkState.ts
  • apps/csm-portal/webapp/src/features/csm-dashboard/types/abtDashboard.ts

Comment thread apps/csm-portal/webapp/src/api/backend/types.ts Outdated
Comment thread apps/csm-portal/webapp/src/features/csm-cases/utils/caseWorkState.ts Outdated
…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.
@rksk

rksk commented Jun 23, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 23, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants