diff --git a/packages/bruno-app/src/components/ResponsePane/WsResponsePane/WSMessagesList/StyledWrapper.js b/packages/bruno-app/src/components/ResponsePane/WsResponsePane/WSMessagesList/StyledWrapper.js
index a03c31dec..3709aee51 100644
--- a/packages/bruno-app/src/components/ResponsePane/WsResponsePane/WSMessagesList/StyledWrapper.js
+++ b/packages/bruno-app/src/components/ResponsePane/WsResponsePane/WSMessagesList/StyledWrapper.js
@@ -1,7 +1,15 @@
import styled from 'styled-components';
const StyledWrapper = styled.div`
- overflow-y: auto;
+ overflow-y: auto;
+
+ .ws-message:not(:last-child) {
+ border-bottom: 1px solid ${(props) => props.theme.table.border};
+ }
+
+ .ws-message:not(:last-child).open {
+ border-bottom-width: 0px;
+ }
.ws-incoming {
background: ${(props) => props.theme.bg};
diff --git a/packages/bruno-app/src/components/ResponsePane/WsResponsePane/WSMessagesList/index.js b/packages/bruno-app/src/components/ResponsePane/WsResponsePane/WSMessagesList/index.js
index 1c429c565..bd86fa148 100644
--- a/packages/bruno-app/src/components/ResponsePane/WsResponsePane/WSMessagesList/index.js
+++ b/packages/bruno-app/src/components/ResponsePane/WsResponsePane/WSMessagesList/index.js
@@ -1,41 +1,41 @@
import React from 'react';
import classnames from 'classnames';
import StyledWrapper from './StyledWrapper';
-import { IconChevronUp, IconChevronDown, IconArrowUpRight, IconArrowDownLeft } from '@tabler/icons';
+import { IconChevronUp, IconInfoCircle, IconChevronDown, IconArrowUpRight, IconArrowDownLeft } from '@tabler/icons';
import CodeEditor from 'components/CodeEditor/index';
import { useTheme } from 'providers/Theme';
import { useState } from 'react';
import { useSelector } from 'react-redux';
+import _ from 'lodash';
+import { forwardRef } from 'react';
-// Example message structure: { direction: 'incoming' | 'outgoing', timestamp, data }
-
-const parseContent = (content) => {
- if (typeof content === 'string') {
- let isJSON = false;
- let resultContent = content;
- let trimmedContent = content;
- try {
- JSON.parse(content);
- isJSON = true;
- resultContent = JSON.stringify(resultContent, null, 2);
- trimmedContent = JSON.stringify(resultContent, null, 0);
- } catch (err) {
- // digest error
- }
-
- return {
- type: isJSON ? 'application/json' : 'text/plain',
- content: resultContent,
- sliced: trimmedContent.slice(0, 30)
- };
- }
+const getContentMeta = (content) => {
if (typeof content === 'object') {
return {
- type: 'application/json',
- content: JSON.stringify(content, null, 2),
- sliced: JSON.stringify(content, null, 0).slice(0, 30)
+ isJSON: true,
+ content: JSON.stringify(content, null, 0)
};
}
+ try {
+ return {
+ isJSON: true,
+ content: JSON.stringify(JSON.parse(content), null, 0)
+ };
+ } catch {
+ return {
+ isJSON: false,
+ content: content
+ };
+ }
+};
+
+const parseContent = (content) => {
+ let contentMeta = getContentMeta(content);
+ return {
+ type: contentMeta.isJSON ? 'application/json' : 'text/plain',
+ content: contentMeta.isJSON ? JSON.stringify(JSON.parse(contentMeta.content), null, 2) : contentMeta.content,
+ sliced: contentMeta.content.slice(0, 30)
+ };
};
const getDataTypeText = (type) => {
@@ -46,28 +46,56 @@ const getDataTypeText = (type) => {
return textMap[type] ?? 'RAW';
};
-const WSMessageItem = ({ message, defaultOpen }) => {
- const [isOpen, setIsOpen] = useState(defaultOpen ?? false);
+/**
+ *
+ * @param {"incoming"|"outgoing"|"info"} type
+ */
+const TypeIcon = ({type})=>{
+ const commonProps = {
+ size: 18
+ }
+ return {
+ "incoming":
,
+ "outgoing":
,
+ "info":
+ }[type]
+}
+
+const WSMessageItem = ({ message, isLast }) => {
+ const [isOpen, setIsOpen] = useState(false);
const preferences = useSelector((state) => state.app.preferences);
const { displayedTheme } = useTheme();
- const isIncoming = message.direction === 'incoming';
+ const isIncoming = message.type === 'incoming';
+ const isInfo = message.type === 'info';
let parsedContent = parseContent(message.message);
const dataType = getDataTypeText(parsedContent.type);
return (
{
+ if (!node) return;
+ if (isLast) node.scrollIntoView();
+ }}
+ className={classnames('ws-message flex flex-col py-2', {
'ws-incoming': isIncoming,
- 'ws-outgoing': !isIncoming
+ 'ws-outgoing': !isIncoming,
+ 'open': isOpen
})}
>
{
- setIsOpen(!isOpen);
+ if(!isInfo){
+ setIsOpen(!isOpen);
+ }
}}
>
@@ -77,10 +105,9 @@ const WSMessageItem = ({ message, defaultOpen }) => {
isIncoming ? 'text-blue-700' : 'text-green-700'
)}
>
- {isIncoming ? : }
+
- {!isOpen ? {parsedContent.sliced} : null}
- {isOpen ? {dataType} : null}
+ {parsedContent.sliced}
{message.timestamp && (
@@ -96,7 +123,11 @@ const WSMessageItem = ({ message, defaultOpen }) => {
{isOpen && (
-
+
+
+
+ {isOpen ?
{dataType} : null}
+
{
);
};
-const WSMessagesList = ({ messages = [] }) => {
+const WSMessagesList = ({ order = -1, messages = [] }) => {
if (!messages.length) {
return No messages yet.
;
}
return (
-
- {messages.map((msg, idx,src) => {
- const isLast = idx === src.length-1
- return ;
- })}
+
+ {messages
+ .toSorted((x, y) => {
+ let a = order == -1 ? x : y
+ let b = order == -1 ? y : x
+ return (new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
+ })
+ .map((msg, idx, src) => {
+ const isLast = src.length - 1 === idx;
+ return ;
+ })}
);
};
diff --git a/packages/bruno-app/src/components/ResponsePane/WsResponsePane/WSResponseSortOrder/StyledWrapper.js b/packages/bruno-app/src/components/ResponsePane/WsResponsePane/WSResponseSortOrder/StyledWrapper.js
new file mode 100644
index 000000000..8c32a8bab
--- /dev/null
+++ b/packages/bruno-app/src/components/ResponsePane/WsResponsePane/WSResponseSortOrder/StyledWrapper.js
@@ -0,0 +1,8 @@
+import styled from 'styled-components';
+
+const StyledWrapper = styled.div`
+ font-size: 0.8125rem;
+ color: ${(props) => props.theme.requestTabPanel.responseStatus};
+`;
+
+export default StyledWrapper;
diff --git a/packages/bruno-app/src/components/ResponsePane/WsResponsePane/WSResponseSortOrder/index.js b/packages/bruno-app/src/components/ResponsePane/WsResponsePane/WSResponseSortOrder/index.js
new file mode 100644
index 000000000..9422da9ab
--- /dev/null
+++ b/packages/bruno-app/src/components/ResponsePane/WsResponsePane/WSResponseSortOrder/index.js
@@ -0,0 +1,32 @@
+import React from 'react';
+import { IconSortDescending2, IconSortAscending2 } from '@tabler/icons';
+import { useDispatch } from 'react-redux';
+import StyledWrapper from './StyledWrapper';
+import { wsUpdateResponseSortOrder } from 'providers/ReduxStore/slices/collections/index';
+
+const WSResponseSortOrder = ({ collection, item }) => {
+ const dispatch = useDispatch();
+
+ const order = item.response?.initiatedWsResponse?.sortOrder ?? -1
+
+ const toggleSortOrder = ()=>{
+ dispatch(
+ wsUpdateResponseSortOrder({
+ itemUid: item.uid,
+ collectionUid: collection.uid,
+ })
+ );
+ }
+
+ return (
+
+
+
+ );
+};
+
+export default WSResponseSortOrder;
diff --git a/packages/bruno-app/src/components/ResponsePane/WsResponsePane/WSStatusCode/index.js b/packages/bruno-app/src/components/ResponsePane/WsResponsePane/WSStatusCode/index.js
index caa5404a7..f338c3397 100644
--- a/packages/bruno-app/src/components/ResponsePane/WsResponsePane/WSStatusCode/index.js
+++ b/packages/bruno-app/src/components/ResponsePane/WsResponsePane/WSStatusCode/index.js
@@ -4,13 +4,11 @@ import wsStatusCodePhraseMap from './get-ws-status-code-phrase';
import StyledWrapper from './StyledWrapper';
const WSStatusCode = ({ status, text }) => {
- // gRPC status codes: 0 is success, anything else is an error
const getTabClassname = (status) => {
- const isPending = text === 'PENDING' || text === 'STREAMING';
return classnames('ml-2', {
- 'text-ok': parseInt(status) === 0,
- 'text-pending': isPending,
- 'text-error': parseInt(status) > 0 && !isPending
+ // ok if normal connect and normal closure
+ 'text-ok': parseInt(status) === 0 || parseInt(status) === 1000,
+ 'text-error': parseInt(status) !== 1000 && parseInt(status) !== 0
});
};
@@ -18,7 +16,7 @@ const WSStatusCode = ({ status, text }) => {
return (
- {Number.isInteger(status) ? {status}
: null}
+ {Number.isInteger(status) && status != 0 ? {status}
: null}
{statusText && {statusText}
}
);
diff --git a/packages/bruno-app/src/components/ResponsePane/WsResponsePane/index.js b/packages/bruno-app/src/components/ResponsePane/WsResponsePane/index.js
index 6fe7e3744..0b414b530 100644
--- a/packages/bruno-app/src/components/ResponsePane/WsResponsePane/index.js
+++ b/packages/bruno-app/src/components/ResponsePane/WsResponsePane/index.js
@@ -1,11 +1,9 @@
-import React, { useState, useEffect } from 'react';
+import React from 'react';
import find from 'lodash/find';
-import classnames from 'classnames';
import { useDispatch, useSelector } from 'react-redux';
import { updateResponsePaneTab } from 'providers/ReduxStore/slices/tabs';
import Overlay from '../Overlay';
import Placeholder from '../Placeholder';
-import WSResponseHeaders from './WSResponseHeaders';
import WSStatusCode from './WSStatusCode';
import ResponseTime from '../ResponseTime/index';
import Timeline from '../Timeline';
@@ -15,6 +13,7 @@ import StyledWrapper from './StyledWrapper';
import ResponseLayoutToggle from '../ResponseLayoutToggle';
import Tab from 'components/Tab';
import WSMessagesList from './WSMessagesList';
+import WSResponseSortOrder from './WSResponseSortOrder';
const WSResult = ({ response }) => {
return response.isError ? (
@@ -22,7 +21,7 @@ const WSResult = ({ response }) => {
{response.error}
) : (
-
+
);
};
@@ -52,9 +51,6 @@ const WSResponsePane = ({ item, collection }) => {
case 'response': {
return
;
}
- case 'headers': {
- return
;
- }
case 'timeline': {
return
;
}
@@ -95,11 +91,6 @@ const WSResponsePane = ({ item, collection }) => {
label: 'Messages',
count: Array.isArray(response.responses) ? response.responses.length : 0
},
- {
- name: 'headers',
- label: 'Metadata',
- count: Array.isArray(response.metadata) ? response.metadata.length : 0
- },
{
name: 'timeline',
label: 'Timeline'
@@ -130,6 +121,7 @@ const WSResponsePane = ({ item, collection }) => {
<>
+
{
+ const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
+
+ if (collection) {
+ const item = findItemInCollection(collection, action.payload.itemUid);
+ if (item) {
+ item.response.initiatedWsResponse.sortOrder = item.response?.initiatedWsResponse?.sortOrder ? -item.response.initiatedWsResponse.sortOrder : -1;
+ }
+ }
}
}
});
@@ -2996,7 +3019,8 @@ export const {
updateCollectionTagsList,
updateActiveConnections,
runWsRequestEvent,
- wsResponseReceived
+ wsResponseReceived,
+ wsUpdateResponseSortOrder
} = collectionsSlice.actions;
export default collectionsSlice.reducer;
diff --git a/packages/bruno-requests/src/ws/ws-client.js b/packages/bruno-requests/src/ws/ws-client.js
index 3698bab88..961dd0da4 100644
--- a/packages/bruno-requests/src/ws/ws-client.js
+++ b/packages/bruno-requests/src/ws/ws-client.js
@@ -186,7 +186,7 @@ class WsClient {
// Emit message sent event
this.eventCallback('ws:message', requestId, collectionUid, {
message: messageToSend,
- direction: 'outgoing',
+ type: 'outgoing',
timestamp: Date.now()
});
}
@@ -274,14 +274,14 @@ class WsClient {
const message = JSON.parse(data.toString());
this.eventCallback('ws:message', requestId, collectionUid, {
message,
- direction: 'incoming',
+ type: 'incoming',
timestamp: Date.now()
});
} catch (error) {
// If parsing fails, send as raw data
this.eventCallback('ws:message', requestId, collectionUid, {
message: data.toString(),
- direction: 'incoming',
+ type: 'incoming',
timestamp: Date.now()
});
}