diff --git a/frontend/app/components/shared/DevTools/NetworkPanel/NetworkPanel.tsx b/frontend/app/components/shared/DevTools/NetworkPanel/NetworkPanel.tsx index 685b219bf..6cb6b66bc 100644 --- a/frontend/app/components/shared/DevTools/NetworkPanel/NetworkPanel.tsx +++ b/frontend/app/components/shared/DevTools/NetworkPanel/NetworkPanel.tsx @@ -67,36 +67,36 @@ export function renderStart(r: any) { ); } -const renderXHRText = () => ( - - {XHR} - - Use our{' '} - - Fetch plugin - - {' to capture HTTP requests and responses, including status codes and bodies.'}
- We also provide{' '} - - support for GraphQL - - {' for easy debugging of your queries.'} - - } - className="ml-1" - /> -
-); +// const renderXHRText = () => ( +// +// {XHR} +// +// Use our{' '} +// +// Fetch plugin +// +// {' to capture HTTP requests and responses, including status codes and bodies.'}
+// We also provide{' '} +// +// support for GraphQL +// +// {' for easy debugging of your queries.'} +// +// } +// className="ml-1" +// /> +//
+// ); function renderSize(r: any) { if (r.responseBodySize) return formatBytes(r.responseBodySize); @@ -180,6 +180,7 @@ function NetworkPanel(props: Props) { const [sortAscending, setSortAscending] = useState(true); const [filter, setFilter] = useState(''); const [showOnlyErrors, setShowOnlyErrors] = useState(false); + const [activeRequest, setActiveRequest] = useState(false ) const onTabClick = (activeTab: any) => setActiveTab(activeTab); const onFilterChange = ({ target: { value } }: any) => setFilter(value); const additionalHeight = 0; @@ -237,7 +238,7 @@ function NetworkPanel(props: Props) { } const onRowClick = (row: any) => { - showModal(, { + showModal(, { right: true, }); }; diff --git a/frontend/app/components/shared/FetchDetailsModal/FetchDetailsModal.js b/frontend/app/components/shared/FetchDetailsModal/FetchDetailsModal.js deleted file mode 100644 index 1f7b21a1d..000000000 --- a/frontend/app/components/shared/FetchDetailsModal/FetchDetailsModal.js +++ /dev/null @@ -1,257 +0,0 @@ -import React from 'react'; -import { JSONTree, NoContent, Button, Tabs, Icon } from 'UI'; -import cn from 'classnames'; -import stl from './fetchDetails.module.css'; -import Headers from './components/Headers'; -import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG'; -import { TYPES } from 'Types/session/resource'; -import { formatBytes } from 'App/utils'; -import CopyText from 'Shared/CopyText'; - -const HEADERS = 'HEADERS'; -const REQUEST = 'REQUEST'; -const RESPONSE = 'RESPONSE'; - -const TABS = [HEADERS, REQUEST, RESPONSE].map((tab) => ({ text: tab, key: tab })); - -export default class FetchDetailsModal extends React.PureComponent { - state = { activeTab: REQUEST, tabs: [] }; - - onTabClick = (activeTab) => this.setState({ activeTab }); - - componentDidMount() { - this.checkTabs(); - } - - renderActiveTab = (tab) => { - const { - resource: { payload, response = this.props.resource.body }, - } = this.props; - let jsonPayload, - jsonResponse, - requestHeaders, - responseHeaders = undefined; - - try { - jsonPayload = typeof payload === 'string' ? JSON.parse(payload) : payload; - requestHeaders = jsonPayload.headers; - jsonPayload.body = - typeof jsonPayload.body === 'string' ? JSON.parse(jsonPayload.body) : jsonPayload.body; - delete jsonPayload.headers; - } catch (e) {} - - try { - jsonResponse = typeof response === 'string' ? JSON.parse(response) : response; - responseHeaders = jsonResponse.headers; - jsonResponse.body = - typeof jsonResponse.body === 'string' ? JSON.parse(jsonResponse.body) : jsonResponse.body; - delete jsonResponse.headers; - } catch (e) {} - - switch (tab) { - case REQUEST: - return ( - - -
Body is Empty.
- - } - size="small" - show={!payload} - // animatedIcon="no-results" - > -
-
- {jsonPayload === undefined ? ( -
{payload}
- ) : ( - - )} -
-
-
- - ); - case RESPONSE: - return ( - - -
Body is Empty.
-
- } - size="small" - show={!response} - // animatedIcon="no-results" - > -
-
- {jsonResponse === undefined ? ( -
{response}
- ) : ( - - )} -
-
-
- - ); - case HEADERS: - return ; - } - }; - - componentDidUpdate(prevProps) { - if (prevProps.resource.index === this.props.resource.index) return; - - this.checkTabs(); - } - - checkTabs() { - const { - resource: { payload, response, body }, - isResult, - } = this.props; - const _tabs = TABS; - // const _tabs = TABS.filter(t => { - // if (t.key == REQUEST && !!payload) { - // return true - // } - - // if (t.key == RESPONSE && !!response) { - // return true; - // } - - // return false; - // }) - this.setState({ tabs: _tabs, activeTab: _tabs.length > 0 ? _tabs[0].key : null }); - } - - render() { - const { - resource, - fetchPresented, - nextClick, - prevClick, - first = false, - last = false, - } = this.props; - const { method, url, duration } = resource; - const { activeTab, tabs } = this.state; - const _duration = parseInt(duration); - - return ( -
-
Network Request
-
-
Name
-
- {resource.name} -
-
- -
-
Type
-
- {resource.type} -
-
- - {!!resource.decodedBodySize && ( -
-
Size
-
- {formatBytes(resource.decodedBodySize)} -
-
- )} - - {method && ( -
-
Request Method
-
- {resource.method} -
-
- )} - - {resource.status && ( -
-
Status
-
- {resource.status === '200' && ( -
- )} - {resource.status} -
-
- )} - - {!!_duration && ( -
-
Time
-
- {_duration} ms -
-
- )} - - {resource.type === TYPES.XHR && !fetchPresented && ( -
-
- - Get more out of network requests -
-
    -
  • - Integrate{' '} - - Fetch plugin - {' '} - to capture fetch payloads. -
  • -
  • - Find a detailed{' '} - - video tutorial - {' '} - to understand practical example of how to use fetch plugin. -
  • -
-
- )} - -
- {resource.type === TYPES.XHR && fetchPresented && ( -
- -
- {this.renderActiveTab(activeTab)} -
-
- )} - - {/*
- - -
*/} -
-
- ); - } -} diff --git a/frontend/app/components/shared/FetchDetailsModal/FetchDetailsModal.tsx b/frontend/app/components/shared/FetchDetailsModal/FetchDetailsModal.tsx new file mode 100644 index 000000000..8ae23e0f4 --- /dev/null +++ b/frontend/app/components/shared/FetchDetailsModal/FetchDetailsModal.tsx @@ -0,0 +1,63 @@ +import React, { useEffect, useState } from 'react'; +import FetchBasicDetails from './components/FetchBasicDetails'; +import { Button } from 'UI'; +import FetchPluginMessage from './components/FetchPluginMessage'; +import { TYPES } from 'Types/session/resource'; +import FetchTabs from './components/FetchTabs/FetchTabs'; + +interface Props { + resource: any; + rows: any; + fetchPresented?: boolean; +} +function FetchDetailsModal(props: Props) { + const { rows, fetchPresented = false } = props; + const [resource, setResource] = useState(props.resource); + const [first, setFirst] = useState(false); + const [last, setLast] = useState(false); + + useEffect(() => { + const index = rows.indexOf(resource); + const length = rows.length - 1; + setFirst(index === 0); + setLast(index === length); + }, [resource]); + + const prevClick = () => { + const index = rows.indexOf(resource); + if (index > 0) { + setResource(rows[index - 1]); + } + }; + + const nextClick = () => { + const index = rows.indexOf(resource); + if (index < rows.length - 1) { + setResource(rows[index + 1]); + } + }; + + return ( +
+
Network Request
+ + + {resource.type === TYPES.XHR && !fetchPresented && } + + {resource.type === TYPES.XHR && fetchPresented && } + + {rows && rows.length > 0 && ( +
+ + +
+ )} +
+ ); +} + +export default FetchDetailsModal; diff --git a/frontend/app/components/shared/FetchDetailsModal/components/FetchBasicDetails/FetchBasicDetails.tsx b/frontend/app/components/shared/FetchDetailsModal/components/FetchBasicDetails/FetchBasicDetails.tsx new file mode 100644 index 000000000..6c81a744c --- /dev/null +++ b/frontend/app/components/shared/FetchDetailsModal/components/FetchBasicDetails/FetchBasicDetails.tsx @@ -0,0 +1,70 @@ +import React from 'react'; +import { Icon } from 'UI'; +import { formatBytes } from 'App/utils'; +import CopyText from 'Shared/CopyText'; +import { TYPES } from 'Types/session/resource'; + +interface Props { + resource: any; +} +function FetchBasicDetails({ resource }: Props) { + const _duration = parseInt(resource.duration); + return ( +
+
+
Name
+
+ {resource.name} +
+
+ +
+
Type
+
+ {resource.type} +
+
+ + {!!resource.decodedBodySize && ( +
+
Size
+
+ {formatBytes(resource.decodedBodySize)} +
+
+ )} + + {resource.method && ( +
+
Request Method
+
+ {resource.method} +
+
+ )} + + {resource.status && ( +
+
Status
+
+ {resource.status === '200' && ( +
+ )} + {resource.status} +
+
+ )} + + {!!_duration && ( +
+
Time
+
+ {_duration} ms +
+
+ )} +
+ ); +} + +export default FetchBasicDetails; diff --git a/frontend/app/components/shared/FetchDetailsModal/components/FetchBasicDetails/index.ts b/frontend/app/components/shared/FetchDetailsModal/components/FetchBasicDetails/index.ts new file mode 100644 index 000000000..86f95ff45 --- /dev/null +++ b/frontend/app/components/shared/FetchDetailsModal/components/FetchBasicDetails/index.ts @@ -0,0 +1 @@ +export { default } from './FetchBasicDetails'; diff --git a/frontend/app/components/shared/FetchDetailsModal/components/FetchPluginMessage/FetchPluginMessage.tsx b/frontend/app/components/shared/FetchDetailsModal/components/FetchPluginMessage/FetchPluginMessage.tsx new file mode 100644 index 000000000..8699b8cb6 --- /dev/null +++ b/frontend/app/components/shared/FetchDetailsModal/components/FetchPluginMessage/FetchPluginMessage.tsx @@ -0,0 +1,31 @@ +import React from 'react'; +import { Icon } from 'UI'; + +function FetchPluginMessage() { + return ( +
+
+ + Get more out of network requests +
+
    +
  • + Integrate{' '} + + Fetch plugin + {' '} + to capture fetch payloads. +
  • +
  • + Find a detailed{' '} + + video tutorial + {' '} + to understand practical example of how to use fetch plugin. +
  • +
+
+ ); +} + +export default FetchPluginMessage; diff --git a/frontend/app/components/shared/FetchDetailsModal/components/FetchPluginMessage/index.ts b/frontend/app/components/shared/FetchDetailsModal/components/FetchPluginMessage/index.ts new file mode 100644 index 000000000..df224dbbf --- /dev/null +++ b/frontend/app/components/shared/FetchDetailsModal/components/FetchPluginMessage/index.ts @@ -0,0 +1 @@ +export { default } from './FetchPluginMessage'; diff --git a/frontend/app/components/shared/FetchDetailsModal/components/FetchTabs/FetchTabs.tsx b/frontend/app/components/shared/FetchDetailsModal/components/FetchTabs/FetchTabs.tsx new file mode 100644 index 000000000..837a61ec6 --- /dev/null +++ b/frontend/app/components/shared/FetchDetailsModal/components/FetchTabs/FetchTabs.tsx @@ -0,0 +1,112 @@ +import React, { useEffect, useState } from 'react'; +import Headers from '../Headers'; +import { JSONTree, Tabs, NoContent } from 'UI'; +import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG'; + +const HEADERS = 'HEADERS'; +const REQUEST = 'REQUEST'; +const RESPONSE = 'RESPONSE'; +const TABS = [HEADERS, REQUEST, RESPONSE].map((tab) => ({ text: tab, key: tab })); + +interface Props { + resource: any; +} +function FetchTabs(props: Props) { + const { resource } = props; + const [activeTab, setActiveTab] = useState(HEADERS); + const onTabClick = (tab: string) => setActiveTab(tab); + const [jsonPayload, setJsonPayload] = useState(null); + const [jsonResponse, setJsonResponse] = useState(null); + const [requestHeaders, setRequestHeaders] = useState(null); + const [responseHeaders, setResponseHeaders] = useState(null); + + useEffect(() => { + const { payload, response } = resource; + + try { + let jsonPayload = typeof payload === 'string' ? JSON.parse(payload) : payload; + let requestHeaders = jsonPayload.headers; + jsonPayload.body = + typeof jsonPayload.body === 'string' ? JSON.parse(jsonPayload.body) : jsonPayload.body; + delete jsonPayload.headers; + setJsonPayload(jsonPayload); + setRequestHeaders(requestHeaders); + } catch (e) {} + + try { + let jsonResponse = typeof response === 'string' ? JSON.parse(response) : response; + let responseHeaders = jsonResponse.headers; + jsonResponse.body = + typeof jsonResponse.body === 'string' ? JSON.parse(jsonResponse.body) : jsonResponse.body; + delete jsonResponse.headers; + setJsonResponse(jsonResponse); + setResponseHeaders(responseHeaders); + } catch (e) {} + }, [resource, activeTab]); + + const renderActiveTab = () => { + const { payload, response } = resource; + switch (activeTab) { + case REQUEST: + return ( + + +
Body is Empty.
+
+ } + size="small" + show={!payload} + // animatedIcon="no-results" + > +
+
+ {jsonPayload === undefined ? ( +
{payload}
+ ) : ( + + )} +
+
+
+ + ); + case RESPONSE: + return ( + + +
Body is Empty.
+
+ } + size="small" + show={!response} + // animatedIcon="no-results" + > +
+
+ {jsonResponse === undefined ? ( +
{response}
+ ) : ( + + )} +
+
+
+ + ); + case HEADERS: + return ; + } + }; + return ( +
+ +
{renderActiveTab()}
+
+ ); +} + +export default FetchTabs; diff --git a/frontend/app/components/shared/FetchDetailsModal/components/Headers/Headers.tsx b/frontend/app/components/shared/FetchDetailsModal/components/Headers/Headers.tsx index c2ec31a07..78c4a5e64 100644 --- a/frontend/app/components/shared/FetchDetailsModal/components/Headers/Headers.tsx +++ b/frontend/app/components/shared/FetchDetailsModal/components/Headers/Headers.tsx @@ -1,9 +1,13 @@ -import React from 'react' -import { NoContent, TextEllipsis } from 'UI' -import stl from './headers.module.css' +import React from 'react'; +import { NoContent, TextEllipsis } from 'UI'; +import stl from './headers.module.css'; import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG'; -function Headers(props) { +interface Props { + requestHeaders: any; + responseHeaders: any; +} +function Headers(props: Props) { return (
} size="small" - show={ !props.requestHeaders && !props.responseHeaders } + show={!props.requestHeaders && !props.responseHeaders} // animatedIcon="no-results" > - { props.requestHeaders && ( + {props.requestHeaders && ( <>
Request Headers
- { - Object.keys(props.requestHeaders).map(h => ( -
- {h}: - {props.requestHeaders[h]} -
- )) - } + {Object.keys(props.requestHeaders).map((h) => ( +
+ {h}: + {props.requestHeaders[h]} +
+ ))}

)} - - { props.responseHeaders && ( + + {props.responseHeaders && (
Response Headers
- { - Object.keys(props.responseHeaders).map(h => ( -
- {h}: - {props.responseHeaders[h]} -
- )) - } + {Object.keys(props.responseHeaders).map((h) => ( +
+ {h}: + {props.responseHeaders[h]} +
+ ))}
)}
@@ -52,4 +52,4 @@ function Headers(props) { ); } -export default Headers; \ No newline at end of file +export default Headers; diff --git a/frontend/app/components/ui/Tabs/Tabs.js b/frontend/app/components/ui/Tabs/Tabs.js index cd9921161..248dae3dd 100644 --- a/frontend/app/components/ui/Tabs/Tabs.js +++ b/frontend/app/components/ui/Tabs/Tabs.js @@ -2,7 +2,7 @@ import React from 'react'; import cn from 'classnames'; import stl from './tabs.module.css'; -const Tabs = ({ tabs, active, onClick, border = true, className }) => ( +const Tabs = ({ tabs, active, onClick, border = true, className = '' }) => (
{ tabs.map(({ key, text, hidden = false, disabled = false }) => (