mirror of
https://github.com/usebruno/bruno.git
synced 2024-11-21 23:43:15 +01:00
feat: response timeline
This commit is contained in:
parent
2be3e4bf69
commit
dd71c9e71b
@ -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;
|
@ -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 (
|
||||
<StyledWrapper className="px-3 pb-4 w-full">
|
||||
<div>
|
||||
<pre className='line request font-bold'>
|
||||
<span className="arrow">{'>'}</span> {request.method} {request.url}
|
||||
</pre>
|
||||
{requestHeaders.map((h) => {
|
||||
return (
|
||||
<pre className='line request' key={h.name}>
|
||||
<span className="arrow">{'>'}</span> {h.name}: {h.value}
|
||||
</pre>
|
||||
);
|
||||
})}
|
||||
|
||||
<pre className='line request'>
|
||||
<span className="arrow">{'>'}</span> data {request.data ? request.data : 'null'}
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
<div className='mt-4'>
|
||||
<pre className='line response font-bold'>
|
||||
<span className="arrow">{'<'}</span> {response.status} {response.statusText}
|
||||
</pre>
|
||||
|
||||
{responseHeaders.map((h) => {
|
||||
return (
|
||||
<pre className='line response' key={h[0]}>
|
||||
<span className="arrow">{'<'}</span> {h[0]}: {h[1]}
|
||||
</pre>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default Timeline;
|
@ -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 <ResponseHeaders headers={response.headers} />;
|
||||
}
|
||||
case 'timeline': {
|
||||
return <Timeline item={item} />;
|
||||
}
|
||||
|
||||
default: {
|
||||
return <div>404 | Not found</div>;
|
||||
@ -84,6 +88,9 @@ const ResponsePane = ({ rightPaneWidth, item, collection }) => {
|
||||
<div className={getTabClassname('headers')} role="tab" onClick={() => selectTab('headers')}>
|
||||
Headers
|
||||
</div>
|
||||
<div className={getTabClassname('timeline')} role="tab" onClick={() => selectTab('timeline')}>
|
||||
Timeline
|
||||
</div>
|
||||
{!isLoading ? (
|
||||
<div className="flex flex-grow justify-end items-center">
|
||||
<StatusCode status={response.status} />
|
||||
|
@ -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({
|
||||
|
@ -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;
|
||||
|
@ -6,8 +6,10 @@ const darkTheme = {
|
||||
|
||||
colors: {
|
||||
text: {
|
||||
green: 'rgb(11 178 126)',
|
||||
danger: '#f06f57',
|
||||
muted: '#9d9d9d'
|
||||
muted: '#9d9d9d',
|
||||
purple: '#cd56d6'
|
||||
},
|
||||
bg: {
|
||||
danger: '#d03544'
|
||||
|
@ -6,8 +6,10 @@ const lightTheme = {
|
||||
|
||||
colors: {
|
||||
text: {
|
||||
green: '#047857',
|
||||
danger: 'rgb(185, 28, 28)',
|
||||
muted: '#4b5563'
|
||||
muted: '#4b5563',
|
||||
purple: '#8e44ad',
|
||||
},
|
||||
bg: {
|
||||
danger: '#dc3545'
|
||||
|
@ -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);
|
||||
}
|
||||
};
|
@ -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);
|
||||
});
|
||||
};
|
||||
|
@ -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"
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user