-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Feature: connect & send functionality within gRPC #6994
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Feature: connect & send functionality within gRPC #6994
Conversation
- Introduced a checkbox to toggle the enabled state of individual gRPC messages. - Updated the Redux store to manage the enabled state of messages. - Enhanced the UI to reflect the enabled/disabled status visually. - Implemented functionality to send all enabled messages in one atomic operation for client-streaming methods. - Updated related components and styles to accommodate the new features.
WalkthroughThis PR introduces enable/disable toggles for individual gRPC messages and adds "connect & send" atomic operations for client-streaming and bidi-streaming gRPC methods, allowing messages to be sent immediately upon connection. Changes
Sequence DiagramsequenceDiagram
actor User
participant UI as GrpcQueryUrl UI
participant Redux as Redux Store
participant Network as Network Utils
participant IPC as Electron IPC
participant GRPC as gRPC Handler
User->>UI: Click "Connect & Send"
UI->>Redux: dispatch(sendRequest)
Redux->>Network: connectAndSendGrpc(item, collection, env)
Network->>IPC: invoke('grpc:connect-and-send', payload)
IPC->>GRPC: grpc:connect-and-send handler
GRPC->>GRPC: Prepare & validate request
GRPC->>GRPC: Filter enabled messages
GRPC->>GRPC: Configure TLS/certs
GRPC->>GRPC: Start connection
GRPC->>GRPC: Send all enabled messages<br/>(sequentially)
GRPC->>GRPC: End stream
GRPC-->>IPC: Return success payload
IPC-->>Network: Resolve with result
Network-->>Redux: Return aggregated result
Redux-->>UI: Update state
UI-->>User: Display response & message count
sequenceDiagram
actor User
participant UI as GrpcBody UI
participant Redux as Redux Store
User->>UI: Click message checkbox
UI->>Redux: dispatch(toggleGrpcMessageEnabled)
Redux->>Redux: Toggle enabled flag<br/>in message object
Redux-->>UI: Update draft state
UI-->>User: Checkbox reflects new state
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 6
🤖 Fix all issues with AI agents
In `@packages/bruno-app/src/components/RequestPane/GrpcBody/index.js`:
- Around line 214-227: The disabled styling is applied unconditionally via the
message-container class using the enabled prop, which leaves messages visually
disabled when the method switches to single-message modes; update the condition
so the 'disabled' class is only added when messages can actually be toggled
(e.g., guard with canClientSendMultipleMessages) or alternatively auto-enable
the message when canClientSendMultipleMessages becomes false; adjust the
className expression where MessageToolbar and the message container are rendered
(references: message-container, isSingleMessage, isLast, enabled,
canClientSendMultipleMessages, MessageToolbar) to either remove 'disabled'
unless canClientSendMultipleMessages is true or call onToggleEnabled(true) when
canClientSendMultipleMessages flips to false.
- Around line 20-46: The checkbox in the MessageToolbar component lacks an
accessible label; add an id to the label span (e.g., message-label span ->
id={`grpc-message-label-${index}`}) and reference that id from the checkbox via
aria-labelledby (on the input element use
aria-labelledby={`grpc-message-label-${index}`}) so screen readers associate the
checkbox with the "Message {index + 1}" text; update elements inside
MessageToolbar (the input and the span with className "message-label")
accordingly and keep the existing data-testid and checked/onChange props.
In `@packages/bruno-app/src/components/RequestPane/GrpcQueryUrl/index.js`:
- Around line 327-342: The dropdown-close effect currently only tracks
showSendMessageDropdown, so if isConnectionActive becomes false the menu can
remain open and its listener stays attached; update the effect (or add a small
effect) to also depend on isConnectionActive and when isConnectionActive is
false call setShowSendMessageDropdown(false) and ensure the existing cleanup
still removes the mousedown listener (keep sendMessageDropdownRef,
setShowSendMessageDropdown, showSendMessageDropdown and the handleClickOutside
logic intact).
- Around line 483-542: The connection/send controls use non-focusable <div>s;
update the interactive elements tied to handleCancelConnection,
handleEndConnection, the send-dropdown trigger (sendMessageDropdownRef /
showSendMessageDropdown), handleSendFirstMessage, and handleSendAllMessages so
they are keyboard-accessible: replace those clickable <div> wrappers with
<button type="button"> elements (preferred) or add role="button", tabIndex={0},
and onKeyDown handlers that call the same handlers on Enter/Space; also ensure
the "Send All" control uses the disabled attribute when enabledMessages.length
=== 0 (instead of only a disabled class) and prevent its click/keyboard handlers
when disabled. Ensure aria-label or visible text remains for screen readers.
In `@packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js`:
- Around line 572-589: The promise path for client-streaming/bidi-streaming uses
connectAndSendGrpc(...) but its catch only toasts the error, leaving the outer
promise unresolved; update the catch to call the promise reject (same as resolve
path) so callers aren't left pending—i.e., in the catch handler for
connectAndSendGrpc(itemCopy, collectionCopy, environment,
collectionCopy.runtimeVariables) invoke reject(err) after/to accompany
toast.error(err.message); similarly ensure sendGrpcRequest(...) catch also
rejects if it should mirror behavior; locate these by the connectAndSendGrpc and
sendGrpcRequest calls inside the sendRequest/sendGrpc action and add reject(err)
in their .catch handlers.
In `@packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js`:
- Around line 1646-1666: The reducer toggleGrpcMessageEnabled reads
item.draft.request.body.grpc without ensuring the request body contains a gRPC
messages array; update the guard inside toggleGrpcMessageEnabled (after
isItemARequest and after creating item.draft) to verify item.draft.request and
that item.draft.request.body.grpc is an array (e.g., const messages =
item.draft.request.body.grpc; proceed only if Array.isArray(messages) and
messages[messageIndex] !== undefined) before accessing or toggling
messages[messageIndex].enabled to prevent runtime errors for non‑gRPC requests.
🧹 Nitpick comments (2)
packages/bruno-app/src/utils/network/index.js (1)
145-172: LGTM with a minor note.The sequential send logic is sound. Using
m.enabled !== falsecorrectly treatsundefinedandtrueas enabled. The continue-on-error behavior is appropriate for batch operations.One consideration: if a single
sendGrpcMessagehangs indefinitely, this function will block. If the IPC layer doesn't have built-in timeouts, you may want to add a per-message timeout wrapper in the future for production resilience.packages/bruno-app/src/components/RequestPane/GrpcQueryUrl/index.js (1)
267-325: Add brief JSDoc for the new gRPC action handlers.These handlers encapsulate connection and send side‑effects; a short JSDoc will make expectations and error behavior easier to maintain.
As per coding guidelines, Add JSDoc comments to abstractions for additional details.
| const MessageToolbar = ({ | ||
| index, | ||
| enabled, | ||
| canClientStream, | ||
| isConnectionActive, | ||
| onSend, | ||
| onToggleEnabled, | ||
| onRegenerateMessage, | ||
| onPrettify, | ||
| onDeleteMessage, | ||
| showDelete | ||
| showDelete, | ||
| showCheckbox | ||
| }) => { | ||
| return ( | ||
| <div className="message-toolbar"> | ||
| <span className="message-label">Message {index + 1}</span> | ||
| <div className="message-label-container"> | ||
| {showCheckbox && ( | ||
| <input | ||
| type="checkbox" | ||
| checked={enabled} | ||
| onChange={onToggleEnabled} | ||
| className="message-checkbox" | ||
| data-testid={`grpc-message-enabled-${index}`} | ||
| /> | ||
| )} | ||
| <span className="message-label">Message {index + 1}</span> | ||
| </div> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
fd "index.js" packages/bruno-app/src/components/RequestPane/GrpcBodyRepository: usebruno/bruno
Length of output: 121
🏁 Script executed:
cat -n packages/bruno-app/src/components/RequestPane/GrpcBody/index.js | head -60Repository: usebruno/bruno
Length of output: 2652
🏁 Script executed:
rg "aria-labelledby|htmlFor" packages/bruno-app/src/components --type js --type jsx --type tsx -A 2 -B 2 | head -50Repository: usebruno/bruno
Length of output: 85
🏁 Script executed:
rg "aria-label" packages/bruno-app/src/components --type js --type jsx --type tsx -A 1 -B 1 | head -30Repository: usebruno/bruno
Length of output: 85
🏁 Script executed:
rg "aria-labelledby|htmlFor|aria-label" packages/bruno-app/src/components -A 2 -B 2 | head -80Repository: usebruno/bruno
Length of output: 8085
🏁 Script executed:
cat -n packages/bruno-app/src/components/RequestPane/GrpcBody/index.js | sed -n '33,50p'Repository: usebruno/bruno
Length of output: 861
Add accessible label for the message checkbox.
The checkbox has no associated label for screen readers. Connect it to the adjacent "Message {index + 1}" text using aria-labelledby:
Suggested fix
{showCheckbox && (
<input
type="checkbox"
checked={enabled}
onChange={onToggleEnabled}
className="message-checkbox"
data-testid={`grpc-message-enabled-${index}`}
+ aria-labelledby={`grpc-message-label-${index}`}
/>
)}
- <span className="message-label">Message {index + 1}</span>
+ <span id={`grpc-message-label-${index}`} className="message-label">Message {index + 1}</span>📝 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.
| const MessageToolbar = ({ | |
| index, | |
| enabled, | |
| canClientStream, | |
| isConnectionActive, | |
| onSend, | |
| onToggleEnabled, | |
| onRegenerateMessage, | |
| onPrettify, | |
| onDeleteMessage, | |
| showDelete | |
| showDelete, | |
| showCheckbox | |
| }) => { | |
| return ( | |
| <div className="message-toolbar"> | |
| <span className="message-label">Message {index + 1}</span> | |
| <div className="message-label-container"> | |
| {showCheckbox && ( | |
| <input | |
| type="checkbox" | |
| checked={enabled} | |
| onChange={onToggleEnabled} | |
| className="message-checkbox" | |
| data-testid={`grpc-message-enabled-${index}`} | |
| /> | |
| )} | |
| <span className="message-label">Message {index + 1}</span> | |
| </div> | |
| const MessageToolbar = ({ | |
| index, | |
| enabled, | |
| canClientStream, | |
| isConnectionActive, | |
| onSend, | |
| onToggleEnabled, | |
| onRegenerateMessage, | |
| onPrettify, | |
| onDeleteMessage, | |
| showDelete, | |
| showCheckbox | |
| }) => { | |
| return ( | |
| <div className="message-toolbar"> | |
| <div className="message-label-container"> | |
| {showCheckbox && ( | |
| <input | |
| type="checkbox" | |
| checked={enabled} | |
| onChange={onToggleEnabled} | |
| className="message-checkbox" | |
| data-testid={`grpc-message-enabled-${index}`} | |
| aria-labelledby={`grpc-message-label-${index}`} | |
| /> | |
| )} | |
| <span id={`grpc-message-label-${index}`} className="message-label">Message {index + 1}</span> | |
| </div> |
🤖 Prompt for AI Agents
In `@packages/bruno-app/src/components/RequestPane/GrpcBody/index.js` around lines
20 - 46, The checkbox in the MessageToolbar component lacks an accessible label;
add an id to the label span (e.g., message-label span ->
id={`grpc-message-label-${index}`}) and reference that id from the checkbox via
aria-labelledby (on the input element use
aria-labelledby={`grpc-message-label-${index}`}) so screen readers associate the
checkbox with the "Message {index + 1}" text; update elements inside
MessageToolbar (the input and the span with className "message-label")
accordingly and keep the existing data-testid and checked/onChange props.
| <div className={`message-container ${isSingleMessage ? 'single' : ''} ${isLast ? 'last' : ''} ${!enabled ? 'disabled' : ''}`}> | ||
| <MessageToolbar | ||
| index={index} | ||
| enabled={enabled} | ||
| canClientStream={canClientStream} | ||
| isConnectionActive={isConnectionActive} | ||
| onSend={onSend} | ||
| onToggleEnabled={onToggleEnabled} | ||
| onRegenerateMessage={onRegenerateMessage} | ||
| onPrettify={onPrettify} | ||
| onDeleteMessage={onDeleteMessage} | ||
| showDelete={index > 0} | ||
| showCheckbox={canClientSendMultipleMessages} | ||
| /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid stranded disabled state when switching to single‑message methods.
If a message is disabled in streaming mode and the method switches to unary/server‑streaming, the checkbox disappears but the message stays styled disabled. Consider gating the disabled styling to multi‑message modes (or auto‑enabling on method change) so users aren’t stuck.
🔧 Suggested guard
- <div className={`message-container ${isSingleMessage ? 'single' : ''} ${isLast ? 'last' : ''} ${!enabled ? 'disabled' : ''}`}>
+ <div className={`message-container ${isSingleMessage ? 'single' : ''} ${isLast ? 'last' : ''} ${canClientSendMultipleMessages && !enabled ? 'disabled' : ''}`}>🤖 Prompt for AI Agents
In `@packages/bruno-app/src/components/RequestPane/GrpcBody/index.js` around lines
214 - 227, The disabled styling is applied unconditionally via the
message-container class using the enabled prop, which leaves messages visually
disabled when the method switches to single-message modes; update the condition
so the 'disabled' class is only added when messages can actually be toggled
(e.g., guard with canClientSendMultipleMessages) or alternatively auto-enable
the message when canClientSendMultipleMessages becomes false; adjust the
className expression where MessageToolbar and the message container are rendered
(references: message-container, isSingleMessage, isLast, enabled,
canClientSendMultipleMessages, MessageToolbar) to either remove 'disabled'
unless canClientSendMultipleMessages is true or call onToggleEnabled(true) when
canClientSendMultipleMessages flips to false.
| // Close dropdown when clicking outside | ||
| useEffect(() => { | ||
| const handleClickOutside = (event) => { | ||
| if (sendMessageDropdownRef.current && !sendMessageDropdownRef.current.contains(event.target)) { | ||
| setShowSendMessageDropdown(false); | ||
| } | ||
| }; | ||
|
|
||
| if (showSendMessageDropdown) { | ||
| document.addEventListener('mousedown', handleClickOutside); | ||
| } | ||
|
|
||
| return () => { | ||
| document.removeEventListener('mousedown', handleClickOutside); | ||
| }; | ||
| }, [showSendMessageDropdown]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Close the send dropdown when the connection ends.
If the connection drops while the dropdown is open, the listener stays attached and the menu can re‑open unexpectedly on reconnect. Reset the dropdown when isConnectionActive flips false.
🔧 Suggested fix
- useEffect(() => {
+ useEffect(() => {
+ if (!isConnectionActive) {
+ setShowSendMessageDropdown(false);
+ return;
+ }
const handleClickOutside = (event) => {
if (sendMessageDropdownRef.current && !sendMessageDropdownRef.current.contains(event.target)) {
setShowSendMessageDropdown(false);
}
};
if (showSendMessageDropdown) {
document.addEventListener('mousedown', handleClickOutside);
}
return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
- }, [showSendMessageDropdown]);
+ }, [showSendMessageDropdown, isConnectionActive]);🤖 Prompt for AI Agents
In `@packages/bruno-app/src/components/RequestPane/GrpcQueryUrl/index.js` around
lines 327 - 342, The dropdown-close effect currently only tracks
showSendMessageDropdown, so if isConnectionActive becomes false the menu can
remain open and its listener stays attached; update the effect (or add a small
effect) to also depend on isConnectionActive and when isConnectionActive is
false call setShowSendMessageDropdown(false) and ensure the existing cleanup
still removes the mousedown listener (keep sendMessageDropdownRef,
setShowSendMessageDropdown, showSendMessageDropdown and the handleClickOutside
logic intact).
| {/* Show connection controls for server-streaming when connected */} | ||
| {isConnectionActive && isServerStreamingMethod && ( | ||
| <div className="connection-controls relative flex items-center h-full gap-3"> | ||
| <div className="infotip" onClick={handleCancelConnection} data-testid="grpc-cancel-connection-button"> | ||
| <IconX color={theme.colors.text.danger} strokeWidth={1.5} size={20} className="cursor-pointer" /> | ||
| <span className="infotip-text text-xs">Cancel</span> | ||
| </div> | ||
| </div> | ||
| )} | ||
|
|
||
| {/* Show connection controls for client-streaming/bidi-streaming when connected (interactive mode) */} | ||
| {isConnectionActive && isClientStreamingMethod && ( | ||
| <div className="connection-controls relative flex items-center h-full gap-3"> | ||
| <div className="infotip" onClick={handleCancelConnection} data-testid="grpc-cancel-connection-button"> | ||
| <IconX color={theme.requestTabs.icon.color} strokeWidth={1.5} size={20} className="cursor-pointer" /> | ||
| <IconX color={theme.colors.text.danger} strokeWidth={1.5} size={20} className="cursor-pointer" /> | ||
| <span className="infotip-text text-xs">Cancel</span> | ||
| </div> | ||
|
|
||
| {isClientStreamingMethod && ( | ||
| <div onClick={handleEndConnection} data-testid="grpc-end-connection-button"> | ||
| <IconCheck | ||
| color={theme.colors.text.green} | ||
| strokeWidth={2} | ||
| size={20} | ||
| className="cursor-pointer" | ||
| /> | ||
| <div className="infotip" onClick={handleEndConnection} data-testid="grpc-end-connection-button"> | ||
| <IconCheck color={theme.colors.text.green} strokeWidth={2} size={20} className="cursor-pointer" /> | ||
| <span className="infotip-text text-xs">End Stream</span> | ||
| </div> | ||
|
|
||
| {/* Send message dropdown */} | ||
| <div className="send-dropdown-container" ref={sendMessageDropdownRef}> | ||
| <div | ||
| className="send-dropdown-trigger cursor-pointer flex items-center" | ||
| onClick={(e) => { | ||
| e.stopPropagation(); | ||
| setShowSendMessageDropdown(!showSendMessageDropdown); | ||
| }} | ||
| data-testid="grpc-send-message-dropdown-trigger" | ||
| > | ||
| <IconSend color={theme.requestTabPanel.url.icon} strokeWidth={1.5} size={18} /> | ||
| <IconChevronDown color={theme.requestTabPanel.url.icon} strokeWidth={1.5} size={14} /> | ||
| </div> | ||
| )} | ||
|
|
||
| {showSendMessageDropdown && ( | ||
| <div className="send-dropdown-menu"> | ||
| <div | ||
| className="send-dropdown-item" | ||
| onClick={handleSendFirstMessage} | ||
| data-testid="grpc-send-first-message" | ||
| > | ||
| <IconSend size={14} strokeWidth={1.5} /> | ||
| <span>Send Message</span> | ||
| </div> | ||
| <div | ||
| className={`send-dropdown-item ${enabledMessages.length === 0 ? 'disabled' : ''}`} | ||
| onClick={enabledMessages.length > 0 ? handleSendAllMessages : undefined} | ||
| data-testid="grpc-send-all-messages" | ||
| > | ||
| <IconSend size={14} strokeWidth={1.5} /> | ||
| <span>Send All ({enabledMessages.length})</span> | ||
| </div> | ||
| </div> | ||
| )} | ||
| </div> | ||
| </div> | ||
| )} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
cat -n packages/bruno-app/src/components/RequestPane/GrpcQueryUrl/index.js | sed -n '483,570p'Repository: usebruno/bruno
Length of output: 4989
🏁 Script executed:
# Check file size to understand if we can read more context
wc -l packages/bruno-app/src/components/RequestPane/GrpcQueryUrl/index.jsRepository: usebruno/bruno
Length of output: 129
🏁 Script executed:
# Look for any existing keyboard event handlers in the file
rg 'onKeyDown|onKeyUp|onKeyPress' packages/bruno-app/src/components/RequestPane/GrpcQueryUrl/index.js -A 2 -B 2Repository: usebruno/bruno
Length of output: 40
🏁 Script executed:
# Check if other components in RequestPane use buttons vs divs for controls
rg 'onClick=' packages/bruno-app/src/components/RequestPane/ -t jsx -A 1 | head -50Repository: usebruno/bruno
Length of output: 85
🏁 Script executed:
# Look for other interactive divs without button role in the codebase
fd '\.jsx?$' packages/bruno-app/src/components/RequestPane/ | xargs rg 'onClick=.*?className="[^"]*cursor-pointer' -lRepository: usebruno/bruno
Length of output: 42
🏁 Script executed:
# Check if there are any accessibility-related utilities or patterns established
rg 'role="button"|tabIndex|onKeyDown.*button' packages/bruno-app/src/components/ -l | head -10Repository: usebruno/bruno
Length of output: 781
Make the connection/send controls keyboard-accessible.
The clickable <div> elements at lines 486, 496, 501, 508–518, 522–529, 530–537, 547–554, and 555–565 aren't keyboard-focusable, blocking keyboard and assistive-tech users. Convert these to <button type="button"> or add role="button", tabIndex="0", and onKeyDown handlers. Also apply disabled attribute to the "Send All" button when enabledMessages.length === 0 (line 531) instead of relying on conditional className.
Example conversion pattern
- <div
+ <button
+ type="button"
className="send-dropdown-trigger cursor-pointer flex items-center"
onClick={(e) => {
e.stopPropagation();
setShowSendMessageDropdown(!showSendMessageDropdown);
}}
data-testid="grpc-send-message-dropdown-trigger"
>
<IconSend color={theme.requestTabPanel.url.icon} strokeWidth={1.5} size={18} />
<IconChevronDown color={theme.requestTabPanel.url.icon} strokeWidth={1.5} size={14} />
- </div>
+ </button>🤖 Prompt for AI Agents
In `@packages/bruno-app/src/components/RequestPane/GrpcQueryUrl/index.js` around
lines 483 - 542, The connection/send controls use non-focusable <div>s; update
the interactive elements tied to handleCancelConnection, handleEndConnection,
the send-dropdown trigger (sendMessageDropdownRef / showSendMessageDropdown),
handleSendFirstMessage, and handleSendAllMessages so they are
keyboard-accessible: replace those clickable <div> wrappers with <button
type="button"> elements (preferred) or add role="button", tabIndex={0}, and
onKeyDown handlers that call the same handlers on Enter/Space; also ensure the
"Send All" control uses the disabled attribute when enabledMessages.length === 0
(instead of only a disabled class) and prevent its click/keyboard handlers when
disabled. Ensure aria-label or visible text remains for screen readers.
| const methodType = itemCopy.draft?.request?.methodType || itemCopy.request?.methodType; | ||
| const isClientStreaming = methodType === 'client-streaming' || methodType === 'bidi-streaming'; | ||
|
|
||
| if (isClientStreaming) { | ||
| // Atomic connect-and-send for client-streaming and bidi-streaming | ||
| connectAndSendGrpc(itemCopy, collectionCopy, environment, collectionCopy.runtimeVariables) | ||
| .then(resolve) | ||
| .catch((err) => { | ||
| toast.error(err.message); | ||
| }); | ||
| } else { | ||
| // Existing behavior for unary and server-streaming | ||
| sendGrpcRequest(itemCopy, collectionCopy, environment, collectionCopy.runtimeVariables) | ||
| .then(resolve) | ||
| .catch((err) => { | ||
| toast.error(err.message); | ||
| }); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Resolve/reject the promise on connect-and-send failures.
Right now the catch blocks only toast, so sendRequest stays pending on errors in streaming mode. Recommend rejecting to avoid hung callers awaiting dispatch.
💡 Suggested fix
if (isClientStreaming) {
// Atomic connect-and-send for client-streaming and bidi-streaming
connectAndSendGrpc(itemCopy, collectionCopy, environment, collectionCopy.runtimeVariables)
.then(resolve)
.catch((err) => {
toast.error(err.message);
+ reject(err);
});
} else {
// Existing behavior for unary and server-streaming
sendGrpcRequest(itemCopy, collectionCopy, environment, collectionCopy.runtimeVariables)
.then(resolve)
.catch((err) => {
toast.error(err.message);
+ reject(err);
});
}📝 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.
| const methodType = itemCopy.draft?.request?.methodType || itemCopy.request?.methodType; | |
| const isClientStreaming = methodType === 'client-streaming' || methodType === 'bidi-streaming'; | |
| if (isClientStreaming) { | |
| // Atomic connect-and-send for client-streaming and bidi-streaming | |
| connectAndSendGrpc(itemCopy, collectionCopy, environment, collectionCopy.runtimeVariables) | |
| .then(resolve) | |
| .catch((err) => { | |
| toast.error(err.message); | |
| }); | |
| } else { | |
| // Existing behavior for unary and server-streaming | |
| sendGrpcRequest(itemCopy, collectionCopy, environment, collectionCopy.runtimeVariables) | |
| .then(resolve) | |
| .catch((err) => { | |
| toast.error(err.message); | |
| }); | |
| } | |
| const methodType = itemCopy.draft?.request?.methodType || itemCopy.request?.methodType; | |
| const isClientStreaming = methodType === 'client-streaming' || methodType === 'bidi-streaming'; | |
| if (isClientStreaming) { | |
| // Atomic connect-and-send for client-streaming and bidi-streaming | |
| connectAndSendGrpc(itemCopy, collectionCopy, environment, collectionCopy.runtimeVariables) | |
| .then(resolve) | |
| .catch((err) => { | |
| toast.error(err.message); | |
| reject(err); | |
| }); | |
| } else { | |
| // Existing behavior for unary and server-streaming | |
| sendGrpcRequest(itemCopy, collectionCopy, environment, collectionCopy.runtimeVariables) | |
| .then(resolve) | |
| .catch((err) => { | |
| toast.error(err.message); | |
| reject(err); | |
| }); | |
| } |
🤖 Prompt for AI Agents
In `@packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js`
around lines 572 - 589, The promise path for client-streaming/bidi-streaming
uses connectAndSendGrpc(...) but its catch only toasts the error, leaving the
outer promise unresolved; update the catch to call the promise reject (same as
resolve path) so callers aren't left pending—i.e., in the catch handler for
connectAndSendGrpc(itemCopy, collectionCopy, environment,
collectionCopy.runtimeVariables) invoke reject(err) after/to accompany
toast.error(err.message); similarly ensure sendGrpcRequest(...) catch also
rejects if it should mirror behavior; locate these by the connectAndSendGrpc and
sendGrpcRequest calls inside the sendRequest/sendGrpc action and add reject(err)
in their .catch handlers.
| toggleGrpcMessageEnabled: (state, action) => { | ||
| const collection = findCollectionByUid(state.collections, action.payload.collectionUid); | ||
|
|
||
| if (collection) { | ||
| const item = findItemInCollection(collection, action.payload.itemUid); | ||
|
|
||
| if (item && isItemARequest(item)) { | ||
| if (!item.draft) { | ||
| item.draft = cloneDeep(item); | ||
| } | ||
|
|
||
| const messages = item.draft.request.body.grpc; | ||
| const messageIndex = action.payload.messageIndex; | ||
|
|
||
| if (messages && messages[messageIndex] !== undefined) { | ||
| const currentEnabled = messages[messageIndex].enabled !== false; | ||
| messages[messageIndex].enabled = !currentEnabled; | ||
| } | ||
| } | ||
| } | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Guard non‑gRPC items before reading body.grpc.
isItemARequest includes http/ws/graphql, so a stray dispatch could throw when body.grpc is missing. A quick type/array guard keeps this reducer safe.
💡 Suggested fix
- const messages = item.draft.request.body.grpc;
+ if (item.type !== 'grpc-request') {
+ return;
+ }
+ const messages = item.draft.request.body?.grpc;
+ if (!Array.isArray(messages)) {
+ return;
+ }📝 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.
| toggleGrpcMessageEnabled: (state, action) => { | |
| const collection = findCollectionByUid(state.collections, action.payload.collectionUid); | |
| if (collection) { | |
| const item = findItemInCollection(collection, action.payload.itemUid); | |
| if (item && isItemARequest(item)) { | |
| if (!item.draft) { | |
| item.draft = cloneDeep(item); | |
| } | |
| const messages = item.draft.request.body.grpc; | |
| const messageIndex = action.payload.messageIndex; | |
| if (messages && messages[messageIndex] !== undefined) { | |
| const currentEnabled = messages[messageIndex].enabled !== false; | |
| messages[messageIndex].enabled = !currentEnabled; | |
| } | |
| } | |
| } | |
| }, | |
| toggleGrpcMessageEnabled: (state, action) => { | |
| const collection = findCollectionByUid(state.collections, action.payload.collectionUid); | |
| if (collection) { | |
| const item = findItemInCollection(collection, action.payload.itemUid); | |
| if (item && isItemARequest(item)) { | |
| if (!item.draft) { | |
| item.draft = cloneDeep(item); | |
| } | |
| if (item.type !== 'grpc-request') { | |
| return; | |
| } | |
| const messages = item.draft.request.body?.grpc; | |
| if (!Array.isArray(messages)) { | |
| return; | |
| } | |
| const messageIndex = action.payload.messageIndex; | |
| if (messages && messages[messageIndex] !== undefined) { | |
| const currentEnabled = messages[messageIndex].enabled !== false; | |
| messages[messageIndex].enabled = !currentEnabled; | |
| } | |
| } | |
| } | |
| }, |
🤖 Prompt for AI Agents
In `@packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js`
around lines 1646 - 1666, The reducer toggleGrpcMessageEnabled reads
item.draft.request.body.grpc without ensuring the request body contains a gRPC
messages array; update the guard inside toggleGrpcMessageEnabled (after
isItemARequest and after creating item.draft) to verify item.draft.request and
that item.draft.request.body.grpc is an array (e.g., const messages =
item.draft.request.body.grpc; proceed only if Array.isArray(messages) and
messages[messageIndex] !== undefined) before accessing or toggling
messages[messageIndex].enabled to prevent runtime errors for non‑gRPC requests.
Description
Jira
#6430
GH discussion #6996
Screen.Recording.2026-01-31.at.1.54.36.AM.mov
Contribution Checklist:
Note: Keeping the PR small and focused helps make it easier to review and merge. If you have multiple changes you want to make, please consider submitting them as separate pull requests.
Publishing to New Package Managers
Please see here for more information.
Summary by CodeRabbit
Release Notes
New Features
UI/UX Improvements
✏️ Tip: You can customize this high-level summary in your review settings.