Skip to content

Conversation

@aleksandernsilva
Copy link
Contributor

@aleksandernsilva aleksandernsilva commented Sep 12, 2025

Proposed changes (including videos or screenshots)

Issue(s)

CTZ-334
CTZ-333

Steps to test or reproduce

Further comments

Summary by CodeRabbit

  • New Features

    • Updated Outbound Message upsell modal to reflect admin vs. non-admin experiences.
    • Community editions now show an “Upgrade” button (instead of “Contact sales”) for admins.
    • Non-admins see only “Learn more,” with contextual annotation; admins don’t see the annotation.
    • “Learn more” opens documentation; “Contact sales” opens the sales page where applicable.
  • Tests

    • Expanded test coverage for admin/non-admin and community/non-community scenarios, including button visibility, annotations, and link behaviors.

@dionisio-bot
Copy link
Contributor

dionisio-bot bot commented Sep 12, 2025

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

  • This PR is missing the 'stat: QA assured' label
  • This PR is missing the required milestone or project

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

⚠️ No Changeset found

Latest commit: 29e7de7

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

Walkthrough

Extends OutboundMessageUpsellModal API with optional isCommunity and refines admin gating. Hook now derives isCommunity from license state and passes it to the modal. Tests are reorganized to cover admin vs non-admin and community vs non-community scenarios, updating expected buttons, annotations, and external-link behaviors.

Changes

Cohort / File(s) Summary
Modal component API and logic
apps/meteor/client/components/Omnichannel/OutboundMessage/modals/OutboundMessageUpsellModal/OutboundMessageUpsellModal.tsx
Adds optional isCommunity prop; admin gating changed to isAdmin && !hasModule; confirm button text switches between “Upgrade” (community) and “Contact_sales” (non-community); updates memo deps to include isCommunity.
Hook integration with license
apps/meteor/client/components/Omnichannel/OutboundMessage/modals/OutboundMessageUpsellModal/useOutboundMessageUpsellModal.tsx
Imports and uses useLicense; computes isCommunity as !license.data?.license; passes isCommunity, isAdmin, and hasModule to modal; open/close mechanics unchanged.
Test suite expansion and restructuring
apps/meteor/client/components/Omnichannel/OutboundMessage/modals/OutboundMessageUpsellModal/OutboundMessageUpsellModal.spec.tsx
Splits scenarios by hasModule, isAdmin, and isCommunity; adjusts expectations for visible buttons and annotation; updates click handlers to open docs/sales URLs; removes obsolete “Contact sales” for non-admin; verifies “Upgrade” for community admins.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor U as User
  participant VC as View/Component
  participant H as useOutboundMessageUpsellModal
  participant L as useLicense
  participant M as OutboundMessageUpsellModal
  participant N as Navigation

  U->>VC: Triggers open()
  VC->>H: open()
  H->>L: license = useLicense()
  H->>M: setModal(<OutboundMessageUpsellModal isAdmin, hasModule, isCommunity = !license>)
  Note right of M: Renders buttons based on isAdmin/hasModule/isCommunity

  alt Admin && !hasModule
    alt isCommunity
      U->>M: Click "Upgrade"
      M->>N: Open upgrade flow
    else Non-community
      U->>M: Click "Contact sales"
      M->>N: Open sales URL
    end
  else Non-admin or hasModule
    U->>M: Click "Learn more"
    M->>N: Open docs URL
  end

  U->>M: Close modal
  M-->>H: onClose()
  H-->>VC: setModal(null)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Pre-merge checks (3 passed)

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "fix(outbound): Upsell modal conditions" is concise and accurately reflects the primary change in the PR, which updates the OutboundMessageUpsellModal gating and behavior (admin/community/license-driven condition changes), so it clearly summarizes the main intent of the changeset.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.

Poem

I twitch my ears at buttons three—
Learn, Upgrade, Sales for thee.
A license breeze decides the way,
Community moons or admin day.
With tidy tests and paths anew,
I hop through flows—reviewed, true.
🐇✨

Tip

👮 Agentic pre-merge checks are now available in preview!

Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.

  • Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
  • Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

Please see the documentation for more information.

Example:

reviews:
  pre_merge_checks:
    custom_checks:
      - name: "Undocumented Breaking Changes"
        mode: "warning"
        instructions: |
          Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).

Please share your feedback with us on this Discord post.

✨ 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-upsell-modal

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

@aleksandernsilva aleksandernsilva changed the base branch from develop to feat/outbound-msg-ui September 12, 2025 16:08
@aleksandernsilva aleksandernsilva marked this pull request as ready for review September 12, 2025 16:12
@aleksandernsilva aleksandernsilva requested a review from a team as a code owner September 12, 2025 16:12
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: 1

🧹 Nitpick comments (5)
apps/meteor/client/components/Omnichannel/OutboundMessage/modals/OutboundMessageUpsellModal/useOutboundMessageUpsellModal.tsx (2)

16-18: Avoid assuming “community” while license is still loading

If license.data is undefined at click time, !license.data?.license evaluates to true and the modal will show an “Upgrade” CTA for licensed workspaces until the hook resolves. Consider deferring modal open until license is known, or defaulting to a neutral/“Contact sales” state and/or moving the license read into the modal so it re-renders when license resolves. Please verify how useLicense() behaves here.


7-7: Small readability win: derive isCommunity once

Computing once makes the JSX cleaner and avoids double-negation in render sites.

 import { useHasLicenseModule } from '../../../../../hooks/useHasLicenseModule';
 import { useLicense } from '../../../../../hooks/useLicense';
 
 export const useOutboundMessageUpsellModal = () => {
   const setModal = useSetModal();
   const isAdmin = useRole('admin');
-  const license = useLicense();
+  const license = useLicense();
+  const isCommunity = !license.data?.license;
   const hasModule = useHasLicenseModule('outbound-messaging') === true;
 
   const close = useEffectEvent(() => setModal(null));
   const open = useEffectEvent(() =>
-    setModal(<OutboundMessageUpsellModal isCommunity={!license.data?.license} isAdmin={isAdmin} hasModule={hasModule} onClose={close} />),
+    setModal(<OutboundMessageUpsellModal isCommunity={isCommunity} isAdmin={isAdmin} hasModule={hasModule} onClose={close} />),
   );

Also applies to: 12-12, 16-18

apps/meteor/client/components/Omnichannel/OutboundMessage/modals/OutboundMessageUpsellModal/OutboundMessageUpsellModal.tsx (2)

18-18: Default optional props to avoid relying on “undefined is falsy”

This makes intent explicit and prevents accidental upsell path if a caller forgets to pass hasModule.

-const OutboundMessageUpsellModal = ({ isCommunity, hasModule, isAdmin, onClose }: OutboundMessageUpsellModalProps) => {
+const OutboundMessageUpsellModal = ({ isCommunity, hasModule = false, isAdmin = false, onClose }: OutboundMessageUpsellModalProps) => {

28-30: “Upgrade” label still opens the sales link—confirm intended behavior

When isCommunity is true, the confirm label is “Upgrade” but onConfirm navigates to the Contact Sales URL. If “Upgrade” should route to a self-serve upgrade/marketplace/billing page, update the link or rename the CTA to avoid mismatched expectations. Also consider adding a test for the “Upgrade” click target.

apps/meteor/client/components/Omnichannel/OutboundMessage/modals/OutboundMessageUpsellModal/OutboundMessageUpsellModal.spec.tsx (1)

56-61: Add click assertion for the “Upgrade” CTA

We render the button but don’t verify its action. Add a test to assert it opens the expected link.

 it('should render "Upgrade" button when isCommunity is true', () => {
   render(<OutboundMessageUpsellModal isAdmin isCommunity onClose={onClose} />, { wrapper: appRoot.build() });
   expect(screen.getByRole('button', { name: 'Upgrade' })).toBeInTheDocument();
   expect(screen.queryByRole('button', { name: 'Contact sales' })).not.toBeInTheDocument();
 });
+
+it('should call openExternalLink with sales link when "Upgrade" is clicked', async () => {
+  render(<OutboundMessageUpsellModal isAdmin isCommunity onClose={onClose} />, { wrapper: appRoot.build() });
+  await userEvent.click(screen.getByRole('button', { name: 'Upgrade' }));
+  expect(openExternalLink).toHaveBeenCalledWith('https://go.rocket.chat/i/contact-sales');
+});
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3ce7075 and c04fd49.

📒 Files selected for processing (3)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/modals/OutboundMessageUpsellModal/OutboundMessageUpsellModal.spec.tsx (1 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/modals/OutboundMessageUpsellModal/OutboundMessageUpsellModal.tsx (2 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/modals/OutboundMessageUpsellModal/useOutboundMessageUpsellModal.tsx (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
apps/meteor/client/components/Omnichannel/OutboundMessage/modals/OutboundMessageUpsellModal/useOutboundMessageUpsellModal.tsx (1)
packages/ui-contexts/src/index.ts (1)
  • useSetModal (68-68)
⏰ 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). (5)
  • GitHub Check: 🔨 Test Storybook / Test Storybook
  • GitHub Check: 🔨 Test Unit / Unit Tests
  • GitHub Check: 🔎 Code Check / Code Lint
  • GitHub Check: 🔎 Code Check / TypeScript
  • GitHub Check: 📦 Meteor Build - coverage
🔇 Additional comments (4)
apps/meteor/client/components/Omnichannel/OutboundMessage/modals/OutboundMessageUpsellModal/OutboundMessageUpsellModal.tsx (2)

24-32: Admin gating logic looks correct

Showing the upsell CTA only for isAdmin && !hasModule aligns with the PR goal.


40-40: Good dependency coverage

useMemo includes isCommunity and handlers; props won’t go stale.

apps/meteor/client/components/Omnichannel/OutboundMessage/modals/OutboundMessageUpsellModal/OutboundMessageUpsellModal.spec.tsx (2)

50-55: Admin no-module path assertions look good

Both buttons rendered as expected for admins.


62-66: Link/annotation expectations are precise

Docs and sales link checks plus annotation visibility look correct for admin scenarios.

Also applies to: 68-72, 74-77

@codecov
Copy link

codecov bot commented Sep 12, 2025

Codecov Report

❌ Patch coverage is 62.50000% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 66.80%. Comparing base (1b9fa01) to head (29e7de7).
⚠️ Report is 2 commits behind head on feat/outbound-msg-ui.

Additional details and impacted files

Impacted file tree graph

@@                   Coverage Diff                    @@
##           feat/outbound-msg-ui   #36930      +/-   ##
========================================================
- Coverage                 66.92%   66.80%   -0.13%     
========================================================
  Files                      3398     3382      -16     
  Lines                    117272   114577    -2695     
  Branches                  21534    21597      +63     
========================================================
- Hits                      78486    76542    -1944     
+ Misses                    36093    35333     -760     
- Partials                   2693     2702       +9     
Flag Coverage Δ
e2e 56.81% <16.66%> (-0.10%) ⬇️
unit 72.19% <100.00%> (+0.42%) ⬆️

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

♻️ Duplicate comments (1)
apps/meteor/client/components/Omnichannel/OutboundMessage/modals/OutboundMessageUpsellModal/OutboundMessageUpsellModal.spec.tsx (1)

31-46: Resolved: “hasModule is false” tests no longer pass hasModule

This addresses the earlier mismatch noted in a past review. Nice cleanup.

🧹 Nitpick comments (4)
apps/meteor/client/components/Omnichannel/OutboundMessage/modals/OutboundMessageUpsellModal/useOutboundMessageUpsellModal.tsx (2)

16-18: Prevent “community” misclassification while license is loading

When license.data is undefined, !license.data?.license evaluates to true, showing “Upgrade” prematurely. Pass undefined while loading to default to the non-community path in the modal.

- const open = useEffectEvent(() =>
-   setModal(<OutboundMessageUpsellModal isCommunity={!license.data?.license} isAdmin={isAdmin} hasModule={hasModule} onClose={close} />),
- );
+ const open = useEffectEvent(() =>
+   setModal(
+     <OutboundMessageUpsellModal
+       isCommunity={license.data ? !license.data.license : undefined}
+       isAdmin={isAdmin}
+       hasModule={hasModule}
+       onClose={close}
+     />,
+   ),
+ );

Note: Confirm that useEffectEvent guarantees the latest license value at call time to avoid stale closures. If not, we should memoize the element or recompute on license changes.


13-13: Prefer boolean coercion over === true

More idiomatic and resilient to non-boolean returns from the hook.

- const hasModule = useHasLicenseModule('outbound-messaging') === true;
+ const hasModule = Boolean(useHasLicenseModule('outbound-messaging'));
apps/meteor/client/components/Omnichannel/OutboundMessage/modals/OutboundMessageUpsellModal/OutboundMessageUpsellModal.tsx (1)

28-29: “Upgrade” label should route to an upgrade path, not sales

Currently the “Upgrade” button still opens the sales link. Suggest routing to a dedicated upgrade URL.

 const OMNICHANNEL_DOCS_LINK = 'https://go.rocket.chat/i/omnichannel-docs';
 const CONTACT_SALES_LINK = 'https://go.rocket.chat/i/contact-sales';
+const UPGRADE_LINK = 'https://go.rocket.chat/i/upgrade';
@@
-       confirmText: isCommunity ? t('Upgrade') : t('Contact_sales'),
-       onConfirm: () => openExternalLink(CONTACT_SALES_LINK),
+       confirmText: isCommunity ? t('Upgrade') : t('Contact_sales'),
+       onConfirm: () => openExternalLink(isCommunity ? UPGRADE_LINK : CONTACT_SALES_LINK),

If a different deep link is desired (e.g., cloud console or in-app admin route), replace UPGRADE_LINK accordingly.

apps/meteor/client/components/Omnichannel/OutboundMessage/modals/OutboundMessageUpsellModal/OutboundMessageUpsellModal.spec.tsx (1)

56-60: Add a click test for the “Upgrade” CTA

Ensure we assert the outbound link for the “Upgrade” path as well.

 			it('should render "Upgrade" button when isCommunity is true', () => {
 				render(<OutboundMessageUpsellModal isAdmin isCommunity onClose={onClose} />, { wrapper: appRoot.build() });
 				expect(screen.getByRole('button', { name: 'Upgrade' })).toBeInTheDocument();
 				expect(screen.queryByRole('button', { name: 'Contact sales' })).not.toBeInTheDocument();
 			});
+
+			it('should call openExternalLink with upgrade link when "Upgrade" is clicked', async () => {
+				render(<OutboundMessageUpsellModal isAdmin isCommunity onClose={onClose} />, { wrapper: appRoot.build() });
+				await userEvent.click(screen.getByRole('button', { name: 'Upgrade' }));
+				expect(openExternalLink).toHaveBeenCalledWith('https://go.rocket.chat/i/upgrade');
+			});

If product decides to keep “Upgrade” -> sales for now, change the expected URL accordingly.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3ce7075 and 29e7de7.

📒 Files selected for processing (3)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/modals/OutboundMessageUpsellModal/OutboundMessageUpsellModal.spec.tsx (1 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/modals/OutboundMessageUpsellModal/OutboundMessageUpsellModal.tsx (2 hunks)
  • apps/meteor/client/components/Omnichannel/OutboundMessage/modals/OutboundMessageUpsellModal/useOutboundMessageUpsellModal.tsx (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
apps/meteor/client/components/Omnichannel/OutboundMessage/modals/OutboundMessageUpsellModal/OutboundMessageUpsellModal.spec.tsx (1)
apps/meteor/client/lib/appLayout.tsx (1)
  • render (26-28)
⏰ 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 (2)
apps/meteor/client/components/Omnichannel/OutboundMessage/modals/OutboundMessageUpsellModal/OutboundMessageUpsellModal.tsx (2)

11-13: Prop addition looks good

isCommunity?: boolean is a clean extension of the props surface.


24-24: Confirm gating rule change

Switching to isAdmin && !hasModule changes who sees the CTA. Please confirm this matches product expectations for admins with and without the module.

@aleksandernsilva aleksandernsilva merged commit aaa3705 into feat/outbound-msg-ui Sep 15, 2025
87 of 89 checks passed
@aleksandernsilva aleksandernsilva deleted the fix/outbound-upsell-modal branch September 15, 2025 13:16
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