From 1ec8f55a9e4a60a74f928c95436cfd392e5e096d Mon Sep 17 00:00:00 2001 From: sanish chirayath Date: Wed, 31 Dec 2025 23:31:39 +0530 Subject: [PATCH] fix: theme within grpc timeline (#6581) * fix: theme within grpc timeline * fix: use font from the theme * remove y padding to make timeline item more compact * fix: font * fix: padding * fix: use fira code * fix: icon spacing * add border to the method search * show bg for message section within request --- .../MethodDropdown/StyledWrapper.js | 10 + .../GrpcQueryUrl/MethodDropdown/index.js | 2 +- .../GrpcTimelineItem/StyledWrapper.js | 244 ++++++++++++++++++ .../Timeline/GrpcTimelineItem/index.js | 212 +++++++-------- 4 files changed, 365 insertions(+), 103 deletions(-) create mode 100644 packages/bruno-app/src/components/ResponsePane/Timeline/GrpcTimelineItem/StyledWrapper.js diff --git a/packages/bruno-app/src/components/RequestPane/GrpcQueryUrl/MethodDropdown/StyledWrapper.js b/packages/bruno-app/src/components/RequestPane/GrpcQueryUrl/MethodDropdown/StyledWrapper.js index e952cae27..9ca77e7d4 100644 --- a/packages/bruno-app/src/components/RequestPane/GrpcQueryUrl/MethodDropdown/StyledWrapper.js +++ b/packages/bruno-app/src/components/RequestPane/GrpcQueryUrl/MethodDropdown/StyledWrapper.js @@ -41,6 +41,16 @@ const StyledWrapper = styled.div` min-width: 15rem; } + input#search-input { + border: 1px solid ${(props) => props.theme.input.border}; + color: ${(props) => props.theme.text}; + + &:focus { + outline: none; + border-color: ${(props) => props.theme.input.focusBorder}; + } + } + .method-dropdown-service-group { margin-bottom: 0.5rem; } diff --git a/packages/bruno-app/src/components/RequestPane/GrpcQueryUrl/MethodDropdown/index.js b/packages/bruno-app/src/components/RequestPane/GrpcQueryUrl/MethodDropdown/index.js index 3b7ba763d..cd474a179 100644 --- a/packages/bruno-app/src/components/RequestPane/GrpcQueryUrl/MethodDropdown/index.js +++ b/packages/bruno-app/src/components/RequestPane/GrpcQueryUrl/MethodDropdown/index.js @@ -153,7 +153,7 @@ const MethodDropdown = ({ onKeyDown={handleKeyDown} onBlur={focusSearchInput} onChange={handleSearchChange} - className="mt-2 mb-3 " + className="mt-2 mb-3" data-testid="grpc-methods-search-input" />
diff --git a/packages/bruno-app/src/components/ResponsePane/Timeline/GrpcTimelineItem/StyledWrapper.js b/packages/bruno-app/src/components/ResponsePane/Timeline/GrpcTimelineItem/StyledWrapper.js new file mode 100644 index 000000000..02aa3cf3e --- /dev/null +++ b/packages/bruno-app/src/components/ResponsePane/Timeline/GrpcTimelineItem/StyledWrapper.js @@ -0,0 +1,244 @@ +import styled from 'styled-components'; +import { rgba } from 'polished'; + +const StyledWrapper = styled.div` + font-size: ${(props) => props.theme.font.size.base}; + + /* Event type styles */ + &.event-metadata { + border-left: 4px solid ${(props) => rgba(props.theme.request.methods.post, 0.2)}; + } + + &.event-response { + border-left: 4px solid ${(props) => rgba(props.theme.request.methods.get, 0.2)}; + } + + &.event-request, + &.event-message { + border-left: 4px solid ${(props) => rgba(props.theme.request.methods.put, 0.2)}; + } + + &.event-status { + border-left: 4px solid ${(props) => rgba(props.theme.colors.text.purple, 0.2)}; + } + + &.event-error { + border-left: 4px solid ${(props) => rgba(props.theme.colors.text.danger, 0.2)}; + } + + &.event-end { + border-left: 4px solid ${(props) => rgba(props.theme.colors.text.muted, 0.2)}; + } + + &.event-cancel { + border-left: 4px solid ${(props) => rgba(props.theme.colors.text.warning, 0.2)}; + } + + /* Event type icon colors */ + .icon-metadata { + color: ${(props) => props.theme.request.methods.post}; + } + + .icon-response { + color: ${(props) => props.theme.request.methods.get}; + } + + .icon-request, + .icon-message { + color: ${(props) => props.theme.request.methods.put}; + } + + .icon-status { + color: ${(props) => props.theme.colors.text.purple}; + } + + .icon-error { + color: ${(props) => props.theme.colors.text.danger}; + } + + .icon-end { + color: ${(props) => props.theme.colors.text.muted}; + } + + .icon-cancel { + color: ${(props) => props.theme.colors.text.warning}; + } + + /* Event Header */ + .event-header { + display: flex; + align-items: center; + gap: 0.375rem; + cursor: pointer; + + .event-icon-container { + display: flex; + align-items: center; + justify-content: center; + width: 1.25rem; + flex-shrink: 0; + } + + span:nth-of-type(1) { + font-weight: 500; + } + + pre { + font-family: var(--font-family-mono); + font-size: ${(props) => props.theme.font.size.xs}; + margin: 0; + } + + .event-timestamp { + opacity: 0.7; + } + } + + /* Common content container styles */ + .content-request, + .content-message, + .content-metadata, + .content-response, + .content-status, + .content-error, + .content-end, + .content-cancel { + margin-top: 0.375rem; + padding: 0.375rem; + border-radius: ${(props) => props.theme.border.radius.base}; + display: flex; + flex-direction: column; + gap: 0.5rem; + } + + /* Request/Message content */ + .content-request, + .content-message { + background-color: ${(props) => rgba(props.theme.request.methods.put, 0.1)}; + } + + .content-request-label, + .content-message-label { + color: ${(props) => props.theme.request.methods.put}; + font-weight: 500; + font-size: ${(props) => props.theme.font.size.sm}; + } + + /* Metadata content */ + .content-metadata { + background-color: ${(props) => rgba(props.theme.request.methods.post, 0.1)}; + } + + .content-metadata-label { + color: ${(props) => props.theme.request.methods.post}; + font-weight: 500; + font-size: ${(props) => props.theme.font.size.sm}; + } + + /* Response content */ + .content-response { + background-color: ${(props) => rgba(props.theme.request.methods.get, 0.1)}; + } + + .content-response-label { + color: ${(props) => props.theme.request.methods.get}; + font-weight: 500; + font-size: ${(props) => props.theme.font.size.sm}; + } + + /* Status content */ + .content-status { + background-color: ${(props) => rgba(props.theme.colors.text.purple, 0.1)}; + } + + .content-status-label { + color: ${(props) => props.theme.colors.text.purple}; + font-weight: 500; + font-size: ${(props) => props.theme.font.size.sm}; + } + + /* Error content */ + .content-error { + background-color: ${(props) => rgba(props.theme.colors.text.danger, 0.1)}; + } + + .content-error-label { + color: ${(props) => props.theme.colors.text.danger}; + font-weight: 500; + font-size: ${(props) => props.theme.font.size.sm}; + } + + /* End content */ + .content-end { + background-color: ${(props) => rgba(props.theme.colors.text.muted, 0.1)}; + font-weight: 500; + font-size: ${(props) => props.theme.font.size.sm}; + } + + /* Cancel content */ + .content-cancel { + background-color: ${(props) => rgba(props.theme.colors.text.warning, 0.1)}; + } + + .content-cancel-label { + color: ${(props) => props.theme.colors.text.warning}; + font-weight: 500; + font-size: ${(props) => props.theme.font.size.sm}; + } + + /* Common content styles */ + .content-box { + background-color: ${(props) => props.theme.bg}; + border-radius: ${(props) => props.theme.border.radius.base}; + padding: 0.375rem; + margin: 0; + + &, + pre { + font-family: var(--font-family-mono); + } + + pre { + margin: 0; + } + } + + .empty-text { + color: ${(props) => props.theme.colors.text.muted}; + font-style: italic; + font-size: ${(props) => props.theme.font.size.xs}; + } + + /* Method type badge */ + .method-type-badge { + background-color: ${(props) => rgba(props.theme.request.methods.put, 0.15)}; + color: ${(props) => props.theme.request.methods.put}; + border-radius: ${(props) => props.theme.border.radius.base}; + font-size: ${(props) => props.theme.font.size.xs}; + font-weight: 500; + } + + /* Timestamp and URL */ + .timestamp-text { + color: ${(props) => props.theme.colors.text.muted}; + font-size: ${(props) => props.theme.font.size.xs}; + } + + .url-text { + color: ${(props) => props.theme.colors.text.muted}; + font-size: ${(props) => props.theme.font.size.xs}; + margin-left: 1.5rem; + margin-top: 0.25rem; + } + + .contents { + display: contents; + font-size: ${(props) => props.theme.font.size.xs}; + + div:first-child { + font-weight: 500; + } + } +`; + +export default StyledWrapper; diff --git a/packages/bruno-app/src/components/ResponsePane/Timeline/GrpcTimelineItem/index.js b/packages/bruno-app/src/components/ResponsePane/Timeline/GrpcTimelineItem/index.js index a42930777..86bc10034 100644 --- a/packages/bruno-app/src/components/ResponsePane/Timeline/GrpcTimelineItem/index.js +++ b/packages/bruno-app/src/components/ResponsePane/Timeline/GrpcTimelineItem/index.js @@ -12,18 +12,7 @@ import { IconX, IconSend } from '@tabler/icons'; - -// Icons for different event types -const EventTypeIcons = { - metadata: , - response: , - request: , - message: , - status: , - error: , - end: , - cancel: -}; +import StyledWrapper from './StyledWrapper'; // Event type display names const EventTypeNames = { @@ -37,18 +26,6 @@ const EventTypeNames = { cancel: 'Cancelled' }; -// Colors for different event types -const EventTypeColors = { - metadata: 'border-blue-500/20', - response: 'border-green-500/20', - request: 'border-orange-500/20', - message: 'border-orange-500/20', - status: 'border-purple-500/20', - error: 'border-red-500/20', - end: 'border-gray-500/20', - cancel: 'border-amber-500/20' -}; - const GrpcTimelineItem = ({ timestamp, request, response, eventType, eventData, item }) => { const [isCollapsed, setIsCollapsed] = useState(true); const toggleCollapse = () => setIsCollapsed((prev) => !prev); @@ -60,10 +37,34 @@ const GrpcTimelineItem = ({ timestamp, request, response, eventType, eventData, const { method, url = '' } = effectiveRequest; const { statusCode, statusText, duration } = response || {}; - // Get event-specific icon and color - const eventIcon = EventTypeIcons[eventType] || ; - const eventColor = EventTypeColors[eventType] || 'border-gray-500/50'; + // Get event-specific icon and class names + const getEventIcon = () => { + const iconClass = `icon-${eventType}`; + switch (eventType) { + case 'metadata': + return ; + case 'response': + return ; + case 'request': + return ; + case 'message': + return ; + case 'status': + return ; + case 'error': + return ; + case 'end': + return ; + case 'cancel': + return ; + default: + return ; + } + }; + + const eventIcon = getEventIcon(); const eventName = EventTypeNames[eventType] || 'Event'; + const eventClass = `event-${eventType}`; // Render appropriate content based on event type const renderEventContent = () => { @@ -72,16 +73,15 @@ const GrpcTimelineItem = ({ timestamp, request, response, eventType, eventData, switch (eventType) { case 'request': return ( -
- +
{effectiveRequest.headers && Object.keys(effectiveRequest.headers).length > 0 && ( -
-
Metadata
-
+
+
Metadata
+
{Object.entries(effectiveRequest.headers).map(([key, value], idx) => (
-
{key}:
-
{typeof value === 'string' ? value : '[Buffer Buffer]'}
+
{key}:
+
{typeof value === 'string' ? value : '[Buffer Buffer]'}
))}
@@ -91,13 +91,11 @@ const GrpcTimelineItem = ({ timestamp, request, response, eventType, eventData, {/* gRPC Messages section */} {!isClientStreaming && effectiveRequest.body?.mode === 'grpc' && effectiveRequest.body?.grpc?.length > 0 && (
-
- Message -
-
+
Message
+
{effectiveRequest.body.grpc.filter((_, index) => index === 0).map((message, idx) => ( -
-
+                    
+
                         {typeof message.content === 'string'
                           ? message.content
                           : JSON.stringify(message.content, null, 2)}
@@ -112,76 +110,82 @@ const GrpcTimelineItem = ({ timestamp, request, response, eventType, eventData,
 
       case 'message':
         return (
-          
-
Message
-
-              {typeof eventData === 'string'
-                ? eventData
-                : JSON.stringify(eventData, null, 2)}
-            
+
+
+
Message
+
+                {typeof eventData === 'string'
+                  ? eventData
+                  : JSON.stringify(eventData, null, 2)}
+              
+
); case 'metadata': return ( -
-
Metadata Headers
- {response.metadata && response.metadata.length > 0 ? ( -
- {response.metadata.map((header, idx) => ( -
-
{header.name}:
-
{header.value}
-
- ))} -
- ) : ( -
No metadata headers
- )} +
+
+
Metadata Headers
+ {response.metadata && response.metadata.length > 0 ? ( +
+ {response.metadata.map((header, idx) => ( +
+
{header.name}:
+
{header.value}
+
+ ))} +
+ ) : ( +
No metadata headers
+ )} +
); case 'response': // For message responses, show the response data return ( -
-
- Response Message #{(response?.responses?.length) || 0} +
+
+
+ Response Message #{(response?.responses?.length) || 0} +
+ {response?.responses && response.responses.length > 0 ? ( +
+                  {JSON.stringify(response.responses[response.responses.length - 1], null, 2)}
+                
+ ) : ( +
Empty message
+ )}
- {response?.responses && response.responses.length > 0 ? ( -
-                {JSON.stringify(response.responses[response.responses.length - 1], null, 2)}
-              
- ) : ( -
Empty message
- )}
); case 'status': // For status events, show status and trailers return ( -
-
+
+
{response.statusDescription && ( -
{response.statusDescription}
+
{response.statusDescription}
)} {response.trailers && response.trailers.length > 0 && ( - <> -
Trailers
-
+
+
Trailers
+
{response.trailers.map((trailer, idx) => (
-
{trailer.name}:
-
{trailer.value || ''}
+
{trailer.name}:
+
{trailer.value || ''}
))}
- +
)}
); @@ -189,26 +193,28 @@ const GrpcTimelineItem = ({ timestamp, request, response, eventType, eventData, case 'error': // For error events, show error details return ( -
-
Error
-
{response.error || 'Unknown error'}
+
+
+
Error
+
{response.error || 'Unknown error'}
+
{response.trailers && response.trailers.length > 0 && ( - <> -
Error Metadata
-
+
+
Error Metadata
+
{response.trailers.map((trailer, idx) => (
-
{trailer.name}:
-
{trailer.value}
+
{trailer.name}:
+
{trailer.value}
))}
- +
)}
); @@ -216,8 +222,8 @@ const GrpcTimelineItem = ({ timestamp, request, response, eventType, eventData, case 'end': // For end events, show summary return ( -
-
Stream Ended
+
+
Stream Ended
Total messages: {(response?.responses?.length) || 0}
@@ -227,8 +233,8 @@ const GrpcTimelineItem = ({ timestamp, request, response, eventType, eventData, case 'cancel': // For cancel events, show cancellation info return ( -
-
Stream Cancelled
+
+
Stream Cancelled
{response.statusDescription || 'The gRPC stream was cancelled'}
); @@ -239,13 +245,15 @@ const GrpcTimelineItem = ({ timestamp, request, response, eventType, eventData, }; return ( -
-
+ +
{isCollapsed ? : } - {eventIcon} - {eventName} +
+ {eventIcon} +
+ {eventName} {eventType === 'request' && effectiveRequest.methodType && ( - + {effectiveRequest.methodType} )} @@ -254,18 +262,18 @@ const GrpcTimelineItem = ({ timestamp, request, response, eventType, eventData,
)} -
[{new Date(timestamp).toISOString()}]
- +
[{new Date(timestamp).toISOString()}]
+
{/* Always show the URL */} -
{url}
+
{url}
{/* Expanded content - only show for non-status items */} {!isCollapsed && renderEventContent()} -
+ ); };