feat: response timeline

This commit is contained in:
Anoop M D 2023-01-18 20:55:10 +05:30
parent 2be3e4bf69
commit dd71c9e71b
11 changed files with 120 additions and 74 deletions

View File

@ -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;

View File

@ -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;

View File

@ -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} />

View File

@ -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({

View File

@ -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;

View File

@ -6,8 +6,10 @@ const darkTheme = {
colors: {
text: {
green: 'rgb(11 178 126)',
danger: '#f06f57',
muted: '#9d9d9d'
muted: '#9d9d9d',
purple: '#cd56d6'
},
bg: {
danger: '#d03544'

View File

@ -6,8 +6,10 @@ const lightTheme = {
colors: {
text: {
green: '#047857',
danger: 'rgb(185, 28, 28)',
muted: '#4b5563'
muted: '#4b5563',
purple: '#8e44ad',
},
bg: {
danger: '#dc3545'

View File

@ -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);
}
};

View File

@ -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);
});
};

View File

@ -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"
}

View File

@ -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
}