Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,12 @@ const Root = styled('div')({
padding: '2px 2px 2px 10px',
borderRadius: '4px',
color: '#3b4151',
'&.Mui-disabled': {
backgroundColor: '#f5f5f5',
color: '#999',
border: '2px solid #ddd',
cursor: 'not-allowed',
},
}
});

Expand All @@ -69,11 +75,21 @@ export default function AsyncApiUI(props) {
const { api } = useContext(ApiContext);
const isAdvertised = api.advertiseInfo && api.advertiseInfo.advertised;

const getPreferredEndpoint = (URLsObj, apiType) => {
if (!URLsObj) return '';
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;
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;
return '';
};
let initialEndpoint;
initialEndpoint = URLs && (URLs.http || URLs.https);
if (api.type === CONSTANTS.API_TYPES.WS) {
initialEndpoint = URLs && (URLs.ws || URLs.wss);
}
initialEndpoint = getPreferredEndpoint(URLs, api.type);

let expandable = true;
if (!URLs || (!URLs.http && !URLs.https)) {
Expand All @@ -84,11 +100,8 @@ export default function AsyncApiUI(props) {
const [endPoint, setEndpoint] = useState(initialEndpoint);

useEffect(() => {
let newInitialEndpoint = URLs && URLs.http;
if (api.type === CONSTANTS.API_TYPES.WS) {
newInitialEndpoint = URLs && URLs.ws;
}
setEndpoint(newInitialEndpoint);
const newInitialEndpoint = getPreferredEndpoint(URLs, api.type);
setEndpoint(newInitialEndpoint || '');
}, [URLs, api.type]);

useEffect(() => {
Expand Down Expand Up @@ -229,13 +242,21 @@ export default function AsyncApiUI(props) {
id="api-endpoint-select"
value={endPoint}
displayEmpty
disabled={Object.keys(URLs).length === 0 || !Object.values(URLs).some(Boolean)}
onChange={handleServerChange}
>
{Object.entries(URLs).map(([key, value]) => {
if (value) {
return <MenuItem value={value} key={key}>{value}</MenuItem>;
}
})}
{Object.keys(URLs).length === 0 || !Object.values(URLs).some(Boolean) ? (
<MenuItem value='' disabled>
<em>No servers available</em>
</MenuItem>
) : (
Object.entries(URLs).map(([key, value]) => {
if (value) {
return <MenuItem value={value} key={key}>{value}</MenuItem>;
}
return null;
})
)}
</Select>
</FormControl>
{api.type === CONSTANTS.API_TYPES.WEBSUB && allTopics.list.map((topic, index) => (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,17 @@ function Environments(props) {

const intl = useIntl();

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 onCopy = () => {
setUrlCopied(true);
const caller = function () {
Expand All @@ -128,19 +139,16 @@ function Environments(props) {

const getDefaultVersionUrl = () => {
const { defaultVersionURLs } = selectedEndpoint;
if (defaultVersionURLs
&& (defaultVersionURLs.https
|| defaultVersionURLs.http
|| defaultVersionURLs.ws
|| defaultVersionURLs.wss)) {
const firstEnabledDefaultUrl = pickFirstEnabledUrl(defaultVersionURLs);
if (firstEnabledDefaultUrl) {
return (
<>
{`
${intl.formatMessage({
id: 'Apis.Details.Environments.default.url',
defaultMessage: '( Default Version ) ',
})}
${(defaultVersionURLs.https || defaultVersionURLs.http || defaultVersionURLs.ws || defaultVersionURLs.wss)}`}
${(firstEnabledDefaultUrl)}`}
<Tooltip
Comment on lines 140 to 152
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

HTTP and GraphQL WebSocket URLs both using the same first URL is incorrect

pickFirstEnabledUrl(selectedEndpoint.URLs) is used for:

  • The main “Gateway URL” (HTTP) display and copy.
  • The “Gateway Websocket URL for GraphQL Subscriptions” display and copy.

This means the GraphQL WS section can end up showing/copying the HTTP URL instead of the ws/wss URL (or vice versa), depending on object property order.

HTTP and WS sections should select independently:

  • HTTP: prefer https then http.
  • WS: prefer wss then ws.

One way to fix this is to derive separate URLs before rendering:

-    const getDefaultVersionUrl = () => {
-        const { defaultVersionURLs } = selectedEndpoint;
-        const firstEnabledDefaultUrl = pickFirstEnabledUrl(defaultVersionURLs);
+    const getDefaultVersionUrl = () => {
+        const { defaultVersionURLs } = selectedEndpoint;
+        const firstEnabledDefaultUrl = pickFirstEnabledUrl(defaultVersionURLs);
@@
-            ${(firstEnabledDefaultUrl)}`}
+            ${firstEnabledDefaultUrl}`}
@@
-                                navigator.clipboard.writeText(firstEnabledDefaultUrl).then(onCopy);
+                                navigator.clipboard.writeText(firstEnabledDefaultUrl).then(onCopy);

And for the main HTTP and GraphQL WS URLs:

-                                    <InputBase
+                                    {/*
+                                      Prefer HTTPS/HTTP for the main gateway URL
+                                    */}
+                                    <InputBase
@@
-                                            value={pickFirstEnabledUrl(selectedEndpoint.URLs)}
+                                            value={pickFirstEnabledUrl({
+                                                https: selectedEndpoint.URLs.https,
+                                                http: selectedEndpoint.URLs.http,
+                                            })}
@@
-                                                    navigator.clipboard.writeText(pickFirstEnabledUrl(selectedEndpoint.URLs)).then(onCopy);
+                                                    const httpUrl = pickFirstEnabledUrl({
+                                                        https: selectedEndpoint.URLs.https,
+                                                        http: selectedEndpoint.URLs.http,
+                                                    });
+                                                    navigator.clipboard.writeText(httpUrl).then(onCopy);
@@
-                                                <InputBase
+                                                {/*
+                                                  Prefer WSS/WS for GraphQL subscription URL
+                                                */}
+                                                <InputBase
@@
-                                                    value={pickFirstEnabledUrl(selectedEndpoint.URLs)}
+                                                    value={pickFirstEnabledUrl({
+                                                        wss: selectedEndpoint.URLs.wss,
+                                                        ws: selectedEndpoint.URLs.ws,
+                                                    })}
@@
-                                                            navigator.clipboard.writeText(pickFirstEnabledUrl(
-                                                                selectedEndpoint.URLs,
-                                                            )).then(onCopy);
+                                                            const wsUrl = pickFirstEnabledUrl({
+                                                                wss: selectedEndpoint.URLs.wss,
+                                                                ws: selectedEndpoint.URLs.ws,
+                                                            });
+                                                            navigator.clipboard.writeText(wsUrl).then(onCopy);

This keeps the “first enabled” behavior while ensuring each section uses the correct protocol family.

Also applies to: 248-253, 272-277, 284-341, 327-331

🤖 Prompt for AI Agents
In
portals/devportal/src/main/webapp/source/src/app/components/Apis/Details/Environments.jsx
around lines 140-152, the GraphQL WebSocket URL is being derived using the same
pickFirstEnabledUrl call as the HTTP Gateway URL which can return an HTTP URL
for the WS field; instead, derive two separate URLs before rendering: one HTTP
candidate (pickFirstEnabledUrl from selectedEndpoint.URLs filtered to prefer
https then http) and one WS candidate (pickFirstEnabledUrl from
selectedEndpoint.URLs filtered to prefer wss then ws). Replace usages that
currently share the single pickFirstEnabledUrl result with these two variables
so each section independently selects the correct protocol family; apply the
same change to the other affected ranges (248-253, 272-277, 284-341, 327-331).

title={
urlCopied
Expand All @@ -161,10 +169,7 @@ function Environments(props) {
aria-label='Copy the Default Version URL to clipboard'
size='large'
onClick={() => {
navigator.clipboard.writeText(defaultVersionURLs.https
|| defaultVersionURLs.http
|| defaultVersionURLs.ws
|| defaultVersionURLs.wss).then(onCopy('urlCopied'));
navigator.clipboard.writeText(firstEnabledDefaultUrl).then(onCopy);
}}
>
<Icon color='secondary'>file_copy</Icon>
Expand Down Expand Up @@ -243,10 +248,7 @@ function Environments(props) {
<InputBase
className={classes.input}
inputProps={{ 'aria-label': 'api url' }}
value={selectedEndpoint.URLs.https
|| selectedEndpoint.URLs.http
|| selectedEndpoint.URLs.wss
|| selectedEndpoint.URLs.ws}
value={pickFirstEnabledUrl(selectedEndpoint.URLs)}
data-testid='http-url'
/>
</Tooltip>
Expand All @@ -271,10 +273,7 @@ function Environments(props) {
aria-label='Copy the API URL to clipboard'
size='large'
onClick={() => {
navigator.clipboard.writeText(selectedEndpoint.URLs.https
|| selectedEndpoint.URLs.http
|| selectedEndpoint.URLs.wss
|| selectedEndpoint.URLs.ws).then(onCopy('urlCopied'));
navigator.clipboard.writeText(pickFirstEnabledUrl(selectedEndpoint.URLs)).then(onCopy);
}}
>
<Icon color='secondary'>file_copy</Icon>
Expand Down Expand Up @@ -303,8 +302,7 @@ function Environments(props) {
<InputBase
className={classes.input}
inputProps={{ 'aria-label': 'api url' }}
value={selectedEndpoint.URLs.wss
|| selectedEndpoint.URLs.ws}
value={pickFirstEnabledUrl(selectedEndpoint.URLs)}
data-testid='websocket-url'
/>
</Tooltip>
Expand All @@ -327,8 +325,9 @@ function Environments(props) {
aria-label='Copy the API URL to clipboard'
size='large'
onClick={() => {
navigator.clipboard.writeText(selectedEndpoint.URLs.wss
|| selectedEndpoint.URLs.ws).then(onCopy('urlCopied'));
navigator.clipboard.writeText(pickFirstEnabledUrl(
selectedEndpoint.URLs,
)).then(onCopy);
}}
>
<Icon color='secondary'>file_copy</Icon>
Expand Down Expand Up @@ -405,7 +404,7 @@ function Environments(props) {
navigator.clipboard.writeText(
advertiseInfo.apiExternalProductionEndpoint,
)
.then(onCopy('urlCopied'));
.then(onCopy);
}}
>
<Icon color='secondary'>file_copy</Icon>
Expand Down Expand Up @@ -458,7 +457,7 @@ function Environments(props) {
navigator.clipboard.writeText(
advertiseInfo.apiExternalSandboxEndpoint,
)
.then(onCopy('urlCopied'));
.then(onCopy);
}}
>
<Icon color='secondary'>file_copy</Icon>
Expand Down Expand Up @@ -500,6 +499,23 @@ function Environments(props) {
Environments.propTypes = {
classes: PropTypes.shape({}).isRequired,
intl: PropTypes.shape({}).isRequired,
selectedEndpoint: PropTypes.shape({
environmentName: PropTypes.string,
environmentDisplayName: PropTypes.string,
environmentType: PropTypes.string,
URLs: PropTypes.shape({
http: PropTypes.string,
https: PropTypes.string,
ws: PropTypes.string,
wss: PropTypes.string,
}).isRequired,
defaultVersionURLs: PropTypes.shape({
http: PropTypes.string,
https: PropTypes.string,
ws: PropTypes.string,
wss: PropTypes.string,
}),
}).isRequired,
};

export default Environments;
Loading