Skip to content

Conversation

@aleksandernsilva
Copy link
Contributor

@aleksandernsilva aleksandernsilva commented Sep 17, 2025

Proposed changes (including videos or screenshots)

This PR:

  • Extracts fields from RepliesForm and MessageForm to improve readability and separation of concerns
  • Removes redundant "enter to submit" shortcut, since the native behavior already covers it
  • Moves RecipientForm.spec.tsx to the RecipientForm folder

Issue(s)

CTZ-186

Steps to test or reproduce

N/A

Further comments

Summary by CodeRabbit

  • New Features
    • Dedicated template selector with clearer validation and a “Learn more” hint.
    • New Replies form with department and agent pickers, permission-aware choices, retry on load failures, and refined error handling.
  • Changes
    • Removed keyboard shortcut (Cmd/Ctrl+Enter) submission — use standard form submit.
  • Tests
    • Cleaned up test mocks and updated imports; no user-facing impact.

@dionisio-bot
Copy link
Contributor

dionisio-bot bot commented Sep 17, 2025

Looks like this PR is ready to merge! 🎉
If you have any trouble, please check the PR guidelines

@changeset-bot
Copy link

changeset-bot bot commented Sep 17, 2025

⚠️ No Changeset found

Latest commit: 9c7b622

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 17, 2025

Walkthrough

Replaces inline template selection with a new TemplateField, removes the keyboard-submit hook, switches forms to standard handleSubmit, restructures RepliesForm into a new module with Agent/Department fields and utilities, updates tests (removes tinykeys mocks and adjusts import paths), and adds unit tests and helper hooks for agent derivation and allowed-agents logic.

Changes

Cohort / File(s) Summary
MessageForm refactor
apps/meteor/.../MessageForm/MessageForm.tsx
Replaces inline TemplateSelect and field-level UI with TemplateField; uses useWatch/useMemo for template resolution; removes keyboard-submit usage and formRef; simplifies formState usage and submit flow.
New Template field
apps/meteor/.../MessageForm/components/TemplateField.tsx
Adds TemplateField component wired to react-hook-form (useController), with ordered validations, accessibility attributes, error display, and a Learn-more hint.
RepliesForm (new module)
apps/meteor/.../RepliesForm/RepliesForm.tsx, apps/meteor/.../RepliesForm/components/AgentField.tsx, apps/meteor/.../RepliesForm/components/DepartmentField.tsx, apps/meteor/.../RepliesForm/index.ts
Adds new RepliesForm with typed payload & ref, permission-aware data fetching, validation, error handling, and field components (AgentField, DepartmentField) plus barrel export.
RepliesForm legacy removal
apps/meteor/.../forms/RepliesForm.tsx
Removes previous RepliesForm file and its exported types.
Keyboard submit hook removal
apps/meteor/.../hooks/useFormKeyboardSubmit.tsx
Deletes useFormKeyboardSubmit hook that bound $mod+Enter using tinykeys.
RecipientForm submit change
apps/meteor/.../RecipientForm/RecipientForm.tsx
Removes keyboard-submit integration and formRef; relies on handleSubmit for submission.
Test updates (tinykeys mocks & paths)
apps/meteor/.../MessageForm/MessageForm.spec.tsx, apps/meteor/.../RecipientForm/RecipientForm.spec.tsx, apps/meteor/.../RepliesForm/RepliesForm.spec.tsx
Removes jest.mock('tinykeys', ...) from specs; adjusts relative imports for test mocks; minor test-data tweaks.
Allowed-agents & agent derivation
apps/meteor/.../hooks/useAllowedAgents.ts, .../hooks/useAllowedAgents.spec.ts, .../utils/getAgentDerivedFromUser.ts, .../utils/getAgentDerivedFromUser.spec.ts
Adds useAllowedAgents hook and getAgentDerivedFromUser utility with tests covering permission and derivation scenarios.

Sequence Diagram(s)

sequenceDiagram
  actor User
  participant MessageForm
  participant TemplateField
  participant TemplatePreview
  participant Caller

  User->>MessageForm: Open form
  MessageForm->>TemplateField: Render(control, templates, onChange)
  User->>TemplateField: Select template
  TemplateField-->>MessageForm: update templateId (form state)
  MessageForm->>TemplatePreview: Render preview for selected template
  User->>MessageForm: Submit
  MessageForm->>MessageForm: handleSubmit -> validate template exists
  alt template found
    MessageForm-->>Caller: onSubmit({ templateId, templateParameters, template })
  else template missing
    MessageForm-->>User: show toast error
  end
Loading
sequenceDiagram
  actor User
  participant RepliesForm
  participant DeptAPI as Department API
  participant AgentsAPI as Agents API
  participant Caller

  User->>RepliesForm: Open form
  RepliesForm->>DeptAPI: fetch department (by departmentId)
  DeptAPI-->>RepliesForm: department data / error
  RepliesForm->>AgentsAPI: fetch agents (permission-gated)
  AgentsAPI-->>RepliesForm: agents list
  User->>RepliesForm: Select Department / Agent
  User->>RepliesForm: Submit
  RepliesForm->>RepliesForm: validate department & agent exist
  alt Valid
    RepliesForm-->>Caller: onSubmit({ departmentId/department, agentId/agent })
  else Not found
    RepliesForm-->>User: set field error (retry available for dept)
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

stat: ready to merge, stat: QA assured

Suggested reviewers

  • lucas-a-pelegrino
  • KevLehman

Poem

"A rabbit hopped in, no tinykeys to bind,
Templates now chosen with a clearer mind.
Replies split to fields, agents and dept in line,
Hooks and tests added, behavior refined.
Hooray — forms submit smoothly, carrot-cake time!" 🐇🥕

Pre-merge checks and finishing touches

❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Linked Issues Check ❓ Inconclusive The PR links issue CTZ-186 (UI Implementation) and the changes are consistent with a UI-focused refactor (extracted TemplateField and RepliesForm components, removal of useFormKeyboardSubmit, test relocations), but the linked_issues payload contains only the issue key without acceptance criteria or detailed requirements, so I cannot conclusively verify full compliance with CTZ-186. Please attach CTZ-186's acceptance criteria or provide a brief mapping of the issue requirements to the changed files (which files satisfy which acceptance items) so I can re-evaluate compliance against the issue.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "refactor: Outbound message forms" is concise, a single sentence, and accurately summarizes the primary change set (refactoring outbound message form components by extracting fields, removing the keyboard-submit shortcut, and relocating tests), so it clearly communicates the main developer intent.
Out of Scope Changes Check ✅ Passed All modifications are confined to the OutboundMessage wizard forms area (MessageForm, RecipientForm, RepliesForm and related components/hooks/tests) and directly relate to the stated refactor objectives; I found no unrelated files or modules modified that would indicate out-of-scope changes.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor/outbound-forms

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

@codecov
Copy link

codecov bot commented Sep 17, 2025

Codecov Report

❌ Patch coverage is 98.42697% with 7 lines in your changes missing coverage. Please review.
✅ Project coverage is 66.24%. Comparing base (b76d99b) to head (9c7b622).
⚠️ Report is 1 commits behind head on develop.

Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff             @@
##           develop   #36972      +/-   ##
===========================================
+ Coverage    66.21%   66.24%   +0.03%     
===========================================
  Files         3384     3389       +5     
  Lines       115027   115187     +160     
  Branches     21069    21009      -60     
===========================================
+ Hits         76161    76304     +143     
- Misses       36261    36277      +16     
- Partials      2605     2606       +1     
Flag Coverage Δ
e2e 56.95% <6.66%> (-0.01%) ⬇️
unit 71.23% <98.42%> (+0.03%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Contributor

@coderabbitai coderabbitai bot left a 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 (15)
apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RepliesForm/components/AgentField.tsx (4)

11-11: Import type from the module barrel to avoid brittle paths

Prefer importing RepliesFormData from the RepliesForm barrel to prevent coupling to an implementation file and reduce churn if files move.

Apply:

-import type { RepliesFormData } from '../RepliesForm';
+import type { RepliesFormData } from '..';

27-30: Guard self‑only assignment when userId is null

If canAssignSelfOnly is true but useUserId() returns null, the field is enabled with an empty list. Disable in that case.

Apply:

-const canAssignAgent = canAssignSelfOnly || canAssignAny;
+const canAssignAgent = Boolean(canAssignAny) || (Boolean(canAssignSelfOnly) && !!userId);

31-37: Propagate RHF onBlur/ref to ensure touched state and focus work

Wire onBlur (and ref if the component forwards it).

Apply:

 				<AutoCompleteAgent
 					name={agentField.name}
+					onBlur={agentField.onBlur}
 					aria-busy={isLoading}
 					aria-invalid={!!agentFieldError}
@@
-					value={agentField.value}
-					onChange={agentField.onChange}
+					value={agentField.value}
+					onChange={agentField.onChange}
+					ref={agentField.ref} // if AutoCompleteAgent forwards refs; otherwise switch to inputRef prop if available
 				/>

Please confirm whether AutoCompleteDepartmentAgent forwards ref or exposes an inputRef prop.

Also applies to: 56-58


4-4: Avoid recomputing filtered agents on every render

Memoize allowedAgents to prevent unnecessary filtering when agents is large.

Apply:

-import { useId } from 'react';
+import { useId, useMemo } from 'react';
@@
-const allowedAgents = canAssignAny ? agents : agents.filter((agent) => agent.agentId === userId);
+const allowedAgents = useMemo(
+	() => (canAssignAny ? agents : agents.filter((agent) => agent.agentId === userId)),
+	[agents, canAssignAny, userId],
+);

Also applies to: 29-30

apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RepliesForm/RepliesForm.spec.tsx (1)

138-150: Re‑enable or document skipped tests.

Both “default values” and “submit with correct values” tests are skipped. Either fix and enable them or add a note pointing to a tracking issue.

Apply to re‑enable:

-xit('should render with default values', async () => {
+it('should render with default values', async () => {
-xit('should call submit with correct values when form is submitted', async () => {
+it('should call submit with correct values when form is submitted', async () => {

Also applies to: 185-204

apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RepliesForm/index.ts (1)

1-2: Barrel export looks good.

Optional: if you only intend to re‑export types, prefer export type * to avoid re‑exporting runtime values. Non‑blocking.

-export * from './RepliesForm';
+export type * from './RepliesForm';
apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RepliesForm/components/DepartmentField.tsx (2)

13-20: Avoid onChange naming collision; make external handler optional.

Rename to reduce confusion with RHF’s onChange, and default to a no‑op.

 type DepartmentFieldProps = {
   control: Control<RepliesFormData>;
   onlyMyDepartments?: boolean;
   isError: boolean;
   isFetching: boolean;
   onRefetch: () => void;
-  onChange: () => void;
+  onDepartmentChange?: () => void;
 };
 
 const DepartmentField = ({
   control,
   onlyMyDepartments,
   isError,
   isFetching,
   onRefetch,
-  onChange: onChangeExternal,
+  onDepartmentChange: onChangeExternal = () => {},
 }: DepartmentFieldProps) => {

Also applies to: 28-29


55-68: Consider exposing loading state to AT.

Expose aria-busy while fetching to communicate loading to assistive tech. Minor.

 				<AutoCompleteDepartment
 					name={departmentField.name}
 					aria-invalid={!!departmentFieldError}
 					aria-labelledby={departmentFieldId}
+					aria-busy={isFetching}
 					aria-describedby={cxp(departmentFieldId, {
 						error: !!departmentFieldError,
 						hint: true,
 					})}
apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RepliesForm/RepliesForm.tsx (4)

70-73: Stabilize/queryKey typing when departmentId is unset.

omnichannelQueryKeys.department expects a string; departmentId may be undefined. Coerce to an empty string to satisfy typing and keep a stable key shape.

-		queryKey: omnichannelQueryKeys.department(departmentId),
+		queryKey: omnichannelQueryKeys.department(departmentId ?? ''),

112-117: Clear agent errors when department changes.

You reset agentId; also clear potential validation errors to avoid a stale error state.

 				<DepartmentField
 					control={control}
 					onlyMyDepartments={!canAssignAllDepartments}
 					isError={isErrorDepartment}
 					isFetching={isFetchingDepartment}
 					onRefetch={refetchDepartment}
-					onChange={() => setValue('agentId', '')}
+					onChange={() => {
+						setValue('agentId', '');
+						clearErrors('agentId');
+					}}
 				/>

96-104: Async onSubmit handler support (optional).

If callers pass an async onSubmit, you may want to await it to surface errors consistently.

-			onSubmit({ departmentId, department: updatedDepartment, agentId, agent });
+			await Promise.resolve(onSubmit({ departmentId, department: updatedDepartment, agentId, agent }));

29-31: Remove unused exported RepliesFormRef OR implement an imperative ref API

RepliesFormRef is declared and exported in apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RepliesForm/RepliesForm.tsx but not referenced anywhere else in the repo — either remove the exported type or implement forwardRef + useImperativeHandle to expose submit.

Suggested removal diff (still valid):

-export type RepliesFormRef = {
-  submit: () => Promise<RepliesFormSubmitPayload>;
-};
apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/MessageForm/MessageForm.tsx (1)

83-84: Resetting templateParameters should mark dirty and re‑validate.

When the template changes, also trigger dirty/validation to clear any stale errors for placeholders.

Apply this diff:

-                <TemplateField control={control} templates={templates} onChange={() => setValue('templateParameters', {})} />
+                <TemplateField
+                  control={control}
+                  templates={templates}
+                  onChange={() => setValue('templateParameters', {}, { shouldDirty: true, shouldValidate: true })}
+                />
apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/MessageForm/components/TemplateField.tsx (2)

55-66: Disable the select when no templates are available.

Prevents users from interacting with an empty selector and matches the “No_templates_available” validation.

 				<TemplateSelect
 					aria-labelledby={templateFieldId}
 					aria-invalid={!!templateFieldError}
 					aria-describedby={cxp(templateFieldId, {
 						error: !!templateFieldError,
 						hint: true,
 					})}
+					disabled={!templates?.length}
 					placeholder={t('Select_template')}
 					error={templateFieldError?.message}
 					templates={templates || []}
 					value={templateField.value}
 					onChange={handleTemplateChange}
 				/>

55-77: Prevent duplicate screen-reader error announcements

TemplateSelectProps = Omit<ComponentProps, 'value' | 'onChange' | 'options'> — TemplateSelect forwards SelectFiltered props, so passing templateFieldError?.message into TemplateSelect will forward the message into SelectFiltered and can cause the error to be announced twice. Two options: (A) pass a boolean to TemplateSelect (styling only) and keep FieldError as the single SR message, or (B) keep the message but set aria-describedby to the hint only and wire aria-errormessage to the FieldError id.

 				<TemplateSelect
 					aria-labelledby={templateFieldId}
 					aria-invalid={!!templateFieldError}
-					aria-describedby={cxp(templateFieldId, {
-						error: !!templateFieldError,
-						hint: true,
-					})}
+					aria-describedby={`${templateFieldId}-hint`}
+					aria-errormessage={templateFieldError ? `${templateFieldId}-error` : undefined}
 					placeholder={t('Select_template')}
-					error={templateFieldError?.message}
+					# If TemplateSelect supports boolean `error`, prefer boolean to avoid duplicate SR text:
+					error={!!templateFieldError}
 					templates={templates || []}
 					value={templateField.value}
 					onChange={handleTemplateChange}
 				/>
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between b1cefb0 and 13923a9.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (12)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/MessageForm/MessageForm.spec.tsx (0 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/MessageForm/MessageForm.tsx (3 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/MessageForm/components/TemplateField.tsx (1 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RecipientForm/RecipientForm.spec.tsx (1 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RecipientForm/RecipientForm.tsx (1 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RepliesForm.tsx (0 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RepliesForm/RepliesForm.spec.tsx (1 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RepliesForm/RepliesForm.tsx (1 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RepliesForm/components/AgentField.tsx (1 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RepliesForm/components/DepartmentField.tsx (1 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RepliesForm/index.ts (1 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/hooks/useFormKeyboardSubmit.tsx (0 hunks)
💤 Files with no reviewable changes (3)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/MessageForm/MessageForm.spec.tsx
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/hooks/useFormKeyboardSubmit.tsx
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RepliesForm.tsx
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: aleksandernsilva
PR: RocketChat/Rocket.Chat#36974
File: apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/MessageForm/MessageForm.tsx:124-129
Timestamp: 2025-09-18T17:32:33.951Z
Learning: The ARIA mismatch issue in MessageForm's template field (where FieldError id used templateId instead of messageFormId) was addressed in PR #36972 through refactoring the template field into a separate TemplateField component, which uses consistent templateFieldId for both aria-describedby and FieldError id.
📚 Learning: 2025-09-18T17:32:33.951Z
Learnt from: aleksandernsilva
PR: RocketChat/Rocket.Chat#36974
File: apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/MessageForm/MessageForm.tsx:124-129
Timestamp: 2025-09-18T17:32:33.951Z
Learning: The ARIA mismatch issue in MessageForm's template field (where FieldError id used templateId instead of messageFormId) was addressed in PR #36972 through refactoring the template field into a separate TemplateField component, which uses consistent templateFieldId for both aria-describedby and FieldError id.

Applied to files:

  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/MessageForm/components/TemplateField.tsx
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/MessageForm/MessageForm.tsx
🧬 Code graph analysis (4)
apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RepliesForm/RepliesForm.tsx (3)
packages/core-typings/src/ILivechatDepartment.ts (1)
  • ILivechatDepartment (1-24)
apps/meteor/client/lib/queryKeys.ts (1)
  • omnichannelQueryKeys (30-82)
apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/utils/errors.ts (1)
  • FormFetchError (7-11)
apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RepliesForm/components/DepartmentField.tsx (2)
apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RepliesForm/RepliesForm.tsx (1)
  • RepliesFormData (17-20)
apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/utils/cx.ts (1)
  • cxp (29-34)
apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/MessageForm/components/TemplateField.tsx (3)
apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/MessageForm/MessageForm.tsx (1)
  • MessageFormData (17-20)
apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/utils/cx.ts (1)
  • cxp (29-34)
apps/meteor/client/components/Omnichannel/OutboundMessage/constants.ts (1)
  • OUTBOUND_DOCS_LINK (1-1)
apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RepliesForm/components/AgentField.tsx (2)
apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RepliesForm/RepliesForm.tsx (1)
  • RepliesFormData (17-20)
apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/utils/cx.ts (1)
  • cxp (29-34)
⏰ 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). (4)
  • GitHub Check: 🔨 Test Unit / Unit Tests
  • GitHub Check: 🔎 Code Check / TypeScript
  • GitHub Check: 🔎 Code Check / Code Lint
  • GitHub Check: 📦 Meteor Build - coverage
🔇 Additional comments (11)
apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RepliesForm/components/AgentField.tsx (3)

47-50: A11y wiring looks solid

aria-describedby correctly references both error and hint IDs; FieldError uses aria-live='assertive'. Nice alignment with the recent ARIA fix pattern.

If you want, I can run a quick pass to confirm no duplicate IDs are generated across the form.

Also applies to: 61-67


56-58: Resolved — AutoCompleteDepartmentAgent value/onChange match RHF (agentId: string)

AutoCompleteDepartmentAgentProps declares value: string and onChange(value: string); option values are set to agent.agentId — passing agentField.value and agentField.onChange is correct.


54-54: Do not change the i18n keys — the current keys exist and are correct.
AgentField.tsx (line 54) uses t('Loading...') and t('Select_agent'); packages/i18n/src/locales/en.i18n.json contains "Loading..." and "Select_agent", so the suggested replacement is incorrect.

Likely an incorrect or invalid review comment.

apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RepliesForm/RepliesForm.spec.tsx (2)

229-238: Good a11y assertion on permission messaging.

Verifies the accessible description when lacking assign permissions; this protects screen reader UX.


10-10: Import path update LGTM; unable to verify tinykeys removal — run local search

Ripgrep in the sandbox skipped files ("No files were searched"); I couldn't confirm absence of tinykeys/useFormKeyboardSubmit. Run locally:

rg -n --hidden -S -g '!/node_modules/' -g '!/dist/' -g '!/build/' "tinykeys|useFormKeyboardSubmit" -C1

File: apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RepliesForm/RepliesForm.spec.tsx

apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RepliesForm/components/DepartmentField.tsx (2)

53-77: A11y wiring is solid and consistent.

Label id, describedby tokens, and error/hint ids are coherent; this fixes common ARIA mismatches.


40-41: i18n keys verified — no change needed.

Both keys exist: "department" and "Outbound_message_department_hint" are defined in packages/i18n/src/locales/en.i18n.json (Outbound_message_department_hint also present in packages/i18n/src/locales/pt-BR.i18n.json).

apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/MessageForm/MessageForm.tsx (1)

81-99: Switch to native form submit: LGTM.

Standardizing on <form onSubmit={handleSubmit(submit)}> removes custom keyboard wiring and is clearer.

apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/MessageForm/components/TemplateField.tsx (1)

24-41: Validation order and messages: LGTM.

Good sequencing: availability → selected‑id existence → required. Reads well and avoids confusing “required” when nothing is available.

apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RecipientForm/RecipientForm.tsx (1)

178-214: Switch to native form submit — leftover tinykeys usages found

Search shows tinykeys still imported/used and listed as a dependency; tests also mock it. Key locations: apps/meteor/package.json, yarn.lock, apps/meteor/client/views/admin/subscription/SubscriptionPage.tsx, apps/meteor/client/sidebar/hooks/useShortcutOpenMenu.ts, apps/meteor/client/sidebarv2/hooks/useShortcutOpenMenu.ts, apps/meteor/client/sidebar/search/SearchList.tsx, apps/meteor/client/sidebar/header/actions/Search.tsx, apps/meteor/client/NavBarV2/NavBarSearch/NavBarSearch.tsx, and tests: apps/meteor/client/components/Omnichannel/OutboundMessage/.../RecipientStep.spec.tsx, MessageStep.spec.tsx, OutboundMessageWizard.spec.tsx. Either remove these imports/mocks and the dependency if tinykeys is no longer needed, or confirm they’re used for other shortcuts and keep them.

apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RecipientForm/RecipientForm.spec.tsx (1)

8-9: Import path updates: LGTM — verify no remaining old mocks imports
Automated sandbox search didn't scan files; run locally to confirm no remaining imports:
rg -n --no-ignore -S -F "tests/mocks/data"

@aleksandernsilva aleksandernsilva added this to the 7.11.0 milestone Sep 18, 2025
tassoevan
tassoevan previously approved these changes Sep 19, 2025
…36989)

Co-authored-by: Tasso <tasso.evangelista@rocket.chat>
@tassoevan tassoevan added the stat: QA assured Means it has been tested and approved by a company insider label Sep 19, 2025
@dionisio-bot dionisio-bot bot added the stat: ready to merge PR tested and approved waiting for merge label Sep 19, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (5)
apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RepliesForm/components/AgentField.tsx (2)

24-31: Propagate onBlur to RHF to set touched/trigger validations

useController provides onBlur; not wiring it can break onBlur validation modes and touched state.

 					value={agentField.value}
 					onChange={agentField.onChange}
+					onBlur={agentField.onBlur}

8-8: Naming nit: align import with module name

The file suggests “AutoCompleteDepartmentAgent” but is imported as AutoCompleteAgent. Aligning names avoids confusion in future refactors.

-import AutoCompleteAgent from '../../../../AutoCompleteDepartmentAgent';
+import AutoCompleteDepartmentAgent from '../../../../AutoCompleteDepartmentAgent';

And update usage:

-				<AutoCompleteAgent
+				<AutoCompleteDepartmentAgent
apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RepliesForm/utils/getAgentDerivedFromUser.ts (1)

3-3: Consider a more descriptive type guard function name.

The type guard isOmnichannelAgent checks if a user has the 'livechat-agent' role, but the function name could be more explicit about what it's checking.

-const isOmnichannelAgent = (user: IUser | null): user is IUser => (user ? user.roles.includes('livechat-agent') : false);
+const hasLivechatAgentRole = (user: IUser | null): user is IUser => (user ? user.roles.includes('livechat-agent') : false);
apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RepliesForm/utils/getAgentDerivedFromUser.spec.ts (1)

4-4: Consider using a relative import for better maintainability.

The import path for createFakeUser is quite long and brittle to directory structure changes.

-import { createFakeUser } from '../../../../../../../../../tests/mocks/data';
+import { createFakeUser } from '@meteor-tests/mocks/data';

Note: This assumes a path alias is configured for test mocks. If not available, the current import is acceptable.

apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RepliesForm/hooks/useAllowedAgents.spec.ts (1)

20-41: Consider extracting mock data to reduce duplication.

The mock data definitions are quite verbose and could be simplified or extracted to reduce test file size.

const createMockAgent = (overrides: Partial<Serialized<ILivechatDepartmentAgents>> = {}) => ({
  agentId: 'agent1',
  username: 'agent.one',
  count: 1,
  order: 1,
  _id: 'agent1',
  _updatedAt: new Date().toISOString(),
  departmentEnabled: true,
  departmentId: 'dept1',
  ...overrides,
});

const mockQueryAgents = [
  createMockAgent(),
  createMockAgent({ agentId: 'agent2', username: 'agent.two', _id: 'agent2', count: 2, order: 2 }),
];

Also applies to: 43-52

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 13923a9 and d553243.

📒 Files selected for processing (7)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RepliesForm/RepliesForm.spec.tsx (3 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RepliesForm/RepliesForm.tsx (1 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RepliesForm/components/AgentField.tsx (1 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RepliesForm/hooks/useAllowedAgents.spec.ts (1 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RepliesForm/hooks/useAllowedAgents.ts (1 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RepliesForm/utils/getAgentDerivedFromUser.spec.ts (1 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RepliesForm/utils/getAgentDerivedFromUser.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RepliesForm/RepliesForm.spec.tsx
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RepliesForm/RepliesForm.tsx
🧰 Additional context used
🧬 Code graph analysis (5)
apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RepliesForm/utils/getAgentDerivedFromUser.ts (1)
packages/core-typings/src/IUser.ts (1)
  • IUser (186-252)
apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RepliesForm/utils/getAgentDerivedFromUser.spec.ts (2)
apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RepliesForm/utils/getAgentDerivedFromUser.ts (1)
  • getAgentDerivedFromUser (5-20)
apps/meteor/tests/mocks/data.ts (1)
  • createFakeUser (32-44)
apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RepliesForm/hooks/useAllowedAgents.spec.ts (3)
apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RepliesForm/utils/getAgentDerivedFromUser.ts (1)
  • getAgentDerivedFromUser (5-20)
apps/meteor/tests/mocks/data.ts (1)
  • createFakeUser (32-44)
apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RepliesForm/hooks/useAllowedAgents.ts (1)
  • useAllowedAgents (14-37)
apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RepliesForm/components/AgentField.tsx (2)
apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RepliesForm/RepliesForm.tsx (1)
  • RepliesFormData (18-21)
apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/utils/cx.ts (1)
  • cxp (29-34)
apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RepliesForm/hooks/useAllowedAgents.ts (2)
packages/core-typings/src/IUser.ts (1)
  • IUser (186-252)
apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RepliesForm/utils/getAgentDerivedFromUser.ts (1)
  • getAgentDerivedFromUser (5-20)
⏰ 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). (3)
  • GitHub Check: 📦 Build Packages
  • GitHub Check: CodeQL-Build
  • GitHub Check: CodeQL-Build
🔇 Additional comments (8)
apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RepliesForm/utils/getAgentDerivedFromUser.ts (2)

5-20: LGTM! Clean implementation with proper error handling.

The function correctly validates the user's livechat agent role and constructs a well-structured agent object. The error message is clear and the return type matches the expected interface.


14-14: Verify _updatedAt timestamp format consistency

apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RepliesForm/utils/getAgentDerivedFromUser.ts uses:
_updatedAt: new Date().toISOString(),
Confirm the rest of the codebase and DB expect ISO strings for _updatedAt (not Date objects or epoch millis). If not consistent, standardize or add conversions at read/write boundaries. Automated repo search in the sandbox returned "No files were searched" — manual verification required.

apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RepliesForm/utils/getAgentDerivedFromUser.spec.ts (1)

6-39: Comprehensive test coverage with clear test cases.

The tests cover all the essential scenarios:

  • Null user validation
  • Non-livechat agent validation
  • Successful agent derivation with proper object structure verification

The use of expect.any(String) for the timestamp is appropriate since the exact value is non-deterministic.

apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RepliesForm/hooks/useAllowedAgents.spec.ts (2)

8-12: LGTM! Proper mocking setup for isolated unit testing.

The mock implementation correctly isolates the getAgentDerivedFromUser dependency and provides a typed mock for better test control.


54-169: Excellent test coverage of all hook scenarios.

The test suite comprehensively covers:

  • Empty department ID handling
  • Permission-based filtering logic
  • Query agents vs derived agents logic
  • Error handling with safe fallback

Each test case is well-structured with clear setup, execution, and assertions. The use of renderHook is appropriate for testing this custom hook.

apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RepliesForm/hooks/useAllowedAgents.ts (3)

6-12: Well-defined TypeScript interface.

The UseAllowedAgentsProps type clearly defines all required parameters with appropriate nullability and optional markers.


14-37: Clean and logical hook implementation.

The hook efficiently handles all business logic scenarios:

  • Early returns for invalid states
  • Priority-based agent selection logic
  • Safe error handling with fallback

The dependency array is complete and the memoization will prevent unnecessary recalculations.


26-29: Verify agent selection priority & constraints

File: apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RepliesForm/hooks/useAllowedAgents.ts (lines 26–29)

		// can assign any agent, return all agents from query (if any)
		if (canAssignAnyAgent && queryAgents?.length) {
			return queryAgents;
		}
  • Ensure queryAgents is filtered before return: exclude offline agents, agents in disabled departments, and agents not in the target department; enforce agent concurrent-chat / global limits.
  • Confirm the check uses the correct transfer permission (Transfer Livechat Guests) so only authorized users can manually assign.
  • Confirm derived-agent logic (contact manager / last-chatted / routing / auto-assignment) is a fallback when queryAgents is empty — unless workspace routing/contact-manager settings explicitly require derived agents to take priority; adjust priority accordingly.

@aleksandernsilva aleksandernsilva removed the stat: QA assured Means it has been tested and approved by a company insider label Sep 19, 2025
@dionisio-bot dionisio-bot bot removed the stat: ready to merge PR tested and approved waiting for merge label Sep 19, 2025
@aleksandernsilva aleksandernsilva added the stat: QA assured Means it has been tested and approved by a company insider label Sep 19, 2025
@dionisio-bot dionisio-bot bot added the stat: ready to merge PR tested and approved waiting for merge label Sep 19, 2025
@kodiakhq kodiakhq bot merged commit 7f53726 into develop Sep 19, 2025
85 of 87 checks passed
@kodiakhq kodiakhq bot deleted the refactor/outbound-forms branch September 19, 2025 15:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

stat: QA assured Means it has been tested and approved by a company insider stat: ready to merge PR tested and approved waiting for merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants