Feat/add chat details - #250
sacheeramesh merged 2 commits into
Conversation
Prefer deployedProduct.label for product display when it contains non-empty trimmed text; fall back to product otherwise. Introduce CaseDetailsDeployedProduct and update CaseDetails.deployedProduct to use this shape. Add ConversationMessage and ConversationMessagesResponse models to represent conversation messages, and add CONVERSATION_MESSAGES to ApiQueryKeys.
Introduce useGetConversationMessages hook that uses useInfiniteQuery to fetch conversation messages (GET /conversations/{conversationId}/messages) with limit/offset pagination. The hook uses useAsgardeo for auth state, useAuthApiClient as the authenticated fetch client, and useLogger for debug logs. It validates CUSTOMER_PORTAL_BACKEND_BASE_URL, defaults pageSize to 10 (configurable), enables the query only when signed in and a conversationId is present, sets a 5-minute staleTime, and computes next offsets via getNextPageParam. Errors are thrown for missing configuration or non-OK responses.
📝 WalkthroughWalkthroughThis PR introduces a new React hook for fetching paginated conversation messages with infinite query support, including authentication validation via ASGardeo. It adds supporting API constants and response types for conversation data, and refines product label rendering to avoid displaying empty values. Changes
Sequence Diagram(s)sequenceDiagram
participant Component as React Component
participant Hook as useGetConversationMessages
participant Auth as ASGardeo
participant Query as TanStack Query
participant API as Backend API
Component->>Hook: Call with conversationId
Hook->>Auth: Check if user signed in & auth loaded
alt Auth Valid
Query->>Query: Check cache & stale status
Query->>API: GET /conversations/{id}/messages?limit=10&offset=0
API-->>Query: ConversationMessagesResponse
Query->>Hook: Return infinite query result
Hook-->>Component: InfiniteData with paginated messages
else Auth Invalid/Loading
Hook-->>Component: Query disabled, no request
end
alt User Requests Next Page
Component->>Hook: Call fetchNextPage()
Hook->>Query: Advance offset, fetch next batch
Query->>API: GET with new offset parameter
API-->>Query: Additional messages
Query-->>Hook: Merge with existing data
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~22 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 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: 1
🧹 Nitpick comments (3)
apps/customer-portal/webapp/src/api/useGetConversationMessages.ts (1)
85-91:getNextPageParamcan loop if the server returnslimit: 0.
nextOffset = lastPage.offset + lastPage.limit— if the server ever returnslimit: 0andtotalRecords > 0,nextOffsetequalslastPage.offsetand the conditionnextOffset < totalRecordsremains true, returning the same offset on every call. A simple progress guard eliminates this:♻️ Proposed fix
getNextPageParam: (lastPage) => { const nextOffset = lastPage.offset + lastPage.limit; - if (nextOffset >= lastPage.totalRecords) { + if (lastPage.limit <= 0 || nextOffset >= lastPage.totalRecords) { return undefined; } return nextOffset; },🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/customer-portal/webapp/src/api/useGetConversationMessages.ts` around lines 85 - 91, The pagination logic in getNextPageParam (in useGetConversationMessages) can loop if the server returns lastPage.limit === 0; modify the implementation to guard against non‑progressing pages by returning undefined when lastPage.limit <= 0 or when computed nextOffset is less than or equal to lastPage.offset (i.e., no progress), otherwise return nextOffset as currently done.apps/customer-portal/webapp/src/models/responses.ts (1)
437-453:ConversationMessage/ConversationMessagesResponseare near-duplicates ofCaseComment/CaseCommentsResponse.The two pairs are structurally identical except that
hasInlineAttachmentsandinlineAttachmentsare required inConversationMessagebut optional inCaseComment. If the API contract warrants this distinction, a shared base interface would eliminate the repetition:♻️ Suggested refactor — shared base type
+/** Shared fields between CaseComment and ConversationMessage. */ +export interface BaseComment { + id: string; + content: string; + type: string; + createdOn: string; + createdBy: string; + isEscalated: boolean; +} -export interface CaseComment { - id: string; - content: string; - type: string; - createdOn: string; - createdBy: string; - isEscalated: boolean; - /** Whether this comment has inline images. */ - hasInlineAttachments?: boolean; - /** Inline attachments for images in content (img src replacement). */ - inlineAttachments?: CaseCommentInlineAttachment[]; -} +export interface CaseComment extends BaseComment { + hasInlineAttachments?: boolean; + inlineAttachments?: CaseCommentInlineAttachment[]; +} -export interface ConversationMessage { - id: string; - content: string; - type: string; - createdOn: string; - createdBy: string; - isEscalated: boolean; - hasInlineAttachments: boolean; - inlineAttachments: CaseCommentInlineAttachment[]; -} +export interface ConversationMessage extends BaseComment { + hasInlineAttachments: boolean; + inlineAttachments: CaseCommentInlineAttachment[]; +}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/customer-portal/webapp/src/models/responses.ts` around lines 437 - 453, ConversationMessage and CaseComment are duplicated types; create a shared base interface (e.g., BaseComment) capturing common fields (id, content, type, createdOn, createdBy, isEscalated, plus inline attachment fields) and then have ConversationMessage and CaseComment extend that base, making hasInlineAttachments and inlineAttachments optional on CaseComment if the API requires; similarly replace duplicated response types with a generic CommentResponse<T extends BaseComment> or have ConversationMessagesResponse and CaseCommentsResponse reuse the same shape (comments: BaseComment[] / T[], totalRecords, offset, limit) so you remove the repeated property definitions while retaining the existing names and optionality for hasInlineAttachments/inlineAttachments and the CaseCommentInlineAttachment type.apps/customer-portal/webapp/src/components/support/case-details/details-tab/CaseDetailsDetailsPanel.tsx (1)
220-224: Simplify the label trim guard — it checks trimmed length but renders the untrimmed value.
?.trim?.()uses unnecessary optional-call syntax (?.()) on.trim, which is always a method on astring. More critically, the expression trims to determine emptiness but then passesdata.deployedProduct.label(untrimmed) toformatValue. The compact form below is both idiomatic and consistent:♻️ Proposed simplification
- {formatValue( - data?.deployedProduct?.label?.trim?.()?.length - ? data.deployedProduct.label - : null, - )} + {formatValue(data?.deployedProduct?.label?.trim() || null)}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/customer-portal/webapp/src/components/support/case-details/details-tab/CaseDetailsDetailsPanel.tsx` around lines 220 - 224, In CaseDetailsDetailsPanel near the use of formatValue, the guard uses optional-call on .trim and checks trimmed length but then passes the untrimmed label; change it to check and pass the trimmed string (and keep optional chaining for deployedProduct/label). Concretely, replace the current expression so formatValue receives either label.trim() when label exists and has nonempty trimmed length, or null otherwise, while removing the unnecessary ?.() on .trim and ensuring you reference data.deployedProduct.label consistently.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/customer-portal/webapp/src/api/useGetConversationMessages.ts`:
- Line 80: The debug line in useGetConversationMessages.ts currently logs the
full ConversationMessagesResponse (variable data) including comments[].content
and comments[].createdBy; remove or replace that raw data log to avoid leaking
PII and instead log only non-sensitive metadata (e.g., number of comments,
pagination offset/limit, and any request id). Update the logger.debug call in
useGetConversationMessages (where it logs "[useGetConversationMessages] Data
received:") to omit data and log a small metadata object (count, offset/limit)
or a concise message, and ensure no raw `data`, `comments[].content`, or
`comments[].createdBy` values are emitted to logs.
---
Nitpick comments:
In `@apps/customer-portal/webapp/src/api/useGetConversationMessages.ts`:
- Around line 85-91: The pagination logic in getNextPageParam (in
useGetConversationMessages) can loop if the server returns lastPage.limit === 0;
modify the implementation to guard against non‑progressing pages by returning
undefined when lastPage.limit <= 0 or when computed nextOffset is less than or
equal to lastPage.offset (i.e., no progress), otherwise return nextOffset as
currently done.
In
`@apps/customer-portal/webapp/src/components/support/case-details/details-tab/CaseDetailsDetailsPanel.tsx`:
- Around line 220-224: In CaseDetailsDetailsPanel near the use of formatValue,
the guard uses optional-call on .trim and checks trimmed length but then passes
the untrimmed label; change it to check and pass the trimmed string (and keep
optional chaining for deployedProduct/label). Concretely, replace the current
expression so formatValue receives either label.trim() when label exists and has
nonempty trimmed length, or null otherwise, while removing the unnecessary ?.()
on .trim and ensuring you reference data.deployedProduct.label consistently.
In `@apps/customer-portal/webapp/src/models/responses.ts`:
- Around line 437-453: ConversationMessage and CaseComment are duplicated types;
create a shared base interface (e.g., BaseComment) capturing common fields (id,
content, type, createdOn, createdBy, isEscalated, plus inline attachment fields)
and then have ConversationMessage and CaseComment extend that base, making
hasInlineAttachments and inlineAttachments optional on CaseComment if the API
requires; similarly replace duplicated response types with a generic
CommentResponse<T extends BaseComment> or have ConversationMessagesResponse and
CaseCommentsResponse reuse the same shape (comments: BaseComment[] / T[],
totalRecords, offset, limit) so you remove the repeated property definitions
while retaining the existing names and optionality for
hasInlineAttachments/inlineAttachments and the CaseCommentInlineAttachment type.
ℹ️ Review info
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
apps/customer-portal/webapp/src/api/useGetConversationMessages.tsapps/customer-portal/webapp/src/components/support/case-details/details-tab/CaseDetailsDetailsPanel.tsxapps/customer-portal/webapp/src/constants/apiConstants.tsapps/customer-portal/webapp/src/models/responses.ts
de42344
into
wso2-open-operations:customer-portal-milestone-1
Purpose
Goals
Approach
User stories
Release note
Documentation
Training
Certification
Marketing
Automation tests
Security checks
Samples
Related PRs
Migrations (if applicable)
Test environment
Learning
Summary by CodeRabbit
Release Notes
New Features
Bug Fixes