@@ -2,15 +2,20 @@ import type {ReactNode} from 'react';
2
2
import styled from '@emotion/styled' ;
3
3
4
4
import { Button } from 'sentry/components/core/button' ;
5
+ import { LinkButton } from 'sentry/components/core/button/linkButton' ;
6
+ import { Flex } from 'sentry/components/core/layout/flex' ;
5
7
import StructuredEventData from 'sentry/components/structuredEventData' ;
6
8
import { t } from 'sentry/locale' ;
7
9
import { space } from 'sentry/styles/space' ;
8
10
import type { Extraction } from 'sentry/utils/replays/extractDomNodes' ;
11
+ import { useReplayReader } from 'sentry/utils/replays/playback/providers/replayReaderProvider' ;
9
12
import type { ReplayFrame } from 'sentry/utils/replays/types' ;
10
13
import { isCLSFrame , isWebVitalFrame } from 'sentry/utils/replays/types' ;
14
+ import useOrganization from 'sentry/utils/useOrganization' ;
11
15
import type { OnExpandCallback } from 'sentry/views/replays/detail/useVirtualizedInspector' ;
12
16
13
17
type MouseCallback = ( frame : ReplayFrame , nodeId ?: number ) => void ;
18
+ type LayoutShift = Record < string , ReactNode [ ] > ;
14
19
15
20
interface Props {
16
21
frame : ReplayFrame ;
@@ -29,15 +34,20 @@ export function BreadcrumbWebVital({
29
34
onMouseEnter,
30
35
onMouseLeave,
31
36
} : Props ) {
37
+ const organization = useOrganization ( ) ;
38
+ const replayReader = useReplayReader ( ) ;
39
+
32
40
if ( ! isWebVitalFrame ( frame ) ) {
33
41
return null ;
34
42
}
35
43
36
- const webVitalData = { value : frame . data . value } ;
37
44
const selectors = extraction ?. selectors ;
45
+ const webVitalData : Record < string , number | ReactNode | LayoutShift [ ] > = {
46
+ value : frame . data . value ,
47
+ } ;
38
48
39
49
if ( isCLSFrame ( frame ) && frame . data . attributions && selectors ) {
40
- const layoutShifts : Array < Record < string , ReactNode [ ] > > = [ ] ;
50
+ const layoutShifts : LayoutShift [ ] = [ ] ;
41
51
for ( const attr of frame . data . attributions ) {
42
52
const elements : ReactNode [ ] = [ ] ;
43
53
if ( 'nodeIds' in attr && Array . isArray ( attr . nodeIds ) ) {
@@ -72,12 +82,10 @@ export function BreadcrumbWebVital({
72
82
layoutShifts . push ( { [ `score ${ attr . value } ` ] : elements } ) ;
73
83
}
74
84
if ( layoutShifts . length ) {
75
- // @ts -expect-error TS(7053): Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
76
85
webVitalData [ 'Layout shifts' ] = layoutShifts ;
77
86
}
78
87
} else if ( selectors ) {
79
88
selectors . forEach ( ( key , value ) => {
80
- // @ts -expect-error TS(7053): Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
81
89
webVitalData [ key ] = (
82
90
< span
83
91
key = { key }
@@ -95,17 +103,33 @@ export function BreadcrumbWebVital({
95
103
}
96
104
97
105
return (
98
- < StructuredEventData
99
- initialExpandedPaths = { expandPaths ?? [ ] }
100
- onToggleExpand = { ( expandedPaths , path ) => {
101
- onInspectorExpanded (
102
- path ,
103
- Object . fromEntries ( expandedPaths . map ( item => [ item , true ] ) )
104
- ) ;
105
- } }
106
- data = { webVitalData }
107
- withAnnotatedText
108
- />
106
+ < Flex gap = "lg" justify = "between" align = "start" >
107
+ < NoMarginWrapper flex = "1" >
108
+ < StructuredEventData
109
+ initialExpandedPaths = { expandPaths ?? [ ] }
110
+ onToggleExpand = { ( expandedPaths , path ) => {
111
+ onInspectorExpanded (
112
+ path ,
113
+ Object . fromEntries ( expandedPaths . map ( item => [ item , true ] ) )
114
+ ) ;
115
+ } }
116
+ data = { webVitalData }
117
+ withAnnotatedText
118
+ />
119
+ </ NoMarginWrapper >
120
+ < NoWrapButton
121
+ priority = "link"
122
+ size = "xs"
123
+ to = { {
124
+ pathname : `/organizations/${ organization . slug } /insights/frontend/pageloads/overview/` ,
125
+ query : {
126
+ projectId : replayReader ?. getReplay ( ) . project_id ,
127
+ } ,
128
+ } }
129
+ >
130
+ { t ( 'All Web Vitals' ) }
131
+ </ NoWrapButton >
132
+ </ Flex >
109
133
) ;
110
134
}
111
135
@@ -131,3 +155,14 @@ const SelectorButton = styled(Button)`
131
155
height: auto;
132
156
min-height: auto;
133
157
` ;
158
+
159
+ const NoMarginWrapper = styled ( Flex ) `
160
+ pre {
161
+ margin: 0;
162
+ flex: 1;
163
+ }
164
+ ` ;
165
+
166
+ const NoWrapButton = styled ( LinkButton ) `
167
+ white-space: nowrap;
168
+ ` ;
0 commit comments