@@ -7,6 +7,7 @@ import AutoSizer from 'react-virtualized-auto-sizer';
7
7
import { FixedSizeList as List , ListChildComponentProps } from 'react-window' ;
8
8
9
9
import { styled } from '../../styles'
10
+ import { css } from "styled-components" ;
10
11
import { ArrowIcon , Icon , PhosphorIcon , WarningIcon } from '../../icons' ;
11
12
12
13
import {
@@ -86,14 +87,18 @@ const ListContainer = styled.div<{ role: 'table' }>`
86
87
}
87
88
` ;
88
89
89
- const Column = styled . div < { role : 'cell' | 'columnheader' } > `
90
+ const columnStyles = css `
90
91
display : block;
91
92
overflow : hidden;
92
93
text-overflow : ellipsis;
93
94
white-space : nowrap;
94
95
padding : 3px 0 ;
95
96
` ;
96
97
98
+ const Column = styled . div < { role : 'cell' | 'columnheader' } > `
99
+ ${ columnStyles }
100
+ ` ;
101
+
97
102
const RowPin = styled (
98
103
filterProps ( Icon , 'pinned' )
99
104
) . attrs ( ( p : { pinned : boolean } ) => ( {
@@ -147,6 +152,26 @@ const MarkerHeader = styled.div<{ role: 'columnheader' }>`
147
152
flex-shrink: 0;
148
153
` ;
149
154
155
+ const BaseTimestamp = ( { timestamp, role = 'cell' , className, children } : {
156
+ timestamp ?: number ,
157
+ role ?: 'columnheader' | 'cell' ,
158
+ className ?: string ,
159
+ children ?: React . ReactNode
160
+ } ) => {
161
+ return (
162
+ < div role = { role } className = { className } >
163
+ { timestamp != null ? ( new Date ( timestamp ) . toLocaleTimeString ( ) ) : ( children ?? '-' ) }
164
+ </ div >
165
+ ) ;
166
+ } ;
167
+
168
+ const Timestamp = styled ( BaseTimestamp ) `
169
+ ${ columnStyles } ;
170
+ transition: flex-basis 0.1s;
171
+ flex-shrink: 0;
172
+ flex-grow: 0;
173
+ ` ;
174
+
150
175
const Method = styled ( Column ) `
151
176
transition: flex-basis 0.1s;
152
177
${ ( p : { pinned ?: boolean } ) =>
@@ -434,30 +459,31 @@ const ExchangeRow = inject('uiStore')(observer(({
434
459
className = { isSelected ? 'selected' : '' }
435
460
style = { style }
436
461
>
437
- < RowPin aria-label = { pinned ? 'Pinned' : undefined } pinned = { pinned } />
462
+ < RowPin aria-label = { pinned ? 'Pinned' : undefined } pinned = { pinned } />
438
463
< RowMarker role = 'cell' category = { category } title = { describeEventCategory ( category ) } />
439
- < Method role = 'cell' pinned = { pinned } > { request . method } </ Method >
464
+ < Timestamp role = 'cell' timestamp = { request . timingEvents . startTime } />
465
+ < Method role = 'cell' pinned = { pinned } > { request . method } </ Method >
440
466
< Status role = 'cell' >
441
467
{
442
468
response === 'aborted'
443
469
? < StatusCode status = { 'aborted' } />
444
- : exchange . downstream . isBreakpointed
445
- ? < WarningIcon title = 'Breakpointed, waiting to be resumed' />
446
- : exchange . isWebSocket ( ) && response ?. statusCode === 101
447
- ? < StatusCode // Special UI for accepted WebSockets
448
- status = { exchange . closeState
449
- ? 'WS:closed'
450
- : 'WS:open'
451
- }
452
- message = { `${ exchange . closeState
453
- ? 'A closed'
454
- : 'An open'
455
- } WebSocket connection`}
456
- />
457
- : < StatusCode
458
- status = { response ?. statusCode }
459
- message = { response ?. statusMessage }
460
- />
470
+ : exchange . downstream . isBreakpointed
471
+ ? < WarningIcon title = 'Breakpointed, waiting to be resumed' />
472
+ : exchange . isWebSocket ( ) && response ?. statusCode === 101
473
+ ? < StatusCode // Special UI for accepted WebSockets
474
+ status = { exchange . closeState
475
+ ? 'WS:closed'
476
+ : 'WS:open'
477
+ }
478
+ message = { `${ exchange . closeState
479
+ ? 'A closed'
480
+ : 'An open'
481
+ } WebSocket connection`}
482
+ />
483
+ : < StatusCode
484
+ status = { response ?. statusCode }
485
+ message = { response ?. statusMessage }
486
+ />
461
487
}
462
488
</ Status >
463
489
< Source role = 'cell' >
@@ -473,21 +499,21 @@ const ExchangeRow = inject('uiStore')(observer(({
473
499
) &&
474
500
< PhosphorIcon
475
501
icon = 'Pencil'
476
- alt = { `Handled by ${
477
- exchange . matchedRule . stepTypes . length === 1
478
- ? nameStepClass ( exchange . matchedRule . stepTypes [ 0 ] )
479
- : 'multi-step'
480
- } rule`}
502
+ alt = { `Handled by
503
+ ${ exchange . matchedRule . stepTypes . length === 1 ?
504
+ nameStepClass ( exchange . matchedRule . stepTypes [ 0 ] )
505
+ : 'multi-step'
506
+ } rule`}
481
507
size = '20px'
482
508
color = { getSummaryColor ( 'mutative' ) }
483
509
/>
484
510
}
485
511
</ Source >
486
- < Host role = 'cell' title = { request . parsedUrl . host } >
487
- { request . parsedUrl . host }
512
+ < Host role = 'cell' title = { request . parsedUrl . host } >
513
+ { request . parsedUrl . host }
488
514
</ Host >
489
- < PathAndQuery role = 'cell' title = { request . parsedUrl . pathname + request . parsedUrl . search } >
490
- { request . parsedUrl . pathname + request . parsedUrl . search }
515
+ < PathAndQuery role = 'cell' title = { request . parsedUrl . pathname + request . parsedUrl . search } >
516
+ { request . parsedUrl . pathname + request . parsedUrl . search }
491
517
</ PathAndQuery >
492
518
</ TrafficEventListRow > ;
493
519
} ) ) ;
@@ -801,6 +827,7 @@ export class ViewEventList extends React.Component<ViewEventListProps> {
801
827
return < ListContainer role = "table" >
802
828
< TableHeaderRow role = "row" >
803
829
< MarkerHeader role = "columnheader" aria-label = "Category" />
830
+ < Timestamp role = "columnheader" > Timestamp</ Timestamp > }
804
831
< Method role = "columnheader" > Method</ Method >
805
832
< Status role = "columnheader" > Status</ Status >
806
833
< Source role = "columnheader" > Source</ Source >
@@ -810,41 +837,41 @@ export class ViewEventList extends React.Component<ViewEventListProps> {
810
837
811
838
{
812
839
events . length === 0
813
- ? ( isPaused
814
- ? < EmptyStateOverlay icon = 'Pause' >
815
- Interception is paused, resume it to collect intercepted requests
816
- </ EmptyStateOverlay >
817
- : < EmptyStateOverlay icon = 'Plug' >
818
- Connect a client and intercept some requests, and they'll appear here
819
- </ EmptyStateOverlay >
820
- )
821
-
822
- : filteredEvents . length === 0
823
- ? < EmptyStateOverlay icon = 'QuestionMark' >
824
- No requests match this search filter{
825
- isPaused ? ' and interception is paused' : ''
826
- }
827
- </ EmptyStateOverlay >
828
-
829
- : < AutoSizer > { ( { height, width } ) =>
830
- < Observer > { ( ) =>
831
- < List
832
- innerRef = { this . setListBodyRef }
833
- outerElementType = { this . KeyBoundListWindow }
834
- ref = { this . listRef }
835
-
836
- height = { height - HEADER_FOOTER_HEIGHT }
837
- width = { width }
838
- itemCount = { filteredEvents . length }
839
- itemSize = { 32 }
840
- itemData = { this . listItemData }
841
-
842
- onScroll = { this . updateScrolledState }
843
- >
844
- { EventRow }
845
- </ List >
846
- } </ Observer >
847
- } </ AutoSizer >
840
+ ? ( isPaused
841
+ ? < EmptyStateOverlay icon = 'Pause' >
842
+ Interception is paused, resume it to collect intercepted requests
843
+ </ EmptyStateOverlay >
844
+ : < EmptyStateOverlay icon = 'Plug' >
845
+ Connect a client and intercept some requests, and they'll appear here
846
+ </ EmptyStateOverlay >
847
+ )
848
+
849
+ : filteredEvents . length === 0
850
+ ? < EmptyStateOverlay icon = 'QuestionMark' >
851
+ No requests match this search filter{
852
+ isPaused ? ' and interception is paused' : ''
853
+ }
854
+ </ EmptyStateOverlay >
855
+
856
+ : < AutoSizer > { ( { height, width } ) =>
857
+ < Observer > { ( ) =>
858
+ < List
859
+ innerRef = { this . setListBodyRef }
860
+ outerElementType = { this . KeyBoundListWindow }
861
+ ref = { this . listRef }
862
+
863
+ height = { height - HEADER_FOOTER_HEIGHT }
864
+ width = { width }
865
+ itemCount = { filteredEvents . length }
866
+ itemSize = { 32 }
867
+ itemData = { this . listItemData }
868
+
869
+ onScroll = { this . updateScrolledState }
870
+ >
871
+ { EventRow }
872
+ </ List >
873
+ } </ Observer >
874
+ } </ AutoSizer >
848
875
}
849
876
</ ListContainer > ;
850
877
}
0 commit comments