diff --git a/packages/bruno-app/src/components/ResponsePane/Timeline/StyledWrapper.js b/packages/bruno-app/src/components/ResponsePane/Timeline/StyledWrapper.js
new file mode 100644
index 000000000..88a5931be
--- /dev/null
+++ b/packages/bruno-app/src/components/ResponsePane/Timeline/StyledWrapper.js
@@ -0,0 +1,23 @@
+import styled from 'styled-components';
+
+const StyledWrapper = styled.div`
+ .line {
+ white-space: pre-line;
+ word-wrap: break-word;
+ word-break: break-all;
+
+ .arrow {
+ opacity: 0.5;
+ }
+
+ &.request {
+ color: ${(props) => props.theme.colors.text.green};
+ }
+
+ &.response {
+ color: ${(props) => props.theme.colors.text.purple};
+ }
+ }
+`;
+
+export default StyledWrapper;
diff --git a/packages/bruno-app/src/components/ResponsePane/Timeline/index.js b/packages/bruno-app/src/components/ResponsePane/Timeline/index.js
new file mode 100644
index 000000000..75477f8f7
--- /dev/null
+++ b/packages/bruno-app/src/components/ResponsePane/Timeline/index.js
@@ -0,0 +1,54 @@
+import React from 'react';
+import forOwn from 'lodash/forOwn';
+import StyledWrapper from './StyledWrapper';
+
+const Timeline = ({ item }) => {
+ const request = item.requestSent || {};
+ const response = item.response || {};
+ const requestHeaders = [];
+ const responseHeaders = response.headers || [];
+
+ forOwn(request.headers, (value, key) => {
+ requestHeaders.push({
+ name: key,
+ value
+ });
+ });
+
+ return (
+
+
+
+ {'>'} {request.method} {request.url}
+
+ {requestHeaders.map((h) => {
+ return (
+
+ {'>'} {h.name}: {h.value}
+
+ );
+ })}
+
+
+ {'>'} data {request.data ? request.data : 'null'}
+
+
+
+
+
+ {'<'} {response.status} {response.statusText}
+
+
+ {responseHeaders.map((h) => {
+ return (
+
+ {'<'} {h[0]}: {h[1]}
+
+ );
+ })}
+
+
+ );
+};
+
+export default Timeline;
diff --git a/packages/bruno-app/src/components/ResponsePane/index.js b/packages/bruno-app/src/components/ResponsePane/index.js
index ace848842..b41578dc1 100644
--- a/packages/bruno-app/src/components/ResponsePane/index.js
+++ b/packages/bruno-app/src/components/ResponsePane/index.js
@@ -10,6 +10,7 @@ import ResponseHeaders from './ResponseHeaders';
import StatusCode from './StatusCode';
import ResponseTime from './ResponseTime';
import ResponseSize from './ResponseSize';
+import Timeline from './Timeline';
import StyledWrapper from './StyledWrapper';
const ResponsePane = ({ rightPaneWidth, item, collection }) => {
@@ -37,6 +38,9 @@ const ResponsePane = ({ rightPaneWidth, item, collection }) => {
case 'headers': {
return ;
}
+ case 'timeline': {
+ return ;
+ }
default: {
return
404 | Not found
;
@@ -84,6 +88,9 @@ const ResponsePane = ({ rightPaneWidth, item, collection }) => {
selectTab('headers')}>
Headers
+ selectTab('timeline')}>
+ Timeline
+
{!isLoading ? (
diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js
index 8a0551791..4fb58d0e4 100644
--- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js
+++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js
@@ -107,13 +107,16 @@ export const sendRequest = (item, collectionUid) => (dispatch, getState) => {
return reject(new Error('Collection not found'));
}
- dispatch(
- requestSent({
- itemUid: item.uid,
- collectionUid: collectionUid,
- cancelTokenUid: cancelTokenUid
- })
- );
+ const onRequestSent = (req) => {
+ dispatch(
+ requestSent({
+ requestSent: req,
+ itemUid: item.uid,
+ collectionUid: collectionUid,
+ cancelTokenUid: cancelTokenUid
+ })
+ );
+ };
const itemCopy = cloneDeep(item);
const collectionCopy = cloneDeep(collection);
@@ -125,7 +128,7 @@ export const sendRequest = (item, collectionUid) => (dispatch, getState) => {
}
}
- sendNetworkRequest(itemCopy, { cancelTokenUid: cancelTokenUid })
+ sendNetworkRequest(itemCopy, { cancelTokenUid: cancelTokenUid }, onRequestSent)
.then((response) => {
return dispatch(
responseReceived({
diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js b/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js
index ab8f22f8e..7ac998102 100644
--- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js
+++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js
@@ -179,12 +179,13 @@ export const collectionsSlice = createSlice({
}
},
requestSent: (state, action) => {
- const { itemUid, collectionUid, cancelTokenUid } = action.payload;
+ const { itemUid, collectionUid, cancelTokenUid, requestSent } = action.payload;
const collection = findCollectionByUid(state.collections, collectionUid);
if (collection) {
const item = findItemInCollection(collection, itemUid);
if (item) {
+ item.requestSent = requestSent
item.response = item.response || {};
item.response.state = 'sending';
item.cancelTokenUid = cancelTokenUid;
diff --git a/packages/bruno-app/src/themes/dark.js b/packages/bruno-app/src/themes/dark.js
index dff763d7c..2669f4557 100644
--- a/packages/bruno-app/src/themes/dark.js
+++ b/packages/bruno-app/src/themes/dark.js
@@ -6,8 +6,10 @@ const darkTheme = {
colors: {
text: {
+ green: 'rgb(11 178 126)',
danger: '#f06f57',
- muted: '#9d9d9d'
+ muted: '#9d9d9d',
+ purple: '#cd56d6'
},
bg: {
danger: '#d03544'
diff --git a/packages/bruno-app/src/themes/light.js b/packages/bruno-app/src/themes/light.js
index da6afd736..12c1eabbf 100644
--- a/packages/bruno-app/src/themes/light.js
+++ b/packages/bruno-app/src/themes/light.js
@@ -6,8 +6,10 @@ const lightTheme = {
colors: {
text: {
+ green: '#047857',
danger: 'rgb(185, 28, 28)',
- muted: '#4b5563'
+ muted: '#4b5563',
+ purple: '#8e44ad',
},
bg: {
danger: '#dc3545'
diff --git a/packages/bruno-app/src/utils/network/browser.js b/packages/bruno-app/src/utils/network/browser.js
deleted file mode 100644
index d32a328c2..000000000
--- a/packages/bruno-app/src/utils/network/browser.js
+++ /dev/null
@@ -1,38 +0,0 @@
-const axios = require('axios');
-import { saveCancelToken, deleteCancelToken } from 'utils/network/cancelTokens';
-
-export const sendHttpRequestInBrowser = async (request, options) => {
- try {
- if (options && options.cancelTokenUid) {
- const cancelToken = axios.CancelToken.source();
- request.cancelToken = cancelToken.token;
- saveCancelToken(options.cancelTokenUid, cancelToken);
- }
-
- const result = await axios(request);
-
- if (options && options.cancelTokenUid) {
- deleteCancelToken(options.cancelTokenUid);
- }
-
- return {
- status: result.status,
- headers: result.headers,
- data: result.data
- };
- } catch (error) {
- if (options && options.cancelTokenUid) {
- deleteCancelToken(options.cancelTokenUid);
- }
-
- if (error.response) {
- return {
- status: error.response.status,
- headers: error.response.headers,
- data: error.response.data
- };
- }
-
- return Promise.reject(error);
- }
-};
diff --git a/packages/bruno-app/src/utils/network/index.js b/packages/bruno-app/src/utils/network/index.js
index 1efde5692..337a15f01 100644
--- a/packages/bruno-app/src/utils/network/index.js
+++ b/packages/bruno-app/src/utils/network/index.js
@@ -2,15 +2,12 @@ import get from 'lodash/get';
import each from 'lodash/each';
import filter from 'lodash/filter';
import qs from 'qs';
-import { sendHttpRequestInBrowser } from './browser';
-import { isElectron } from 'utils/common/platform';
-import cancelTokens, { deleteCancelToken } from 'utils/network/cancelTokens';
-export const sendNetworkRequest = async (item, options) => {
+export const sendNetworkRequest = async (item, options, onRequestSent) => {
return new Promise((resolve, reject) => {
if (['http-request', 'graphql-request'].includes(item.type)) {
const timeStart = Date.now();
- sendHttpRequest(item.draft ? item.draft.request : item.request, options)
+ sendHttpRequest(item.draft ? item.draft.request : item.request, options, onRequestSent)
.then((response) => {
const timeEnd = Date.now();
resolve({
@@ -19,6 +16,7 @@ export const sendNetworkRequest = async (item, options) => {
headers: Object.entries(response.headers),
size: response.headers['content-length'] || 0,
status: response.status,
+ statusText: response.statusText,
duration: timeEnd - timeStart
});
})
@@ -27,7 +25,7 @@ export const sendNetworkRequest = async (item, options) => {
});
};
-const sendHttpRequest = async (request, options) => {
+const sendHttpRequest = async (request, options, onRequestSent) => {
return new Promise((resolve, reject) => {
const { ipcRenderer } = window;
@@ -91,28 +89,20 @@ const sendHttpRequest = async (request, options) => {
console.log('>>> Sending Request');
console.log(axiosRequest);
- if (isElectron()) {
- ipcRenderer.invoke('send-http-request', axiosRequest, options).then(resolve).catch(reject);
- } else {
- sendHttpRequestInBrowser(axiosRequest, options).then(resolve).catch(reject);
- }
+ onRequestSent(axiosRequest);
+
+ ipcRenderer
+ .invoke('send-http-request', axiosRequest, options)
+ .then(resolve)
+ .catch(reject);
});
};
export const cancelNetworkRequest = async (cancelTokenUid) => {
- if (isElectron()) {
- return new Promise((resolve, reject) => {
- ipcRenderer.invoke('cancel-http-request', cancelTokenUid).then(resolve).catch(reject);
- });
- }
-
return new Promise((resolve, reject) => {
- if (cancelTokenUid && cancelTokens[cancelTokenUid]) {
- cancelTokens[cancelTokenUid].cancel();
- deleteCancelToken(cancelTokenUid);
- resolve();
- } else {
- reject(new Error('cancel token not found'));
- }
+ ipcRenderer
+ .invoke('cancel-http-request', cancelTokenUid)
+ .then(resolve)
+ .catch(reject);
});
};
diff --git a/packages/bruno-electron/package.json b/packages/bruno-electron/package.json
index 2389bc311..f9d346ed5 100644
--- a/packages/bruno-electron/package.json
+++ b/packages/bruno-electron/package.json
@@ -31,7 +31,7 @@
"dmg-license": "^1.0.11"
},
"devDependencies": {
- "electron": "^21.1.1",
+ "electron": "21.1.1",
"electron-builder": "23.0.2",
"electron-icon-maker": "^0.0.5"
}
diff --git a/packages/bruno-electron/src/ipc/network.js b/packages/bruno-electron/src/ipc/network.js
index f290c0243..c1f8e6d95 100644
--- a/packages/bruno-electron/src/ipc/network.js
+++ b/packages/bruno-electron/src/ipc/network.js
@@ -33,6 +33,7 @@ const registerNetworkIpc = () => {
return {
status: result.status,
+ statusText: result.statusText,
headers: result.headers,
data: result.data
};
@@ -44,6 +45,7 @@ const registerNetworkIpc = () => {
if(error.response) {
return {
status: error.response.status,
+ statusText: error.response.statusText,
headers: error.response.headers,
data: error.response.data
}