forked from extern/bruno
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 StatusCode from './StatusCode';
|
||||||
import ResponseTime from './ResponseTime';
|
import ResponseTime from './ResponseTime';
|
||||||
import ResponseSize from './ResponseSize';
|
import ResponseSize from './ResponseSize';
|
||||||
|
import Timeline from './Timeline';
|
||||||
import StyledWrapper from './StyledWrapper';
|
import StyledWrapper from './StyledWrapper';
|
||||||
|
|
||||||
const ResponsePane = ({ rightPaneWidth, item, collection }) => {
|
const ResponsePane = ({ rightPaneWidth, item, collection }) => {
|
||||||
@ -37,6 +38,9 @@ const ResponsePane = ({ rightPaneWidth, item, collection }) => {
|
|||||||
case 'headers': {
|
case 'headers': {
|
||||||
return <ResponseHeaders headers={response.headers} />;
|
return <ResponseHeaders headers={response.headers} />;
|
||||||
}
|
}
|
||||||
|
case 'timeline': {
|
||||||
|
return <Timeline item={item} />;
|
||||||
|
}
|
||||||
|
|
||||||
default: {
|
default: {
|
||||||
return <div>404 | Not found</div>;
|
return <div>404 | Not found</div>;
|
||||||
@ -84,6 +88,9 @@ const ResponsePane = ({ rightPaneWidth, item, collection }) => {
|
|||||||
<div className={getTabClassname('headers')} role="tab" onClick={() => selectTab('headers')}>
|
<div className={getTabClassname('headers')} role="tab" onClick={() => selectTab('headers')}>
|
||||||
Headers
|
Headers
|
||||||
</div>
|
</div>
|
||||||
|
<div className={getTabClassname('timeline')} role="tab" onClick={() => selectTab('timeline')}>
|
||||||
|
Timeline
|
||||||
|
</div>
|
||||||
{!isLoading ? (
|
{!isLoading ? (
|
||||||
<div className="flex flex-grow justify-end items-center">
|
<div className="flex flex-grow justify-end items-center">
|
||||||
<StatusCode status={response.status} />
|
<StatusCode status={response.status} />
|
||||||
|
@ -107,13 +107,16 @@ export const sendRequest = (item, collectionUid) => (dispatch, getState) => {
|
|||||||
return reject(new Error('Collection not found'));
|
return reject(new Error('Collection not found'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const onRequestSent = (req) => {
|
||||||
dispatch(
|
dispatch(
|
||||||
requestSent({
|
requestSent({
|
||||||
|
requestSent: req,
|
||||||
itemUid: item.uid,
|
itemUid: item.uid,
|
||||||
collectionUid: collectionUid,
|
collectionUid: collectionUid,
|
||||||
cancelTokenUid: cancelTokenUid
|
cancelTokenUid: cancelTokenUid
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const itemCopy = cloneDeep(item);
|
const itemCopy = cloneDeep(item);
|
||||||
const collectionCopy = cloneDeep(collection);
|
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) => {
|
.then((response) => {
|
||||||
return dispatch(
|
return dispatch(
|
||||||
responseReceived({
|
responseReceived({
|
||||||
|
@ -179,12 +179,13 @@ export const collectionsSlice = createSlice({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
requestSent: (state, action) => {
|
requestSent: (state, action) => {
|
||||||
const { itemUid, collectionUid, cancelTokenUid } = action.payload;
|
const { itemUid, collectionUid, cancelTokenUid, requestSent } = action.payload;
|
||||||
const collection = findCollectionByUid(state.collections, collectionUid);
|
const collection = findCollectionByUid(state.collections, collectionUid);
|
||||||
|
|
||||||
if (collection) {
|
if (collection) {
|
||||||
const item = findItemInCollection(collection, itemUid);
|
const item = findItemInCollection(collection, itemUid);
|
||||||
if (item) {
|
if (item) {
|
||||||
|
item.requestSent = requestSent
|
||||||
item.response = item.response || {};
|
item.response = item.response || {};
|
||||||
item.response.state = 'sending';
|
item.response.state = 'sending';
|
||||||
item.cancelTokenUid = cancelTokenUid;
|
item.cancelTokenUid = cancelTokenUid;
|
||||||
|
@ -6,8 +6,10 @@ const darkTheme = {
|
|||||||
|
|
||||||
colors: {
|
colors: {
|
||||||
text: {
|
text: {
|
||||||
|
green: 'rgb(11 178 126)',
|
||||||
danger: '#f06f57',
|
danger: '#f06f57',
|
||||||
muted: '#9d9d9d'
|
muted: '#9d9d9d',
|
||||||
|
purple: '#cd56d6'
|
||||||
},
|
},
|
||||||
bg: {
|
bg: {
|
||||||
danger: '#d03544'
|
danger: '#d03544'
|
||||||
|
@ -6,8 +6,10 @@ const lightTheme = {
|
|||||||
|
|
||||||
colors: {
|
colors: {
|
||||||
text: {
|
text: {
|
||||||
|
green: '#047857',
|
||||||
danger: 'rgb(185, 28, 28)',
|
danger: 'rgb(185, 28, 28)',
|
||||||
muted: '#4b5563'
|
muted: '#4b5563',
|
||||||
|
purple: '#8e44ad',
|
||||||
},
|
},
|
||||||
bg: {
|
bg: {
|
||||||
danger: '#dc3545'
|
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 each from 'lodash/each';
|
||||||
import filter from 'lodash/filter';
|
import filter from 'lodash/filter';
|
||||||
import qs from 'qs';
|
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) => {
|
return new Promise((resolve, reject) => {
|
||||||
if (['http-request', 'graphql-request'].includes(item.type)) {
|
if (['http-request', 'graphql-request'].includes(item.type)) {
|
||||||
const timeStart = Date.now();
|
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) => {
|
.then((response) => {
|
||||||
const timeEnd = Date.now();
|
const timeEnd = Date.now();
|
||||||
resolve({
|
resolve({
|
||||||
@ -19,6 +16,7 @@ export const sendNetworkRequest = async (item, options) => {
|
|||||||
headers: Object.entries(response.headers),
|
headers: Object.entries(response.headers),
|
||||||
size: response.headers['content-length'] || 0,
|
size: response.headers['content-length'] || 0,
|
||||||
status: response.status,
|
status: response.status,
|
||||||
|
statusText: response.statusText,
|
||||||
duration: timeEnd - timeStart
|
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) => {
|
return new Promise((resolve, reject) => {
|
||||||
const { ipcRenderer } = window;
|
const { ipcRenderer } = window;
|
||||||
|
|
||||||
@ -91,28 +89,20 @@ const sendHttpRequest = async (request, options) => {
|
|||||||
console.log('>>> Sending Request');
|
console.log('>>> Sending Request');
|
||||||
console.log(axiosRequest);
|
console.log(axiosRequest);
|
||||||
|
|
||||||
if (isElectron()) {
|
onRequestSent(axiosRequest);
|
||||||
ipcRenderer.invoke('send-http-request', axiosRequest, options).then(resolve).catch(reject);
|
|
||||||
} else {
|
ipcRenderer
|
||||||
sendHttpRequestInBrowser(axiosRequest, options).then(resolve).catch(reject);
|
.invoke('send-http-request', axiosRequest, options)
|
||||||
}
|
.then(resolve)
|
||||||
|
.catch(reject);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const cancelNetworkRequest = async (cancelTokenUid) => {
|
export const cancelNetworkRequest = async (cancelTokenUid) => {
|
||||||
if (isElectron()) {
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
ipcRenderer.invoke('cancel-http-request', cancelTokenUid).then(resolve).catch(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'));
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -31,7 +31,7 @@
|
|||||||
"dmg-license": "^1.0.11"
|
"dmg-license": "^1.0.11"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"electron": "^21.1.1",
|
"electron": "21.1.1",
|
||||||
"electron-builder": "23.0.2",
|
"electron-builder": "23.0.2",
|
||||||
"electron-icon-maker": "^0.0.5"
|
"electron-icon-maker": "^0.0.5"
|
||||||
}
|
}
|
||||||
|
@ -33,6 +33,7 @@ const registerNetworkIpc = () => {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
status: result.status,
|
status: result.status,
|
||||||
|
statusText: result.statusText,
|
||||||
headers: result.headers,
|
headers: result.headers,
|
||||||
data: result.data
|
data: result.data
|
||||||
};
|
};
|
||||||
@ -44,6 +45,7 @@ const registerNetworkIpc = () => {
|
|||||||
if(error.response) {
|
if(error.response) {
|
||||||
return {
|
return {
|
||||||
status: error.response.status,
|
status: error.response.status,
|
||||||
|
statusText: error.response.statusText,
|
||||||
headers: error.response.headers,
|
headers: error.response.headers,
|
||||||
data: error.response.data
|
data: error.response.data
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user