Skip to content

Conversation

@aleksandernsilva
Copy link
Contributor

@aleksandernsilva aleksandernsilva commented Sep 18, 2025

Proposed changes (including videos or screenshots)

Screenshot 2025-09-18 at 16 40 26

This PR adds fallbacks so agents are be able to assign an outbound message reply to themselves according to the outbound permissions.

  • If agent doesn't have neither outbound.can-assign-self-only nor outbound.can-assign-any-agent, no agents should be available.
  • If agent does have outbound.can-assign-any-agent but doesn't have view-livechat-department, only themselves must be available.
  • If agent does have outbound.can-assign-self-only only themselves must be available.
  • All other scenarios no agents are listed.

Issue(s)

CTZ-330

Steps to test or reproduce

  • Access workspace with outbound messaging add-on
  • Create new > Outbound message
  • Fill out recipient step
  • Fill out message step
  • Select a department
  • Agent should be able to assign themselves or other agents based on the outbound permissions

Further comments

Summary by CodeRabbit

  • New Features

    • Agent selection in Outbound Message wizard is now permission- and department-aware, showing only eligible agents.
    • Unified permission handling with a single “can assign agent” flag and clearer field hints.
  • Bug Fixes

    • Prevents submission when no eligible agent is available.
    • Handles missing department or invalid user more gracefully.
  • Refactor

    • Centralized agent filtering into a dedicated hook; simplified component props.
  • Tests

    • Added unit tests for agent eligibility logic and user-to-agent derivation.
    • Updated specs to reflect permission scenarios.

@dionisio-bot
Copy link
Contributor

dionisio-bot bot commented Sep 18, 2025

Looks like this PR is not ready to merge, because of the following issues:

  • This PR is missing the 'stat: QA assured' label

Please fix the issues and try again

If you have any trouble, please check the PR guidelines

@changeset-bot
Copy link

changeset-bot bot commented Sep 18, 2025

⚠️ No Changeset found

Latest commit: c248f2c

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 18, 2025

Pre-merge checks and finishing touches

✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The pull request title "fix: Agents unable to assign outbound message replies to themselves" accurately describes the main change being implemented. The code changes introduce permission-based agent filtering logic that specifically addresses the issue where agents with self-assignment permissions couldn't assign outbound message replies to themselves. The title clearly identifies this as a bug fix and specifically mentions the core problem being solved, making it easy for teammates to understand the primary change when scanning commit history.
Linked Issues Check ✅ Passed The code changes directly address the requirements from linked issue CTZ-330. The implementation adds the missing functionality for agents to assign chats to themselves through new components like useAllowedAgents hook and getAgentDerivedFromUser utility that handle permission-based agent filtering. The changes specifically implement the fallback logic where agents with outbound.can-assign-self-only permission can assign messages to themselves, and agents with outbound.can-assign-any-agent permission but lacking department access are limited to self-assignment only. The refactored AgentField component now properly supports these assignment scenarios that were previously disabled.
Out of Scope Changes Check ✅ Passed All code changes in this pull request are directly related to fixing the agent self-assignment issue described in CTZ-330. The new useAllowedAgents hook, getAgentDerivedFromUser utility, and modifications to RepliesForm and AgentField components all work together to implement the required permission-based agent filtering logic. The changes to test files provide coverage for the new functionality, and the prop signature changes in AgentField simplify the API while maintaining the required functionality. No changes appear to be out of scope or unrelated to the core objective of enabling agents to assign outbound message replies to themselves.
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 fix/outbound-agent-assign

📜 Recent 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 75d517a and c248f2c.

📒 Files selected for processing (2)
  • 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)
🧰 Additional context used
🧬 Code graph analysis (2)
apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RepliesForm/hooks/useAllowedAgents.spec.ts (4)
apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RepliesForm/utils/getAgentDerivedFromUser.ts (1)
  • getAgentDerivedFromUser (5-20)
packages/core-typings/src/IUser.ts (1)
  • IUser (186-252)
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/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). (7)
  • GitHub Check: 🔨 Test Unit / Unit Tests
  • GitHub Check: 🔨 Test Storybook / Test Storybook
  • GitHub Check: 🔎 Code Check / TypeScript
  • GitHub Check: 🔎 Code Check / Code Lint
  • GitHub Check: 📦 Meteor Build - coverage
  • GitHub Check: CodeQL-Build
  • GitHub Check: CodeQL-Build
🔇 Additional comments (16)
apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RepliesForm/hooks/useAllowedAgents.spec.ts (10)

1-12: LGTM!

The imports are correctly structured and the mocking setup is properly configured.


14-18: LGTM!

The mock user setup includes the necessary livechat-agent role which aligns with the isOmnichannelAgent check in getAgentDerivedFromUser.


20-52: LGTM!

The mock data structures are well-defined and provide comprehensive test fixtures for both query agents and derived agents.


59-71: LGTM!

This test correctly verifies the early return condition when no department is selected.


73-85: LGTM!

This test properly validates the permission-gating logic when the user has neither assignment permission.


87-99: LGTM!

This test correctly verifies the "assign any agent" scenario with available query agents.


101-116: LGTM!

This test properly validates the "self-only" assignment scenario and verifies the correct parameters are passed to getAgentDerivedFromUser.


118-133: LGTM!

This test correctly covers the fallback scenario when canAssignAnyAgent is true but no query agents are available.


135-150: LGTM!

This test properly covers the edge case with undefined query agents, ensuring consistent behavior.


152-168: LGTM!

This test correctly verifies the error handling when getAgentDerivedFromUser throws an exception, ensuring graceful fallback behavior.

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

1-12: LGTM!

The type definition is well-structured and accurately represents the hook's parameters. The import organization is clean and appropriate.


14-19: LGTM!

The early return for missing department ID is correct and aligns with the business logic that requires a department selection.


21-24: LGTM!

This permission check correctly implements the requirement that users must have at least one assignment permission to see any agents.


26-29: LGTM!

This logic correctly prioritizes returning all available agents when the user has broad assignment permissions and agents are available from the query.


31-37: LGTM!

The fallback logic with error handling is well-implemented. The try-catch ensures graceful handling when getAgentDerivedFromUser throws (e.g., when user is not a livechat agent), returning an empty array as expected.

The dependency array for useMemo is complete and correctly includes all variables that could affect the computation.


14-37: Verify permission fallback logic aligns with PR objectives

Hook implements expected fallbacks (no perms → [], can-assign-any → queryAgents if present else self, can-assign-self-only → self). Repository search returned no matches for the permission keys or hook callers. Actions:

  • Confirm callers pass booleans derived from permissions "outbound.can-assign-self-only" and "outbound.can-assign-any-agent" to useAllowedAgents (apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RepliesForm/hooks/useAllowedAgents.ts lines 14–37).
  • If permission key names differ in the codebase, align callers or update the hook to use the actual keys.

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

@codecov
Copy link

codecov bot commented Sep 18, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 67.13%. Comparing base (13923a9) to head (c248f2c).
⚠️ Report is 1 commits behind head on refactor/outbound-forms.

Additional details and impacted files

Impacted file tree graph

@@                     Coverage Diff                     @@
##           refactor/outbound-forms   #36989      +/-   ##
===========================================================
- Coverage                    67.13%   67.13%   -0.01%     
===========================================================
  Files                         3407     3409       +2     
  Lines                       117870   117929      +59     
  Branches                     21473    21483      +10     
===========================================================
+ Hits                         79136    79174      +38     
- Misses                       36030    36054      +24     
+ Partials                      2704     2701       -3     
Flag Coverage Δ
e2e 56.94% <9.09%> (-0.03%) ⬇️
unit 71.88% <100.00%> (-0.01%) ⬇️

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.

@aleksandernsilva aleksandernsilva marked this pull request as ready for review September 18, 2025 22:44
@aleksandernsilva aleksandernsilva requested a review from a team as a code owner September 18, 2025 22:44
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 (4)
apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RepliesForm/components/AgentField.tsx (1)

48-48: Consider adding null check for edge cases.

While the logic is correct, consider whether !canAssignAgent should be explicitly checked as canAssignAgent === false to handle potential undefined values more predictably.

-					disabled={disabled || isLoading || !canAssignAgent}
+					disabled={disabled || isLoading || canAssignAgent !== true}
apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RepliesForm/utils/getAgentDerivedFromUser.ts (1)

14-14: Consider using a stable timestamp for testing.

Using new Date().toISOString() creates non-deterministic values that could make testing harder. Consider accepting an optional timestamp parameter or using a factory pattern for better testability.

-export const getAgentDerivedFromUser = (user: IUser | null, departmentId: string): Serialized<ILivechatDepartmentAgents> => {
+export const getAgentDerivedFromUser = (user: IUser | null, departmentId: string, timestamp?: string): Serialized<ILivechatDepartmentAgents> => {
 	if (!isOmnichannelAgent(user)) {
 		throw new Error('User is not a livechat agent');
 	}
 
 	return {
 		agentId: user._id,
 		username: user.username || '',
 		_id: user._id,
-		_updatedAt: new Date().toISOString(),
+		_updatedAt: timestamp || new Date().toISOString(),
 		departmentId,
 		departmentEnabled: true,
 		count: 0,
 		order: 0,
 	};
 };
apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RepliesForm/hooks/useAllowedAgents.tsx (1)

34-36: Silent error handling might hide issues.

The catch block silently returns an empty array, which could mask legitimate errors. Consider logging errors for debugging purposes while maintaining the graceful fallback.

 		} catch {
+			// Log error for debugging while gracefully falling back
+			console.warn('Failed to derive allowed agents:', error);
 			return [];
 		}
apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RepliesForm/RepliesForm.tsx (1)

92-92: Potential undefined reference in agent lookup.

The agents variable could be undefined if useAllowedAgents returns undefined. While the hook implementation suggests it always returns an array, consider adding a defensive check.

-			const agent = agents?.find((agent) => agent.agentId === agentId);
+			const agent = agents?.find((agent) => agent.agentId === agentId);
+			// Or more defensively:
+			// const agent = (agents || []).find((agent) => agent.agentId === agentId);
📜 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 75d517a.

📒 Files selected for processing (7)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RepliesForm/RepliesForm.spec.tsx (2 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RepliesForm/RepliesForm.tsx (6 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RepliesForm/components/AgentField.tsx (2 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RepliesForm/hooks/useAllowedAgents.spec.tsx (1 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RepliesForm/hooks/useAllowedAgents.tsx (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)
🧰 Additional context used
🧬 Code graph analysis (5)
apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RepliesForm/hooks/useAllowedAgents.spec.tsx (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.tsx (1)
  • useAllowedAgents (14-38)
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 (3)
apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RepliesForm/utils/getAgentDerivedFromUser.ts (1)
  • getAgentDerivedFromUser (5-20)
packages/core-typings/src/IUser.ts (1)
  • IUser (186-252)
apps/meteor/tests/mocks/data.ts (1)
  • createFakeUser (32-44)
apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RepliesForm/hooks/useAllowedAgents.tsx (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)
apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RepliesForm/RepliesForm.tsx (1)
apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RepliesForm/hooks/useAllowedAgents.tsx (1)
  • useAllowedAgents (14-38)
🔇 Additional comments (9)
apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RepliesForm/RepliesForm.spec.tsx (2)

21-21: LGTM! Role addition aligns with permission requirements.

Adding the 'livechat-agent' role to the mock user ensures consistency with the new agent validation logic that requires users to have this role.


222-222: Confirmed — appRoot([]) intentionally removes permissions and matches the test intent.

appRoot is defined to accept a permissions array (defaults to ['outbound.can-assign-self-only','outbound.can-assign-any-agent']); passing [] clears permissions, so using appRoot([]).build() for tests that assert agent-not-found / agent field disabled is correct.

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

1-39: Well-structured test coverage for agent derivation utility.

The tests comprehensively cover all edge cases: null user, non-livechat-agent user, and valid livechat-agent user. The assertions correctly validate the expected error messages and object structure.

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

13-20: Good API simplification with unified permission flag.

Consolidating canAssignAny and canAssignSelfOnly into a single canAssignAgent prop simplifies the component interface and makes the permission logic clearer.

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

3-3: Type guard correctly validates livechat-agent role.

The isOmnichannelAgent function properly checks for null users and validates the required role.

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

16-36: Well-structured permission logic with clear fallback patterns.

The hook correctly implements the fallback rules as described in the PR objectives. The logic flow is clear and handles all permission scenarios appropriately.


28-30: Verify empty queryAgents edge case.

The condition canAssignAnyAgent && queryAgents?.length is false when queryAgents is an empty array, causing fall-through to the derived-agent path. Confirm whether agents with canAssignAnyAgent but no department agents should be allowed to assign to themselves.

			if (canAssignAnyAgent && queryAgents?.length) {
				return queryAgents;
			}
apps/meteor/client/components/Omnichannel/OutboundMessage/components/OutboundMessageWizard/forms/RepliesForm/hooks/useAllowedAgents.spec.tsx (1)

1-169: Comprehensive test coverage with proper mocking.

The test suite thoroughly covers all permission scenarios and edge cases, including error handling. The mock setup and assertions are well-structured.

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

77-83: Clean integration of user-aware agent selection.

The integration of useAllowedAgents hook properly centralizes the agent filtering logic based on user permissions and department selection.

@aleksandernsilva aleksandernsilva added this to the 7.11.0 milestone Sep 18, 2025
@tassoevan tassoevan merged commit d553243 into refactor/outbound-forms Sep 19, 2025
42 checks passed
@tassoevan tassoevan deleted the fix/outbound-agent-assign branch September 19, 2025 04:50
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.

3 participants