Skip to content

Fix blank vhost issue for websockets#1252

Open
DDH13 wants to merge 8 commits intowso2:mainfrom
DDH13:main.4234.3
Open

Fix blank vhost issue for websockets#1252
DDH13 wants to merge 8 commits intowso2:mainfrom
DDH13:main.4234.3

Conversation

@DDH13
Copy link
Contributor

@DDH13 DDH13 commented Feb 21, 2026

Summary by CodeRabbit

  • Bug Fixes

    • Added proper fallback handling when no servers are available for endpoint selection.
  • Improvements

    • Enhanced endpoint selection logic to intelligently prioritize appropriate servers based on API protocol (WebSocket vs HTTP).
    • Improved server validation checks and UI feedback consistency across environment configuration screens.
    • Optimized endpoint availability detection for more reliable API configurations.

@coderabbitai
Copy link

coderabbitai bot commented Feb 21, 2026

Walkthrough

This change introduces a set of vhost validation and endpoint selection utilities to consistently handle WebSocket and HTTP URL selection across API portal components. Helper functions are created to validate protocol availability, select appropriate hosts, and manage UI state based on supported protocols.

Changes

Cohort / File(s) Summary
Vhost Utility Module
portals/publisher/src/main/webapp/source/src/app/components/Shared/Environments/Vhosts.jsx
New utility module with five exported functions: wsDisabled(), wssDisabled(), hasValidWebSocketPorts(), hasValidHosts(), and getHostValue() for consistent vhost validation and host selection logic.
Endpoint Selection Helpers
portals/devportal/src/main/webapp/source/src/app/components/Apis/Details/AsyncApiConsole/AsyncApiUI.jsx, portals/devportal/src/main/webapp/source/src/app/components/Apis/Details/Environments.jsx
Adds helper functions for preferred endpoint selection. Introduces pickFirstEnabledUrl() and pickFirstEnabledWSUrl() helpers, replaces inline URL selection chains with helper calls, updates clipboard handlers, and expands Environments.propTypes with detailed selectedEndpoint structure.
Publisher Vhost Integration
portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Environments/Environments.jsx, portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Environments/DeploymentOnboarding.jsx
Imports and integrates vhost utilities throughout. Replaces direct host selection with getHostValue(), adds pre-operation validations via hasValidHosts(), filters vhost options based on WebSocket support, and gates WebSocket endpoints with wsDisabled/wssDisabled checks.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

Hoppy helpers bound with care,
WebSocket endpoints everywhere!
Vhosts validating left and right,
HTTP and WSS shining bright!
Endpoint selection's neat today,
The rabbit's coded a better way! 🐰✨

🚥 Pre-merge checks | ✅ 3
✅ 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 accurately summarizes the main change: fixing blank vhost handling for WebSocket APIs by introducing helpers and validation logic throughout the codebase.
Docstring Coverage ✅ Passed Docstring coverage is 85.71% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@sonarqubecloud
Copy link

Quality Gate Failed Quality Gate failed

Failed conditions
6.2% Duplication on New Code (required ≤ 3%)

See analysis details on SonarQube Cloud

Copy link

@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: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
portals/devportal/src/main/webapp/source/src/app/components/Apis/Details/Environments.jsx (1)

315-343: ⚠️ Potential issue | 🟠 Major

WS copy button should use the WS helper.

The WS row shows pickFirstEnabledWSUrl(...), but the copy action still uses pickFirstEnabledUrl(...), which can copy the HTTP/default URL instead of WS/WSS.

🛠️ Suggested fix
-    navigator.clipboard.writeText(pickFirstEnabledUrl(
-        selectedEndpoint.URLs,
-    )).then(onCopy);
+    navigator.clipboard.writeText(pickFirstEnabledWSUrl(
+        selectedEndpoint.URLs,
+    )).then(onCopy);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@portals/devportal/src/main/webapp/source/src/app/components/Apis/Details/Environments.jsx`
around lines 315 - 343, The copy handler for the WebSocket row is using
pickFirstEnabledUrl instead of the WebSocket helper, so update the IconButton
onClick handler to write the WebSocket URL by calling
pickFirstEnabledWSUrl(selectedEndpoint.URLs) (keeping the existing promise
then(onCopy) behavior); locate the IconButton onClick that currently calls
navigator.clipboard.writeText(pickFirstEnabledUrl(...)) and replace the inner
call to use pickFirstEnabledWSUrl so the copied value matches the displayed
InputBase value.
🧹 Nitpick comments (3)
portals/devportal/src/main/webapp/source/src/app/components/Apis/Details/AsyncApiConsole/AsyncApiUI.jsx (2)

78-105: Consider preferring secure endpoints by default.

getPreferredEndpoint currently favors ws/http over wss/https. If both exist, selecting the secure endpoint first is generally safer.

🛠️ Suggested tweak
-    if (apiType === CONSTANTS.API_TYPES.WS) {
-      // prefer ws, but if ws does not exist fall back to wss
-      if (URLsObj.ws) return URLsObj.ws;
-      if (URLsObj.wss) return URLsObj.wss;
+    if (apiType === CONSTANTS.API_TYPES.WS) {
+      // prefer wss, but if wss does not exist fall back to ws
+      if (URLsObj.wss) return URLsObj.wss;
+      if (URLsObj.ws) return URLsObj.ws;
       return '';
     }
-    // for non-WS APIs prefer http, but if http does not exist fall back to https
-    if (URLsObj.http) return URLsObj.http;
-    if (URLsObj.https) return URLsObj.https;
+    // for non-WS APIs prefer https, but if https does not exist fall back to http
+    if (URLsObj.https) return URLsObj.https;
+    if (URLsObj.http) return URLsObj.http;
     return '';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@portals/devportal/src/main/webapp/source/src/app/components/Apis/Details/AsyncApiConsole/AsyncApiUI.jsx`
around lines 78 - 105, getPreferredEndpoint currently prefers non-secure
endpoints (ws/http) over secure ones; change it to prefer secure endpoints first
by checking URLsObj.wss before URLsObj.ws for apiType CONSTANTS.API_TYPES.WS and
checking URLsObj.https before URLsObj.http for non-WS types (keep returning ''
if none). Update any initialEndpoint / useEffect usage remains the same since
they rely on getPreferredEndpoint (ensure setEndpoint gets the new preferred
secure value).

245-259: Guard against null/undefined URL sets in disabled/empty logic.

Object.keys(URLs) and Object.values(URLs) will throw if URLs is null/undefined. A small guard keeps this resilient if upstream ever passes a missing value.

🛠️ Suggested tweak
-                        disabled={Object.keys(URLs).length === 0 || !Object.values(URLs).some(Boolean)}
+                        disabled={!URLs || Object.keys(URLs).length === 0 || !Object.values(URLs).some(Boolean)}
 ...
-                      {Object.keys(URLs).length === 0 || !Object.values(URLs).some(Boolean) ? (
+                      {!URLs || Object.keys(URLs).length === 0 || !Object.values(URLs).some(Boolean) ? (
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@portals/devportal/src/main/webapp/source/src/app/components/Apis/Details/AsyncApiConsole/AsyncApiUI.jsx`
around lines 245 - 259, The code uses Object.keys(URLs) and Object.values(URLs)
which will throw if URLs is null/undefined; fix by normalizing URLs to a safe
object before use (e.g., const safeURLs = URLs || {}) or by using fallback
expressions in-place (Object.keys(URLs || {}) and Object.values(URLs || {})) and
update the disabled prop, the conditional rendering check, and the map over
Object.entries(URLs || {}) so the select, the onChange handler reference
(handleServerChange) and MenuItem rendering tolerate missing URL sets.
portals/devportal/src/main/webapp/source/src/app/components/Apis/Details/Environments.jsx (1)

121-143: Make URL preference explicit to avoid protocol mix‑ups.

pickFirstEnabledUrl currently depends on object value order, which can return ws/wss even when http/https are present. Consider explicitly preferring https/http and falling back to wss/ws only when needed (so WS‑only APIs still work).

🛠️ Suggested refactor
 const pickFirstEnabledUrl = (urls) => {
     if (!urls || typeof urls !== 'object') {
         return '';
     }
-    // Get all truthy values and return the first one, or '' if none are found.
-    const firstUrl = Object.values(urls).find(
-        (val) => typeof val === 'string' && val.trim() !== '',
-    );
-    return firstUrl || '';
+    const preferred = ['https', 'http', 'wss', 'ws'];
+    for (const key of preferred) {
+        const val = urls[key];
+        if (typeof val === 'string' && val.trim() !== '') {
+            return val;
+        }
+    }
+    return '';
 };

Based on learnings: For WSO2 APIM devportal, the backend populates selectedEndpoint.URLs based on API type (HTTP has http/https only, WS has ws/wss only, GraphQL has all four), so explicit preference avoids picking the wrong protocol for the HTTP row.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@portals/devportal/src/main/webapp/source/src/app/components/Apis/Details/Environments.jsx`
around lines 121 - 143, pickFirstEnabledUrl relies on object value order and may
return ws/wss when http/https exist; change it to explicitly check keys in
preferred order (e.g., ['https','http','wss','ws']) and return the first
non-empty string, rather than using Object.values; also update
pickFirstEnabledWSUrl to prefer secure websockets first (check ['wss','ws'] in
that order) so WS endpoints return secure variants when available. Ensure both
functions still handle missing/invalid urls and return '' as the fallback.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Environments/Environments.jsx`:
- Around line 3161-3169: The WS dropdowns in Environments.jsx are mapping
row.vhosts directly, which can produce blank options when a vhost lacks ws/wss;
update the mapping to first filter row.vhosts using the same
hasValidWebSocketPorts predicate (or equivalent helper) when api.isWebSocket()
is true, then map the filtered list to MenuItem using getHostValue(vhost,
api.isWebSocket()); apply the same change to the other occurrences noted (around
the blocks that render vhost MenuItem lists at the lines referenced) so only
vhosts with valid WebSocket ports are shown.

In
`@portals/publisher/src/main/webapp/source/src/app/components/Shared/Environments/Vhosts.jsx`:
- Around line 44-57: getHostValue currently returns vhost.wsHost or
vhost.wssHost which can be null when both WS and WSS are disabled; update
getHostValue (referencing the function name getHostValue and helper checks
wsDisabled and wssDisabled) to detect the case where the resolved host is falsy
and return a safe fallback (explicit empty string or null) instead of passing
through a null value, ensuring callers get a predictable empty value rather than
a blank option.
- Around line 5-17: wsDisabled and wssDisabled currently only return true when
both host and port are null; change their logic to treat any missing/empty host
or port as disabled by using OR checks and also account for empty
strings/undefined. Update the functions wsDisabled(vhost) and wssDisabled(vhost)
to return true when vhost?.wsPort is null/undefined/empty OR vhost?.wsHost is
null/undefined/empty (and likewise for wssPort/wssHost) so a missing single
field marks the socket type disabled.

---

Outside diff comments:
In
`@portals/devportal/src/main/webapp/source/src/app/components/Apis/Details/Environments.jsx`:
- Around line 315-343: The copy handler for the WebSocket row is using
pickFirstEnabledUrl instead of the WebSocket helper, so update the IconButton
onClick handler to write the WebSocket URL by calling
pickFirstEnabledWSUrl(selectedEndpoint.URLs) (keeping the existing promise
then(onCopy) behavior); locate the IconButton onClick that currently calls
navigator.clipboard.writeText(pickFirstEnabledUrl(...)) and replace the inner
call to use pickFirstEnabledWSUrl so the copied value matches the displayed
InputBase value.

---

Nitpick comments:
In
`@portals/devportal/src/main/webapp/source/src/app/components/Apis/Details/AsyncApiConsole/AsyncApiUI.jsx`:
- Around line 78-105: getPreferredEndpoint currently prefers non-secure
endpoints (ws/http) over secure ones; change it to prefer secure endpoints first
by checking URLsObj.wss before URLsObj.ws for apiType CONSTANTS.API_TYPES.WS and
checking URLsObj.https before URLsObj.http for non-WS types (keep returning ''
if none). Update any initialEndpoint / useEffect usage remains the same since
they rely on getPreferredEndpoint (ensure setEndpoint gets the new preferred
secure value).
- Around line 245-259: The code uses Object.keys(URLs) and Object.values(URLs)
which will throw if URLs is null/undefined; fix by normalizing URLs to a safe
object before use (e.g., const safeURLs = URLs || {}) or by using fallback
expressions in-place (Object.keys(URLs || {}) and Object.values(URLs || {})) and
update the disabled prop, the conditional rendering check, and the map over
Object.entries(URLs || {}) so the select, the onChange handler reference
(handleServerChange) and MenuItem rendering tolerate missing URL sets.

In
`@portals/devportal/src/main/webapp/source/src/app/components/Apis/Details/Environments.jsx`:
- Around line 121-143: pickFirstEnabledUrl relies on object value order and may
return ws/wss when http/https exist; change it to explicitly check keys in
preferred order (e.g., ['https','http','wss','ws']) and return the first
non-empty string, rather than using Object.values; also update
pickFirstEnabledWSUrl to prefer secure websockets first (check ['wss','ws'] in
that order) so WS endpoints return secure variants when available. Ensure both
functions still handle missing/invalid urls and return '' as the fallback.

Comment on lines 3161 to +3169
{row.vhosts?.map(
(vhost) => (
<MenuItem value={api.isWebSocket()
? vhost.wsHost : vhost.host}>
{api.isWebSocket()
? vhost.wsHost : vhost.host}
<MenuItem
key={getHostValue(vhost,
api.isWebSocket())}
value={getHostValue(
vhost,api.isWebSocket())}>
{getHostValue(
vhost,api.isWebSocket())}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Filter WS vhost options here too to avoid blank entries.

These dropdowns still list all vhosts even in WebSocket mode; if a vhost lacks ws/wss, it can show up as an empty option despite the hasValidHosts gating. Apply the same hasValidWebSocketPorts filter used elsewhere.

🛠️ Suggested fix (apply similarly in the other WS dropdowns)
- {row.vhosts?.map((vhost) => (
+ {row.vhosts
+     .filter((vhost) => !api.isWebSocket() || hasValidWebSocketPorts(vhost))
+     .map((vhost) => (
         <MenuItem
             key={getHostValue(vhost, api.isWebSocket())}
             value={getHostValue(vhost, api.isWebSocket())}
         >
             {getHostValue(vhost, api.isWebSocket())}
         </MenuItem>
     ))}

Also applies to: 3595-3602, 3819-3826

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Environments/Environments.jsx`
around lines 3161 - 3169, The WS dropdowns in Environments.jsx are mapping
row.vhosts directly, which can produce blank options when a vhost lacks ws/wss;
update the mapping to first filter row.vhosts using the same
hasValidWebSocketPorts predicate (or equivalent helper) when api.isWebSocket()
is true, then map the filtered list to MenuItem using getHostValue(vhost,
api.isWebSocket()); apply the same change to the other occurrences noted (around
the blocks that render vhost MenuItem lists at the lines referenced) so only
vhosts with valid WebSocket ports are shown.

Comment on lines +5 to +17
/**
* Returns true when websocket (ws) host/port is missing.
* @param {Object} vhost VHost object
* @returns {boolean}
*/
export const wsDisabled = (vhost) => vhost?.wsPort === null && vhost?.wsHost === null;

/**
* Returns true when secure websocket (wss) host/port is missing.
* @param {Object} vhost VHost object
* @returns {boolean}
*/
export const wssDisabled = (vhost) => vhost?.wssPort === null && vhost?.wssHost === null;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Treat partial/empty WS/WSS data as disabled.

Using && only flags disabled when both host and port are null, so a missing host or port is still considered valid and can yield blank/invalid URLs. Consider treating any missing/empty field as disabled.

🛠️ Suggested fix
-export const wsDisabled = (vhost) => vhost?.wsPort === null && vhost?.wsHost === null;
+export const wsDisabled = (vhost) =>
+    vhost?.wsPort == null || vhost?.wsHost == null || vhost.wsHost === '';
 
-export const wssDisabled = (vhost) => vhost?.wssPort === null && vhost?.wssHost === null;
+export const wssDisabled = (vhost) =>
+    vhost?.wssPort == null || vhost?.wssHost == null || vhost.wssHost === '';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/**
* Returns true when websocket (ws) host/port is missing.
* @param {Object} vhost VHost object
* @returns {boolean}
*/
export const wsDisabled = (vhost) => vhost?.wsPort === null && vhost?.wsHost === null;
/**
* Returns true when secure websocket (wss) host/port is missing.
* @param {Object} vhost VHost object
* @returns {boolean}
*/
export const wssDisabled = (vhost) => vhost?.wssPort === null && vhost?.wssHost === null;
/**
* Returns true when websocket (ws) host/port is missing.
* `@param` {Object} vhost VHost object
* `@returns` {boolean}
*/
export const wsDisabled = (vhost) =>
vhost?.wsPort == null || vhost?.wsHost == null || vhost.wsHost === '';
/**
* Returns true when secure websocket (wss) host/port is missing.
* `@param` {Object} vhost VHost object
* `@returns` {boolean}
*/
export const wssDisabled = (vhost) =>
vhost?.wssPort == null || vhost?.wssHost == null || vhost.wssHost === '';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@portals/publisher/src/main/webapp/source/src/app/components/Shared/Environments/Vhosts.jsx`
around lines 5 - 17, wsDisabled and wssDisabled currently only return true when
both host and port are null; change their logic to treat any missing/empty host
or port as disabled by using OR checks and also account for empty
strings/undefined. Update the functions wsDisabled(vhost) and wssDisabled(vhost)
to return true when vhost?.wsPort is null/undefined/empty OR vhost?.wsHost is
null/undefined/empty (and likewise for wssPort/wssHost) so a missing single
field marks the socket type disabled.

Comment on lines +44 to +57
/**
* Get the host value based on the API type and available ports.
* @param {Object} vhost VHost object
* @param {boolean} isWebSocket Whether the API is a websocket API
* @returns {string}
*/
export const getHostValue = (vhost, isWebSocket) => {
if (!isWebSocket) {
return vhost.host;
}
if (wsDisabled(vhost) && !wssDisabled(vhost)) {
return vhost.wssHost;
}
return vhost.wsHost;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Return a safe fallback when both WS and WSS are unavailable.

If both protocols are disabled, vhost.wsHost can be null, which later becomes a blank option/value. Consider returning an explicit empty string (or null) instead.

🛠️ Suggested fix
 export const getHostValue = (vhost, isWebSocket) => {
     if (!isWebSocket) {
         return vhost.host;
     }
     if (wsDisabled(vhost) && !wssDisabled(vhost)) {
         return vhost.wssHost;
     }
+    if (wsDisabled(vhost) && wssDisabled(vhost)) {
+        return '';
+    }
     return vhost.wsHost;
 };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/**
* Get the host value based on the API type and available ports.
* @param {Object} vhost VHost object
* @param {boolean} isWebSocket Whether the API is a websocket API
* @returns {string}
*/
export const getHostValue = (vhost, isWebSocket) => {
if (!isWebSocket) {
return vhost.host;
}
if (wsDisabled(vhost) && !wssDisabled(vhost)) {
return vhost.wssHost;
}
return vhost.wsHost;
/**
* Get the host value based on the API type and available ports.
* `@param` {Object} vhost VHost object
* `@param` {boolean} isWebSocket Whether the API is a websocket API
* `@returns` {string}
*/
export const getHostValue = (vhost, isWebSocket) => {
if (!isWebSocket) {
return vhost.host;
}
if (wsDisabled(vhost) && !wssDisabled(vhost)) {
return vhost.wssHost;
}
if (wsDisabled(vhost) && wssDisabled(vhost)) {
return '';
}
return vhost.wsHost;
};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@portals/publisher/src/main/webapp/source/src/app/components/Shared/Environments/Vhosts.jsx`
around lines 44 - 57, getHostValue currently returns vhost.wsHost or
vhost.wssHost which can be null when both WS and WSS are disabled; update
getHostValue (referencing the function name getHostValue and helper checks
wsDisabled and wssDisabled) to detect the case where the resolved host is falsy
and return a safe fallback (explicit empty string or null) instead of passing
through a null value, ensuring callers get a predictable empty value rather than
a blank option.

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.

1 participant