From 7c0da02df414ea0ab708aff56923a651be61abf5 Mon Sep 17 00:00:00 2001 From: Yu Yi <58987452+yuyi-sl@users.noreply.github.com> Date: Sat, 10 Aug 2024 19:24:16 +0200 Subject: [PATCH] Redesign main table (#139) * use new variable for 8px size * wip - grid * tooltip timechart * layout on selected request * styling clean up * remove scroll to selected request * collapse all besides General * update test snapshots * remove unused deps * adjust layout * option to hide waterfall * remove react-window dep * decrease padding and font-size * adjust styling for request details * clean up * virtualize network table body --- src/Components/Common/Dropdown.styles.scss | 2 +- src/Components/Common/Tooltip/Tooltip.jsx | 5 +- .../Common/Tooltip/Tooltip.styles.scss | 1 + src/Components/Import/ImportHAR.jsx | 23 +- .../NetworkTable/NetworkCellValue.jsx | 28 +- .../NetworkTable/NetworkTable.styles.scss | 104 +++ .../NetworkTable/NetworkTableBody.jsx | 77 +++ .../NetworkTable/NetworkTableFooter.jsx | 9 +- .../NetworkTableFooter.styles.scss | 24 - .../NetworkTable/NetworkTableHeader.jsx | 54 +- .../NetworkTableHeader.styles.scss | 87 --- .../NetworkTable/NetworkTableRow.jsx | 83 ++- src/Components/NetworkTable/TimeChart.jsx | 5 +- .../NetworkTable/TimeChart.styles.scss | 2 +- .../NetworkTable/TimeChartTooltip.jsx | 5 +- src/Components/ReqDetail/Headers.jsx | 9 +- src/Components/ReqDetail/Headers.styles.scss | 15 +- .../ReqDetail/headers/HeaderInfo.jsx | 11 +- src/Containers/MainContainer.jsx | 14 +- src/Containers/MainContainer.styles.scss | 6 +- src/Containers/NetworkTableContainer.jsx | 72 +- .../NetworkTableContainer.styles.scss | 43 +- src/Containers/ReqDetailContainer.styles.scss | 14 +- src/NetworkViewer.jsx | 3 +- src/constants.js | 49 +- src/hooks/useResizeObserver.js | 35 + src/state/network/Context.jsx | 1 + src/state/network/NetworkProvider.jsx | 6 +- src/state/network/actions.js | 5 + src/state/network/reducer.js | 6 + src/state/network/types.js | 1 + src/styles/variables.scss | 8 +- src/utils.js | 33 +- .../__snapshots__/Search.spec.jsx.snap | 2 + .../__snapshots__/ImportHar.spec.jsx.snap | 3 +- .../NetworkTable/NetworkTableHeader.spec.jsx | 5 +- .../NetworkTable/NetworkTableRow.spec.jsx | 12 +- .../NetworkTableFooter.spec.jsx.snap | 2 +- .../NetworkTableHeader.spec.jsx.snap | 168 +++-- .../NetworkTableRow.spec.jsx.snap | 639 ++++++++++-------- .../__snapshots__/Headers.spec.jsx.snap | 61 +- .../__snapshots__/HeaderInfo.spec.jsx.snap | 3 +- .../FilterContainer.spec.jsx.snap | 3 +- .../__snapshots__/MainContainer.spec.jsx.snap | 12 +- .../NetworkTableContainer.spec.jsx.snap | 7 +- .../ReqDetailContainer.spec.jsx.snap | 2 + .../TimelineContainer.spec.jsx.snap | 2 + .../__snapshots__/NetworkViewer.spec.jsx.snap | 6 +- .../NetworkProvider.spec.jsx.snap | 4 + 49 files changed, 1025 insertions(+), 746 deletions(-) create mode 100644 src/Components/NetworkTable/NetworkTable.styles.scss create mode 100644 src/Components/NetworkTable/NetworkTableBody.jsx delete mode 100644 src/Components/NetworkTable/NetworkTableFooter.styles.scss delete mode 100644 src/Components/NetworkTable/NetworkTableHeader.styles.scss create mode 100644 src/hooks/useResizeObserver.js diff --git a/src/Components/Common/Dropdown.styles.scss b/src/Components/Common/Dropdown.styles.scss index 086c8b4..6576b83 100644 --- a/src/Components/Common/Dropdown.styles.scss +++ b/src/Components/Common/Dropdown.styles.scss @@ -6,7 +6,7 @@ .dropdown-toggle { display: flex; justify-content: space-between; - width: 7.5rem; + width: 8.5rem; font-weight: 500; font-size: $font-size-h5; diff --git a/src/Components/Common/Tooltip/Tooltip.jsx b/src/Components/Common/Tooltip/Tooltip.jsx index 3afaebf..d1d5f39 100644 --- a/src/Components/Common/Tooltip/Tooltip.jsx +++ b/src/Components/Common/Tooltip/Tooltip.jsx @@ -22,10 +22,7 @@ const Tooltip = forwardRef(({ const targetRef = useObjectRef(forwardedRef); const tooltipRef = useRef(null); - const state = useTooltipTriggerState({ - // isOpen: true, - delay, - }); + const state = useTooltipTriggerState({ delay }); const { overlayProps, diff --git a/src/Components/Common/Tooltip/Tooltip.styles.scss b/src/Components/Common/Tooltip/Tooltip.styles.scss index 78f2975..31da101 100644 --- a/src/Components/Common/Tooltip/Tooltip.styles.scss +++ b/src/Components/Common/Tooltip/Tooltip.styles.scss @@ -11,6 +11,7 @@ display: inline-block; background-color: $brand-primary-dark-gray; color: $white-100; + font-size: $font-size-small; padding: $size-xs $size-xs-s; border: $border; border-radius: $border-radius-base; diff --git a/src/Components/Import/ImportHAR.jsx b/src/Components/Import/ImportHAR.jsx index 43efee7..71502ac 100644 --- a/src/Components/Import/ImportHAR.jsx +++ b/src/Components/Import/ImportHAR.jsx @@ -4,8 +4,6 @@ import { useDropzone } from 'react-dropzone'; import { useNetwork } from './../../state/network/Context'; import Styles from './ImportHAR.styles.scss'; -import Button from './../Common/Button'; -import IconUpload from '../../icons/IconImport'; import ImportHarButton from '../Actions/ImportHarButton'; const DROP_FILE_CONFIG = { @@ -13,10 +11,7 @@ const DROP_FILE_CONFIG = { multiple: false, }; -const ImportHar = ({ - showButton, - className, -}) => { +const ImportHar = ({ showButton }) => { const { actions } = useNetwork(); const { updateErrorMessage } = actions; @@ -48,25 +43,21 @@ const ImportHar = ({ return (
- {showButton ? - () : - ( -

- Drag and drop HAR file here, or click to select - file -

- )} + {showButton ? : ( +

+ Drag and drop HAR file here, or click to select + file +

+ )}
); }; ImportHar.propTypes = { - className: PropTypes.string, showButton: PropTypes.bool, }; ImportHar.defaultProps = { - className: null, showButton: true, }; diff --git a/src/Components/NetworkTable/NetworkCellValue.jsx b/src/Components/NetworkTable/NetworkCellValue.jsx index e4a7011..a5397cd 100644 --- a/src/Components/NetworkTable/NetworkCellValue.jsx +++ b/src/Components/NetworkTable/NetworkCellValue.jsx @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; import classNames from 'classnames/bind'; import { formatValue } from '../../utils'; -import Styles from './NetworkTableHeader.styles.scss'; +import Styles from './NetworkTable.styles.scss'; import { VIEWER_FIELDS } from '../../constants'; import Tooltip from '../Common/Tooltip/Tooltip'; @@ -33,25 +33,21 @@ const NetworkCellValue = ({ if (!shouldDisplayTooltip) { return ( - -
- {formattedValue} -
- +
+ {formattedValue} +
); } return ( - - - - {formattedValue} - - - + +
+ {formattedValue} +
+
); }; diff --git a/src/Components/NetworkTable/NetworkTable.styles.scss b/src/Components/NetworkTable/NetworkTable.styles.scss new file mode 100644 index 0000000..6a6b716 --- /dev/null +++ b/src/Components/NetworkTable/NetworkTable.styles.scss @@ -0,0 +1,104 @@ +@import "./../../styles/variables.scss"; + +.network-table-header { + display: flex; + width: 100%; + + padding: $size-xs-s; + font-size: $font-size-small; + font-weight: 700; + text-transform: uppercase; + border-bottom: $border; + color: $brand-primary-gray; + align-content: center; + + &:last-child { + padding-right: 0; + } +} + +.table-column { + display:flex; + width: 12%; + align-self: center; + padding-right: $size-xs-s; + + &:last-child { + padding-right: 0; + } + + &.show-waterfall { + width: 9%; + } + + &.file { + width: 25%; + + &.limited-cols { + width: 100%; + } + } + + &.domain, + &.waterfall{ + width: 15%; + } +} + +.network-table-row { + display: flex; + width: 100%; + overflow: hidden; + + cursor: pointer; + padding: $size-xs $size-xs-s; + border-bottom: $border; + color: $brand-primary-dark-gray; + font-size: $font-size-small; + + &.pending { + color: $white-66; + } + + &.error { + color: $brand-danger; + } + + &:hover { + background: $white-90; + } + + &.highlight { + background: $dark-gray; + color: $white-100; + } +} +.value-text { + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + word-break: break-all; +} + +.network-table-footer { + border-top: $border; + width: 100%; + background: $bg-gray-90; + padding: $size-xs $size-s; + display: inline-flex; + vertical-align: middle; + + span { + flex: 1 1 auto; + justify-content: center; + display: inline-flex; + align-items: center; + border-right: 1px solid $white-80; + padding: 0 $size-xs; + overflow: hidden; + + &:last-child { + border-right: none; + } + } +} diff --git a/src/Components/NetworkTable/NetworkTableBody.jsx b/src/Components/NetworkTable/NetworkTableBody.jsx new file mode 100644 index 0000000..18a2078 --- /dev/null +++ b/src/Components/NetworkTable/NetworkTableBody.jsx @@ -0,0 +1,77 @@ +import React, { useEffect, useRef } from 'react'; +import { FixedSizeList } from 'react-window'; +import PropTypes from 'prop-types'; + +import { useNetwork } from '../../state/network/Context'; +import NetworkTableRow from './NetworkTableRow'; +import { TABLE_ENTRY_HEIGHT } from '../../constants'; +import { useResizeObserver } from '../../hooks/useResizeObserver'; + +const virtualizedTableRow = ({ + data, + index, + style, +}) => { + const { + listData, + totalNetworkTime, + handleReqSelect, + selectedReqIndex, + } = data; + const item = listData.get(index); + + return ( + + ); +}; + +const NetworkTableBody = ({ height }) => { + const { + state, + actions, + callbacks, + } = useNetwork(); + const data = state.get('data'); + const totalNetworkTime = state.get('totalNetworkTime'); + const selectedReqIndex = state.get('selectedReqIndex'); + + const ref = useRef(null); + const { elementDims } = useResizeObserver(ref); + useEffect(() => actions.setTableHeaderWidth(elementDims.width), [elementDims]); + + const handleReqSelect = (payload) => { + actions.updateScrollToIndex(payload.index); + actions.selectRequest(payload); + callbacks.onRequestSelect(payload); + }; + + return ( + + {virtualizedTableRow} + + ); +}; + +NetworkTableBody.propTypes = { + height: PropTypes.number.isRequired, +}; + +export default NetworkTableBody; diff --git a/src/Components/NetworkTable/NetworkTableFooter.jsx b/src/Components/NetworkTable/NetworkTableFooter.jsx index e4a0031..0101f62 100644 --- a/src/Components/NetworkTable/NetworkTableFooter.jsx +++ b/src/Components/NetworkTable/NetworkTableFooter.jsx @@ -2,13 +2,16 @@ import React from 'react'; import classNames from 'classnames/bind'; import PropTypes from 'prop-types'; -import Styles from './NetworkTableFooter.styles.scss'; +import Styles from './NetworkTable.styles.scss'; import { formatSize, formatTime } from './../../utils'; const context = classNames.bind(Styles); -const NetworkTableFooter = ({ dataSummary, showAllInfo }) => ( -
+const NetworkTableFooter = ({ + dataSummary, + showAllInfo, +}) => ( +
{showAllInfo ? ( <> {`${dataSummary.get('totalRequests')} requests`} diff --git a/src/Components/NetworkTable/NetworkTableFooter.styles.scss b/src/Components/NetworkTable/NetworkTableFooter.styles.scss deleted file mode 100644 index 0a6b823..0000000 --- a/src/Components/NetworkTable/NetworkTableFooter.styles.scss +++ /dev/null @@ -1,24 +0,0 @@ -@import "./../../styles/variables.scss"; - -.footer { - border-top: $border; - width: 100%; - background: $bg-gray-90; - padding: $size-xs-s $size-s; - display: inline-flex; - vertical-align: middle; - - span { - flex: 1 1 auto; - justify-content: center; - display: inline-flex; - align-items: center; - border-right: 1px solid $white-80; - padding: 0 $size-xs; - overflow: hidden; - - &:last-child { - border-right: none; - } - } -} diff --git a/src/Components/NetworkTable/NetworkTableHeader.jsx b/src/Components/NetworkTable/NetworkTableHeader.jsx index fcaa9aa..2971000 100644 --- a/src/Components/NetworkTable/NetworkTableHeader.jsx +++ b/src/Components/NetworkTable/NetworkTableHeader.jsx @@ -1,37 +1,45 @@ import React from 'react'; import classNames from 'classnames/bind'; -import { VIEWER_FIELDS } from './../../constants'; -import Styles from './NetworkTableHeader.styles.scss'; +import Styles from './NetworkTable.styles.scss'; +import { useNetwork } from '../../state/network/Context'; import { useTheme } from '../../state/theme/Context'; +import { getViewerFields } from '../../utils'; const context = classNames.bind(Styles); const NetworkTableHeader = () => { + const { state } = useNetwork(); + const showReqDetail = state.get('showReqDetail'); + const tableHeaderWidth = state.get('tableHeaderWidth'); const { showWaterfall } = useTheme(); + const columns = getViewerFields(showReqDetail, showWaterfall); + return ( - - - {Object.entries(VIEWER_FIELDS) - .map(([datakey, { - key, - name, - }]) => ( - - {name} - - ))} - {showWaterfall && ( - - Waterfall - - )} - - +
+ {Object.entries(columns) + .map(([datakey, { + key, + name, + }]) => ( +
+ {name} +
+ ))} +
); }; diff --git a/src/Components/NetworkTable/NetworkTableHeader.styles.scss b/src/Components/NetworkTable/NetworkTableHeader.styles.scss deleted file mode 100644 index 9691f72..0000000 --- a/src/Components/NetworkTable/NetworkTableHeader.styles.scss +++ /dev/null @@ -1,87 +0,0 @@ -@import "./../../styles/variables"; - -$cell-height: 32px; - -%ellipsis { - text-overflow: ellipsis; - overflow: hidden; - white-space: nowrap; - word-break: break-all; -} - -.thead tr th { - text-align: left; - height: $cell-height; -} - -.value-cell { - width: 7%; - box-sizing: border-box; - height: $cell-height; - - &.filename { - width: 30%; - } - - &.domain { - max-width: 10%; - } - - .value-text { - @extend %ellipsis; - display: inline-block; - width: 100%; - } -} - -.timeline-header { - width: 25%; -} - -tbody { - .network-table-row { - cursor: pointer; - - &:nth-of-type(odd) { - background-color: $white-97; - } - - &.pending { - color: $white-66; - } - - &.error { - color: $brand-danger; - } - - &:hover { - background: $white-90; - } - - &.highlight { - background: $dark-gray; - color: $white-100; - } - } -} - -.tooltip { - display: block; - width: auto; - max-width: 300px; - height: auto; - background: $white-100; - border: 1px solid $white-90; - border-radius: 3px; - color: $white-20; - font-size: $font-size-small; - padding: $size-xs-s; - opacity: 1; - word-break: break-all; -} - -.url-tooltip { - @extend .tooltip; - background: $white-0; - color: $white-100; -} diff --git a/src/Components/NetworkTable/NetworkTableRow.jsx b/src/Components/NetworkTable/NetworkTableRow.jsx index 1d29826..d5fdfa0 100644 --- a/src/Components/NetworkTable/NetworkTableRow.jsx +++ b/src/Components/NetworkTable/NetworkTableRow.jsx @@ -2,62 +2,72 @@ import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames/bind'; -import { VIEWER_FIELDS, ROW_ID_PREFIX } from './../../constants'; -import Styles from './NetworkTableHeader.styles.scss'; +import { ROW_ID_PREFIX } from './../../constants'; +import Styles from './NetworkTable.styles.scss'; import TimeChart from './TimeChart'; import NetworkCellValue from './NetworkCellValue'; -import { getStatusClass } from '../../utils'; +import { getStatusClass, getViewerFields } from '../../utils'; import { useTheme } from '../../state/theme/Context'; +import { useNetwork } from '../../state/network/Context'; const context = classNames.bind(Styles); const NetworkTableRow = ({ entry, maxTime, - scrollHighlight, onSelect, + scrollHighlight, + style, }) => { + const { state } = useNetwork(); + const showReqDetail = state.get('showReqDetail'); const { showWaterfall } = useTheme(); - const handleSelectRequest = () => { - onSelect(entry); - }; + const columns = getViewerFields(showReqDetail, showWaterfall); + const handleSelectRequest = () => onSelect(entry); const rowProps = { className: context( 'network-table-row', - getStatusClass(entry), { - highlight: scrollHighlight, - }), + getStatusClass(entry), + { highlight: scrollHighlight }, + ), id: ROW_ID_PREFIX + entry.index, onClick: handleSelectRequest, }; return ( - - {Object.entries(VIEWER_FIELDS) - .map(([datakey, { - key, - unit, - }]) => ( - - ))} - {showWaterfall && ( - - {entry.time ? ( - - ) : ''} - - )} - +
+
+ {Object.entries(columns) + .map(([datakey, { + key, + unit, + }]) => ( +
+ {(key === 'waterfall' && entry.time ? ( + + ) : ( + + ))} +
+ ))} +
+
); }; @@ -66,6 +76,11 @@ NetworkTableRow.propTypes = { maxTime: PropTypes.number.isRequired, onSelect: PropTypes.func.isRequired, scrollHighlight: PropTypes.bool.isRequired, + style: PropTypes.object, +}; + +NetworkTableRow.defaultProps = { + style: {}, }; export default NetworkTableRow; diff --git a/src/Components/NetworkTable/TimeChart.jsx b/src/Components/NetworkTable/TimeChart.jsx index a2dda6c..ad19331 100644 --- a/src/Components/NetworkTable/TimeChart.jsx +++ b/src/Components/NetworkTable/TimeChart.jsx @@ -20,7 +20,10 @@ const TimeChart = ({ placement="left" title={} > - + {chartAttributes.map((chartProps) => ( { {`Started at ${tooltipData.startedAt}`}

- {DETAIL.map(({ - title, - category, - }) => ( + {DETAIL.map(({ title, category }) => (
(!data ? null : ( component={General} data={data} eventKey="general" + isVisible /> {(data.headers.queryString && data.headers.queryString.length) ? ( { - const [isVisible, updateVisibleStates] = useState(true); + const [isOpen, setIsOpen] = useState(isVisible); const [isPayloadTransformed, updateTransform] = useState(true); const handlePayloadTransform = () => updateTransform(!isPayloadTransformed); @@ -25,16 +26,16 @@ const HeaderInfo = ({ }); return ( -
+
updateVisibleStates(!isVisible)} + onClick={() => setIsOpen(!isOpen)} onPayloadTransform={handlePayloadTransform} /> - {isVisible && } + {isOpen && }
); }; @@ -45,12 +46,14 @@ HeaderInfo.propTypes = { eventKey: PropTypes.string.isRequired, isEncodeEnabled: PropTypes.bool, isParseEnabled: PropTypes.bool, + isVisible: PropTypes.bool, }; HeaderInfo.defaultProps = { data: null, isEncodeEnabled: false, isParseEnabled: false, + isVisible: false, }; export default HeaderInfo; diff --git a/src/Containers/MainContainer.jsx b/src/Containers/MainContainer.jsx index 046a8a4..8b5d120 100644 --- a/src/Containers/MainContainer.jsx +++ b/src/Containers/MainContainer.jsx @@ -1,5 +1,4 @@ import React from 'react'; -import PropTypes from 'prop-types'; import { useNetwork } from '../state/network/Context'; import FilterContainer from './FilterContainer'; @@ -12,7 +11,7 @@ import TimelineContainer from './TimelineContainer'; import { useTheme } from '../state/theme/Context'; import NetworkTableFooter from './../Components/NetworkTable/NetworkTableFooter'; -const MainContainer = ({ onRequestSelect }) => { +const MainContainer = () => { const { state } = useNetwork(); const { showTimeline } = useTheme(); const loading = state.get('loading'); @@ -32,7 +31,7 @@ const MainContainer = ({ onRequestSelect }) => {
- + {showReqDetail && }
{actualData.size ? : null} @@ -42,13 +41,4 @@ const MainContainer = ({ onRequestSelect }) => { ); }; -MainContainer.propTypes = { - onRequestSelect: PropTypes.func, -}; - -MainContainer.defaultProps = { - onRequestSelect: () => { - }, -}; - export default MainContainer; diff --git a/src/Containers/MainContainer.styles.scss b/src/Containers/MainContainer.styles.scss index 826db1e..a5e9ee4 100644 --- a/src/Containers/MainContainer.styles.scss +++ b/src/Containers/MainContainer.styles.scss @@ -9,9 +9,7 @@ .main-container { display: flex; - flex: 1 1 auto; - flex-wrap: wrap; - flex-direction: row; - position: relative; + height: 100%; + width: 100%; overflow: auto; } diff --git a/src/Containers/NetworkTableContainer.jsx b/src/Containers/NetworkTableContainer.jsx index 57e097a..72e1313 100644 --- a/src/Containers/NetworkTableContainer.jsx +++ b/src/Containers/NetworkTableContainer.jsx @@ -1,38 +1,33 @@ -import React from 'react'; +import React, { useEffect, useRef, useState } from 'react'; import classNames from 'classnames/bind'; -import PropTypes from 'prop-types'; import NetworkTableHeader from './../Components/NetworkTable/NetworkTableHeader'; -import NetworkTableRow from './../Components/NetworkTable/NetworkTableRow'; import { useNetwork } from './../state/network/Context'; import ImportHar from '../Components/Import/ImportHAR'; import Styles from './NetworkTableContainer.styles.scss'; import ErrorMessage from './../Components/ErrorMessage'; import { useTheme } from '../state/theme/Context'; import InputHAR from '../Components/Import/InputHAR'; +import NetworkTableBody from '../Components/NetworkTable/NetworkTableBody'; +import { TABLE_HEADER_HEIGHT } from '../constants'; const context = classNames.bind(Styles); -const NetworkTableContainer = ({ onRequestSelect }) => { - const { - state, - actions, - } = useNetwork(); - const { showImportHar } = useTheme(); +const NetworkTableContainer = () => { + const { state } = useNetwork(); + const { showImportHar, showWaterfall } = useTheme(); const actualData = state.get('actualData'); - const data = state.get('data'); - const totalNetworkTime = state.get('totalNetworkTime'); const error = state.get('error'); - const selectedReqIndex = state.get('selectedReqIndex'); const showReqDetail = state.get('showReqDetail'); - const containerClassName = context('table-container', { - 'limited-cols': showReqDetail, - }); - const handleReqSelect = (payload) => { - actions.updateScrollToIndex(payload.index); - actions.selectRequest(payload); - onRequestSelect(payload); - }; + + const [tableBodyHeight, setTableBodyHeight] = useState(0); + const ref = useRef(null); + + useEffect(() => { + if (ref?.current) { + setTableBodyHeight(ref.current.clientHeight - TABLE_HEADER_HEIGHT); + } + }, [ref]); if (error) { return ( @@ -42,7 +37,7 @@ const NetworkTableContainer = ({ onRequestSelect }) => { if (!actualData.size && showImportHar) { return ( -
+
@@ -50,33 +45,18 @@ const NetworkTableContainer = ({ onRequestSelect }) => { } return ( -
- - - - {Array.from(data) - .map((rowInfo) => ( - - ))} - -
+
+ +
); }; -NetworkTableContainer.propTypes = { - onRequestSelect: PropTypes.func, -}; - -NetworkTableContainer.defaultProps = { - onRequestSelect: () => { - }, -}; - export default NetworkTableContainer; diff --git a/src/Containers/NetworkTableContainer.styles.scss b/src/Containers/NetworkTableContainer.styles.scss index cc764b3..1e215f5 100644 --- a/src/Containers/NetworkTableContainer.styles.scss +++ b/src/Containers/NetworkTableContainer.styles.scss @@ -1,43 +1,18 @@ @import "./../styles/variables"; .table-container { - flex: 1 1 auto; + display: flex; + flex-direction: column; height: 100%; - overflow: auto; + width: 100%; &.limited-cols { - max-width: $name-col-width; - - .table { - td, th { - border-right: 0; - } - - thead tr th:first-child, - td:nth-child(1) { - width: 100%; - } - - th:nth-child(1) ~ th, - td:nth-child(1) ~ td { - display: none; - } - } + width: 30%; } +} - .table { - width: 100%; - border-right: 0; - border-collapse: collapse; - table-layout: fixed; - - td, th { - padding: .3rem; - } - - thead th { - vertical-align: bottom; - color: $white-66; - } - } +.har-selection { + display: flex; + flex-direction: column; + width: 100%; } diff --git a/src/Containers/ReqDetailContainer.styles.scss b/src/Containers/ReqDetailContainer.styles.scss index 32af5ec..dbc06bf 100644 --- a/src/Containers/ReqDetailContainer.styles.scss +++ b/src/Containers/ReqDetailContainer.styles.scss @@ -4,14 +4,12 @@ $close-button-width: 35px; $tab-height: 35px; .req-detail-container { - flex: 1 1 auto; + width: 70%; height: 100%; overflow: auto; background: $white-100; - position: absolute; - right: 0; - width: calc(100% - #{$name-col-width}); border-left: $border; + position: relative; .nav-tabs { padding-left: $close-button-width; @@ -22,6 +20,7 @@ $tab-height: 35px; color: $white-33; border: 0; border-bottom: 2px solid transparent; + padding: $size-xs-s $size-s $size-xs; &:hover { text-decoration: none; @@ -41,10 +40,11 @@ $tab-height: 35px; cursor: pointer; background: transparent; border: 0; + outline: none; .close-icon { - height: $size-xs * 2; - width: $size-xs * 2; + height: $size-xs-s; + width: $size-xs-s; g { stroke: $white-33; @@ -61,7 +61,7 @@ $tab-height: 35px; .tabs-container { display: flex; width: 100%; - padding: $size-m; + padding: $size-s $size-m; height: calc(100% - #{$tab-height}); overflow: auto; } diff --git a/src/NetworkViewer.jsx b/src/NetworkViewer.jsx index 58ab081..d866e3b 100644 --- a/src/NetworkViewer.jsx +++ b/src/NetworkViewer.jsx @@ -35,12 +35,13 @@ const NetworkViewer = ({ onDataError={onDataError} onDataLoaded={onDataLoaded} onPause={onPause} + onRequestSelect={onRequestSelect} onReset={onReset} onResume={onResume} scrollRequestPosition={scrollRequestPosition} scrollTimeStamp={scrollTimeStamp} > - +
diff --git a/src/constants.js b/src/constants.js index 9936111..3401fc1 100644 --- a/src/constants.js +++ b/src/constants.js @@ -1,7 +1,11 @@ export const VIEWER_FIELDS = Object.freeze({ file: Object.freeze({ key: 'filename', - name: 'File', + name: 'Path', + }), + domain: Object.freeze({ + key: 'domain', + name: 'Domain', }), status: Object.freeze({ key: 'status', @@ -11,10 +15,41 @@ export const VIEWER_FIELDS = Object.freeze({ key: 'method', name: 'Method', }), + type: Object.freeze({ + key: 'type', + name: 'Type', + }), + size: Object.freeze({ + key: 'size', + name: 'Size', + }), + time: Object.freeze({ + key: 'time', + name: 'Time', + }), + waterfall: Object.freeze({ + key: 'waterfall', + name: 'Waterfall', + }), +}); + +export const VIEWER_FIELDS_HIDE_WATERFALL = Object.freeze({ + file: Object.freeze({ + key: 'filename', + name: 'Path', + }), domain: Object.freeze({ key: 'domain', name: 'Domain', }), + status: Object.freeze({ + key: 'status', + name: 'Status', + }), + method: Object.freeze({ + key: 'method', + name: 'Method', + }), type: Object.freeze({ key: 'type', name: 'Type', @@ -29,6 +64,13 @@ export const VIEWER_FIELDS = Object.freeze({ }), }); +export const VIEWER_FIELD_FILE = { + file: Object.freeze({ + key: 'filename', + name: 'Path', + }), +}; + export const FILTER_OPTION = Object.freeze({ STATUS: 'STATUS', TYPE: 'TYPE', @@ -185,8 +227,6 @@ export const TIMINGS = { }; export const TIME_CHART_SVG_PROPS = { - width: '100%', - height: '20', viewBox: '0 0 250 20', version: '1.1', preserveAspectRatio: 'xMinYMin meet', @@ -277,3 +317,6 @@ export const EMPTY_NETWORK_HAR = Object.freeze({ pages: [], }, }); + +export const TABLE_HEADER_HEIGHT = 32; +export const TABLE_ENTRY_HEIGHT = 24; diff --git a/src/hooks/useResizeObserver.js b/src/hooks/useResizeObserver.js new file mode 100644 index 0000000..6056805 --- /dev/null +++ b/src/hooks/useResizeObserver.js @@ -0,0 +1,35 @@ +import { useEffect, useState } from 'react'; + +/* eslint no-underscore-dangle: 0 */ + +export const useResizeObserver = (elementRef) => { + const [elementDims, setElementDims] = useState({ + width: 0, + height: 0, + }); + useEffect(() => { + const element = elementRef.current; + + const onResize = () => { + if (element._outerRef) { + setElementDims({ + width: element._outerRef.clientWidth, + height: element._outerRef.clientHeight, + }); + } + }; + const resizeObserver = new ResizeObserver(onResize); + + if (element._outerRef) { + resizeObserver.observe(element._outerRef); + } + + return () => { + if (element._outerRef) { + resizeObserver.unobserve(element._outerRef); + } + }; + }, [elementRef]); + + return { elementDims }; +}; diff --git a/src/state/network/Context.jsx b/src/state/network/Context.jsx index 0275e67..731d709 100644 --- a/src/state/network/Context.jsx +++ b/src/state/network/Context.jsx @@ -20,6 +20,7 @@ export const useNetwork = () => { updateTypeFilter: actions.updateTypeFilter, updateErrorMessage: actions.updateErrorMessage, selectRequest: actions.selectRequest, + setTableHeaderWidth: actions.setTableHeaderWidth, updateScrollToIndex: actions.updateScrollToIndex, resetState: actions.resetState, })(dispatch, state); diff --git a/src/state/network/NetworkProvider.jsx b/src/state/network/NetworkProvider.jsx index bd972c2..bb0528f 100644 --- a/src/state/network/NetworkProvider.jsx +++ b/src/state/network/NetworkProvider.jsx @@ -5,7 +5,6 @@ import { reducer, initialState as defaultState } from './reducer'; import { updateData, fetchFile, updateScrollToIndex } from './actions'; import { NetworkContext } from './Context'; import { findRequestIndex } from '../../utils'; -import { ROW_ID_PREFIX } from '../../constants'; const NetworkProvider = (props) => { const { @@ -21,6 +20,7 @@ const NetworkProvider = (props) => { onPause, onResume, onReset, + onRequestSelect, } = props; const [state, dispatch] = useReducer(reducer, initialState); @@ -28,9 +28,9 @@ const NetworkProvider = (props) => { onPause, onResume, onReset, + onRequestSelect, }; const value = useMemo(() => [state, dispatch, callbacks], [state]); - const selectedReqIndex = value[0].get('selectedReqIndex'); const requestData = value[0].get('data'); const showReqDetail = value[0].get('showReqDetail'); const actualData = value[0].get('actualData'); @@ -94,6 +94,7 @@ NetworkProvider.propTypes = { onDataError: PropTypes.func, onDataLoaded: PropTypes.func, onPause: PropTypes.func, + onRequestSelect: PropTypes.func, onReset: PropTypes.func, onResume: PropTypes.func, scrollRequestPosition: PropTypes.oneOf(['before', 'after', 'near']), @@ -109,6 +110,7 @@ NetworkProvider.defaultProps = { onDataError: null, onDataLoaded: null, onPause: null, + onRequestSelect: null, onReset: null, onResume: null, scrollRequestPosition: 'near', diff --git a/src/state/network/actions.js b/src/state/network/actions.js index 872a16b..89fdacc 100644 --- a/src/state/network/actions.js +++ b/src/state/network/actions.js @@ -57,6 +57,11 @@ export const selectRequest = (dispatch) => (payload) => dispatch({ payload, }); +export const setTableHeaderWidth = (dispatch) => (payload) => dispatch({ + type: types.SET_TABLE_HEADER_WIDTH, + payload, +}); + export const resetState = (dispatch) => (payload) => dispatch({ type: types.RESET, payload, diff --git a/src/state/network/reducer.js b/src/state/network/reducer.js index 66d235f..3d8a0b2 100644 --- a/src/state/network/reducer.js +++ b/src/state/network/reducer.js @@ -29,6 +29,7 @@ const initialState = new Map({ selectedReqIndex: null, showReqDetail: false, reqDetail: null, + tableHeaderWidth: '100%', }); const reducer = (state = initialState, { @@ -168,6 +169,11 @@ const reducer = (state = initialState, { .set('showReqDetail', !!payload); }); } + case types.SET_TABLE_HEADER_WIDTH: { + return state.withMutations((newState) => { + newState.set('tableHeaderWidth', `${payload}px`); + }); + } case types.RESET: { return initialState; } diff --git a/src/state/network/types.js b/src/state/network/types.js index 848fbfb..8375e55 100644 --- a/src/state/network/types.js +++ b/src/state/network/types.js @@ -6,6 +6,7 @@ export const UPDATE_TYPE_FILTER = 'UPDATE_TYPE_FILTER'; export const UPDATE_ERROR_MESSAGE = 'UPDATE_ERROR_MESSAGE'; export const UPDATE_SCROLL_TO_INDEX = 'UPDATE_SCROLL_TO_INDEX'; export const SELECT_REQUEST = 'SELECT_REQUEST'; +export const SET_TABLE_HEADER_WIDTH = 'SET_TABLE_HEADER_WIDTH'; export const FETCH_FILE = { REQUEST: 'FETCH_FILE_REQUEST', SUCCESS: 'FETCH_FILE_SUCCESS', diff --git a/src/styles/variables.scss b/src/styles/variables.scss index 100da43..852e0f7 100644 --- a/src/styles/variables.scss +++ b/src/styles/variables.scss @@ -103,9 +103,7 @@ $size-xxxl: $size-base * 11; // 44px; brandBlue: $brand-blue; } -// width/height of container -$name-col-width: 300px; - // borders -$border: 1px solid #B6BECC; -$border-dashed: 2px dashed #B6BECC; +$border-color: #DBE0EA; +$border: 1px solid $border-color; +$border-dashed: 2px dashed $border-color; diff --git a/src/utils.js b/src/utils.js index 5e2a9b7..e71ce8f 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,4 +1,10 @@ -import { TIMINGS, TIMELINE_DATA_POINT_HEIGHT, FILTER_OPTION } from './constants'; +import { + FILTER_OPTION, + TIMELINE_DATA_POINT_HEIGHT, + TIMINGS, + VIEWER_FIELD_FILE, VIEWER_FIELDS, + VIEWER_FIELDS_HIDE_WATERFALL, +} from './constants'; /* eslint no-underscore-dangle: 0 */ @@ -28,7 +34,7 @@ export const formatTime = (time) => { }; export const getUrlInfo = (url) => { - // If there's an invalid URL (resource identifier, etc) the constructor would throw an exception. + // If there's an invalid URL (resource identifier, etc.) the constructor would throw an exception. // Return a 'placeholder' object with default values in the event the passed value cannot be // parsed. try { @@ -103,7 +109,7 @@ export const getContent = ({ text, }) => { if (mimeType === 'application/json') { - let parsedJson = text; + let parsedJson; try { parsedJson = JSON.stringify(JSON.parse(text), null, 2); } catch (err) { @@ -322,12 +328,9 @@ export const parseTime = (time) => { return `${time.toFixed(2)} ms`; }; -export const calcTotalTime = (data) => { - const total = Object.keys(data) - .filter((key) => !['_blocked_queueing', '_queued', 'startTime'].includes(key)) - .reduce((acc, key) => acc + data[key], 0); - return total; -}; +export const calcTotalTime = (data) => Object.keys(data) + .filter((key) => !['_blocked_queueing', '_queued', 'startTime'].includes(key)) + .reduce((acc, key) => acc + data[key], 0); export const prepareTooltipData = (data) => ({ queuedAt: parseTime(data.startTime), @@ -381,7 +384,7 @@ export const calcChartAttributes = (data, maxTime, cx, index, cy = null) => { .forEach((key) => { const timingInfo = TIMINGS[key]; const dataKey = Array.isArray(timingInfo.dataKey) ? - timingInfo.dataKey.find((key) => data[key]) : + timingInfo.dataKey.find((dKey) => data[dKey]) : timingInfo.dataKey; const value = data[dataKey]; if (value <= 0) { @@ -477,7 +480,7 @@ export const getSummary = (data) => ( ); export const parseRequestPayload = (text) => { - let parsedJson = text; + let parsedJson; try { parsedJson = JSON.stringify(JSON.parse(text), null, 2); } catch (err) { @@ -485,3 +488,11 @@ export const parseRequestPayload = (text) => { } return parsedJson; }; + +export const getViewerFields = (showReqDetail, showWaterfall) => { + if (showReqDetail) { + return VIEWER_FIELD_FILE; + } + + return showWaterfall ? VIEWER_FIELDS : VIEWER_FIELDS_HIDE_WATERFALL; +}; diff --git a/tests/__tests__/Components/Filters/__snapshots__/Search.spec.jsx.snap b/tests/__tests__/Components/Filters/__snapshots__/Search.spec.jsx.snap index 3e651eb..78a1996 100644 --- a/tests/__tests__/Components/Filters/__snapshots__/Search.spec.jsx.snap +++ b/tests/__tests__/Components/Filters/__snapshots__/Search.spec.jsx.snap @@ -23,6 +23,7 @@ exports[`Search renders without crashing 1`] = ` }, "scrollToIndex": null, "data": Immutable.List [], + "tableHeaderWidth": "100%", "selectedReqIndex": null, "reqDetail": null, "showReqDetail": false, @@ -54,6 +55,7 @@ exports[`Search renders without crashing 1`] = ` onDataError={null} onDataLoaded={null} onPause={null} + onRequestSelect={null} onReset={null} onResume={null} scrollRequestPosition="near" diff --git a/tests/__tests__/Components/Import/__snapshots__/ImportHar.spec.jsx.snap b/tests/__tests__/Components/Import/__snapshots__/ImportHar.spec.jsx.snap index 0636949..f70a622 100644 --- a/tests/__tests__/Components/Import/__snapshots__/ImportHar.spec.jsx.snap +++ b/tests/__tests__/Components/Import/__snapshots__/ImportHar.spec.jsx.snap @@ -23,6 +23,7 @@ exports[`ImportHAR renders without crashing 1`] = ` }, "scrollToIndex": null, "data": Immutable.List [], + "tableHeaderWidth": "100%", "selectedReqIndex": null, "reqDetail": null, "showReqDetail": false, @@ -54,13 +55,13 @@ exports[`ImportHAR renders without crashing 1`] = ` onDataError={null} onDataLoaded={null} onPause={null} + onRequestSelect={null} onReset={null} onResume={null} scrollRequestPosition="near" scrollTimeStamp={null} >
{ it('renders without crashing', () => { const element = mount( - + -
, + , ); expect(element).toMatchSnapshot(); }); diff --git a/tests/__tests__/Components/NetworkTable/NetworkTableRow.spec.jsx b/tests/__tests__/Components/NetworkTable/NetworkTableRow.spec.jsx index c6b964c..cdff82b 100644 --- a/tests/__tests__/Components/NetworkTable/NetworkTableRow.spec.jsx +++ b/tests/__tests__/Components/NetworkTable/NetworkTableRow.spec.jsx @@ -2,6 +2,7 @@ import React from 'react'; import { mount } from 'enzyme'; import NetworkTableRow from './../../../../src/Components/NetworkTable/NetworkTableRow'; +import NetworkProvider from '../../../../src/state/network/NetworkProvider'; describe('NetworkTableRow', () => { const NOOP = () => { @@ -37,13 +38,10 @@ describe('NetworkTableRow', () => { it('renders without crashing', () => { const element = mount( - - - - -
, + + + , ); - expect(element) - .toMatchSnapshot(); + expect(element).toMatchSnapshot(); }); }); diff --git a/tests/__tests__/Components/NetworkTable/__snapshots__/NetworkTableFooter.spec.jsx.snap b/tests/__tests__/Components/NetworkTable/__snapshots__/NetworkTableFooter.spec.jsx.snap index adc8152..1095384 100644 --- a/tests/__tests__/Components/NetworkTable/__snapshots__/NetworkTableFooter.spec.jsx.snap +++ b/tests/__tests__/Components/NetworkTable/__snapshots__/NetworkTableFooter.spec.jsx.snap @@ -17,7 +17,7 @@ exports[`NetworkTableFooter renders without crashing 1`] = ` showAllInfo={true} >
20 requests diff --git a/tests/__tests__/Components/NetworkTable/__snapshots__/NetworkTableHeader.spec.jsx.snap b/tests/__tests__/Components/NetworkTable/__snapshots__/NetworkTableHeader.spec.jsx.snap index f38aed4..f8c72e8 100644 --- a/tests/__tests__/Components/NetworkTable/__snapshots__/NetworkTableHeader.spec.jsx.snap +++ b/tests/__tests__/Components/NetworkTable/__snapshots__/NetworkTableHeader.spec.jsx.snap @@ -1,57 +1,123 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`NetworkTableHeader renders without crashing 1`] = ` - + - - - - - - - - - - - - +
+
+ Path +
+
+ Domain +
+
+ Status +
+
+ Method +
+
+ Type +
+
+ Size +
+
+ Time +
+
+ Waterfall +
+
-
- File - - Status - - Method - - Domain - - Type - - Size - - Time - - Waterfall -
+ `; diff --git a/tests/__tests__/Components/NetworkTable/__snapshots__/NetworkTableRow.spec.jsx.snap b/tests/__tests__/Components/NetworkTable/__snapshots__/NetworkTableRow.spec.jsx.snap index ee05f52..56af37e 100644 --- a/tests/__tests__/Components/NetworkTable/__snapshots__/NetworkTableRow.spec.jsx.snap +++ b/tests/__tests__/Components/NetworkTable/__snapshots__/NetworkTableRow.spec.jsx.snap @@ -1,73 +1,135 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`NetworkTableRow renders without crashing 1`] = ` - - - + +
-
- - - - + +
+ +
+ developer.mozilla.org +
+
+
+
-
- - + +
-
- - - - - + +
-
- - + +
-
- - + +
+ +
+ +
+
-
- - - - -
- slice - + - -
+
200
-
-
- GET -
-
-
- developer.mozilla.org +
+ GET
-
-
+
html
-
-
+
135.04
-
-
-
-
+
+ +
+
+
+ + `; diff --git a/tests/__tests__/Components/ReqDetail/__snapshots__/Headers.spec.jsx.snap b/tests/__tests__/Components/ReqDetail/__snapshots__/Headers.spec.jsx.snap index c86f8cd..7ad0cf3 100644 --- a/tests/__tests__/Components/ReqDetail/__snapshots__/Headers.spec.jsx.snap +++ b/tests/__tests__/Components/ReqDetail/__snapshots__/Headers.spec.jsx.snap @@ -96,6 +96,7 @@ exports[`Headers renders without crashing 1`] = ` eventKey="general" isEncodeEnabled={false} isParseEnabled={false} + isVisible={true} >
- Response Headers + Request Headers
- -
-

- - Connection: - - - keep-alive - -

-
-
- Request Headers + Response Headers
- -
-

- - Connection: - - - keep-alive - -

-
-
- -
-
-
-                {
-  "hello": "foo"
-}
-              
-
-
-
diff --git a/tests/__tests__/Components/ReqDetail/headers/__snapshots__/HeaderInfo.spec.jsx.snap b/tests/__tests__/Components/ReqDetail/headers/__snapshots__/HeaderInfo.spec.jsx.snap index 6270b84..392dd6b 100644 --- a/tests/__tests__/Components/ReqDetail/headers/__snapshots__/HeaderInfo.spec.jsx.snap +++ b/tests/__tests__/Components/ReqDetail/headers/__snapshots__/HeaderInfo.spec.jsx.snap @@ -2,7 +2,7 @@ exports[`HeaderInfo renders without crashing 1`] = `
-
`; diff --git a/tests/__tests__/Containers/__snapshots__/FilterContainer.spec.jsx.snap b/tests/__tests__/Containers/__snapshots__/FilterContainer.spec.jsx.snap index dc88596..af253fa 100644 --- a/tests/__tests__/Containers/__snapshots__/FilterContainer.spec.jsx.snap +++ b/tests/__tests__/Containers/__snapshots__/FilterContainer.spec.jsx.snap @@ -23,6 +23,7 @@ exports[`FilterContainer renders without crashing 1`] = ` }, "scrollToIndex": null, "data": Immutable.List [], + "tableHeaderWidth": "100%", "selectedReqIndex": null, "reqDetail": null, "showReqDetail": false, @@ -54,6 +55,7 @@ exports[`FilterContainer renders without crashing 1`] = ` onDataError={null} onDataLoaded={null} onPause={null} + onRequestSelect={null} onReset={null} onResume={null} scrollRequestPosition="near" @@ -227,7 +229,6 @@ exports[`FilterContainer renders without crashing 1`] = `
- +
- +
- +
- +
diff --git a/tests/__tests__/state/__snapshots__/NetworkProvider.spec.jsx.snap b/tests/__tests__/state/__snapshots__/NetworkProvider.spec.jsx.snap index c53eae6..2985015 100644 --- a/tests/__tests__/state/__snapshots__/NetworkProvider.spec.jsx.snap +++ b/tests/__tests__/state/__snapshots__/NetworkProvider.spec.jsx.snap @@ -23,6 +23,7 @@ exports[`NetworkProvider renders without crashing 1`] = ` }, "scrollToIndex": null, "data": Immutable.List [], + "tableHeaderWidth": "100%", "selectedReqIndex": null, "reqDetail": null, "showReqDetail": false, @@ -54,6 +55,7 @@ exports[`NetworkProvider renders without crashing 1`] = ` onDataError={null} onDataLoaded={null} onPause={null} + onRequestSelect={null} onReset={null} onResume={null} scrollRequestPosition="near" @@ -72,6 +74,7 @@ exports[`NetworkProvider renders without crashing 1`] = ` }, "scrollToIndex": null, "data": Immutable.List [], + "tableHeaderWidth": "100%", "selectedReqIndex": null, "reqDetail": null, "showReqDetail": false, @@ -102,6 +105,7 @@ exports[`NetworkProvider renders without crashing 1`] = ` [Function], Object { "onPause": null, + "onRequestSelect": null, "onReset": null, "onResume": null, },