@@ -12,19 +12,27 @@ import {PanelTable} from 'sentry/components/panels/panelTable';
12
12
import SearchBar from 'sentry/components/searchBar' ;
13
13
import SentryDocumentTitle from 'sentry/components/sentryDocumentTitle' ;
14
14
import { TabList , TabPanels , Tabs } from 'sentry/components/tabs' ;
15
+ import { Tooltip } from 'sentry/components/tooltip' ;
15
16
import { DEFAULT_DEBOUNCE_DURATION } from 'sentry/constants' ;
17
+ import { IconArrow , IconWarning } from 'sentry/icons' ;
16
18
import { t , tct } from 'sentry/locale' ;
17
19
import { space } from 'sentry/styles/space' ;
18
- import type { MetricMeta , Organization , Project } from 'sentry/types' ;
19
- import { browserHistory } from 'sentry/utils/browserHistory' ;
20
- import { METRICS_DOCS_URL } from 'sentry/utils/metrics/constants' ;
20
+ import type { MetricMeta } from 'sentry/types/metrics' ;
21
+ import type { Organization } from 'sentry/types/organization' ;
22
+ import type { Project } from 'sentry/types/project' ;
23
+ import {
24
+ DEFAULT_METRICS_CARDINALITY_LIMIT ,
25
+ METRICS_DOCS_URL ,
26
+ } from 'sentry/utils/metrics/constants' ;
21
27
import { getReadableMetricType } from 'sentry/utils/metrics/formatters' ;
22
28
import { formatMRI } from 'sentry/utils/metrics/mri' ;
23
29
import { useBlockMetric } from 'sentry/utils/metrics/useBlockMetric' ;
30
+ import { useMetricsCardinality } from 'sentry/utils/metrics/useMetricsCardinality' ;
24
31
import { useMetricsMeta } from 'sentry/utils/metrics/useMetricsMeta' ;
25
32
import { decodeScalar } from 'sentry/utils/queryString' ;
26
33
import routeTitleGen from 'sentry/utils/routeTitle' ;
27
34
import { middleEllipsis } from 'sentry/utils/string/middleEllipsis' ;
35
+ import { useNavigate } from 'sentry/utils/useNavigate' ;
28
36
import { useMetricsOnboardingSidebar } from 'sentry/views/metrics/ddmOnboarding/useMetricsOnboardingSidebar' ;
29
37
import SettingsPageHeader from 'sentry/views/settings/components/settingsPageHeader' ;
30
38
import TextBlock from 'sentry/views/settings/components/text/textBlock' ;
@@ -43,35 +51,68 @@ enum BlockingStatusTab {
43
51
DISABLED = 'disabled' ,
44
52
}
45
53
54
+ type MetricWithCardinality = MetricMeta & { cardinality : number } ;
55
+
46
56
function ProjectMetrics ( { project, location} : Props ) {
47
- const { data : meta , isLoading } = useMetricsMeta (
57
+ const metricsMeta = useMetricsMeta (
48
58
{ projects : [ parseInt ( project . id , 10 ) ] } ,
49
59
[ 'custom' ] ,
50
60
false
51
61
) ;
62
+
63
+ const metricsCardinality = useMetricsCardinality ( {
64
+ project,
65
+ } ) ;
66
+
67
+ const sortedMeta = useMemo ( ( ) => {
68
+ if ( ! metricsMeta . data ) {
69
+ return [ ] ;
70
+ }
71
+
72
+ if ( ! metricsCardinality . data ) {
73
+ return metricsMeta . data . map ( meta => ( { ...meta , cardinality : 0 } ) ) ;
74
+ }
75
+
76
+ return metricsMeta . data
77
+ . map ( ( { mri, ...rest } ) => {
78
+ return {
79
+ mri,
80
+ cardinality : metricsCardinality . data [ mri ] ?? 0 ,
81
+ ...rest ,
82
+ } ;
83
+ } )
84
+ . sort ( ( a , b ) => {
85
+ return b . cardinality - a . cardinality ;
86
+ } ) as MetricWithCardinality [ ] ;
87
+ } , [ metricsCardinality . data , metricsMeta . data ] ) ;
88
+
52
89
const query = decodeScalar ( location . query . query , '' ) . trim ( ) ;
53
- const { activateSidebar} = useMetricsOnboardingSidebar ( ) ;
54
- const [ selectedTab , setSelectedTab ] = useState ( BlockingStatusTab . ACTIVE ) ;
55
90
91
+ const metrics = sortedMeta . filter (
92
+ ( { mri, type, unit} ) =>
93
+ mri . includes ( query ) ||
94
+ getReadableMetricType ( type ) . includes ( query ) ||
95
+ unit . includes ( query )
96
+ ) ;
97
+
98
+ const isLoading = metricsMeta . isLoading || metricsCardinality . isLoading ;
99
+
100
+ const navigate = useNavigate ( ) ;
56
101
const debouncedSearch = useMemo (
57
102
( ) =>
58
103
debounce (
59
104
( searchQuery : string ) =>
60
- browserHistory . replace ( {
105
+ navigate ( {
61
106
pathname : location . pathname ,
62
107
query : { ...location . query , query : searchQuery } ,
63
108
} ) ,
64
109
DEFAULT_DEBOUNCE_DURATION
65
110
) ,
66
- [ location . pathname , location . query ]
111
+ [ location . pathname , location . query , navigate ]
67
112
) ;
68
113
69
- const metrics = meta . filter (
70
- ( { mri, type, unit} ) =>
71
- mri . includes ( query ) ||
72
- getReadableMetricType ( type ) . includes ( query ) ||
73
- unit . includes ( query )
74
- ) ;
114
+ const { activateSidebar} = useMetricsOnboardingSidebar ( ) ;
115
+ const [ selectedTab , setSelectedTab ] = useState ( BlockingStatusTab . ACTIVE ) ;
75
116
76
117
return (
77
118
< Fragment >
@@ -151,21 +192,27 @@ function ProjectMetrics({project, location}: Props) {
151
192
152
193
interface MetricsTableProps {
153
194
isLoading : boolean ;
154
- metrics : MetricMeta [ ] ;
195
+ metrics : MetricWithCardinality [ ] ;
155
196
project : Project ;
156
197
query : string ;
157
198
}
158
199
159
200
function MetricsTable ( { metrics, isLoading, query, project} : MetricsTableProps ) {
160
201
const blockMetricMutation = useBlockMetric ( project ) ;
161
202
const { hasAccess} = useAccess ( { access : [ 'project:write' ] } ) ;
203
+ const cardinalityLimit =
204
+ project . relayCustomMetricCardinalityLimit ?? DEFAULT_METRICS_CARDINALITY_LIMIT ;
162
205
163
206
return (
164
207
< StyledPanelTable
165
208
headers = { [
166
209
t ( 'Metric' ) ,
210
+ < Cell right key = "cardinality" >
211
+ < IconArrow size = "xs" direction = "down" />
212
+
213
+ { t ( 'Cardinality' ) }
214
+ </ Cell > ,
167
215
< Cell right key = "type" >
168
- { ' ' }
169
216
{ t ( 'Type' ) }
170
217
</ Cell > ,
171
218
< Cell right key = "unit" >
@@ -183,8 +230,9 @@ function MetricsTable({metrics, isLoading, query, project}: MetricsTableProps) {
183
230
isEmpty = { metrics . length === 0 }
184
231
isLoading = { isLoading }
185
232
>
186
- { metrics . map ( ( { mri, type, unit, blockingStatus} ) => {
233
+ { metrics . map ( ( { mri, type, unit, cardinality , blockingStatus} ) => {
187
234
const isBlocked = blockingStatus [ 0 ] ?. isBlocked ;
235
+ const isCardinalityLimited = cardinality >= cardinalityLimit ;
188
236
return (
189
237
< Fragment key = { mri } >
190
238
< Cell >
@@ -196,6 +244,19 @@ function MetricsTable({metrics, isLoading, query, project}: MetricsTableProps) {
196
244
{ middleEllipsis ( formatMRI ( mri ) , 65 , / \. | - | _ / ) }
197
245
</ Link >
198
246
</ Cell >
247
+ < Cell right >
248
+ { isCardinalityLimited && (
249
+ < Tooltip
250
+ title = { tct (
251
+ 'The tag cardinality of this metric exceeded our limit of [cardinalityLimit], which led to the data being dropped' ,
252
+ { cardinalityLimit}
253
+ ) }
254
+ >
255
+ < StyledIconWarning size = "sm" color = "red300" />
256
+ </ Tooltip >
257
+ ) }
258
+ { cardinality }
259
+ </ Cell >
199
260
< Cell right >
200
261
< Tag > { getReadableMetricType ( type ) } </ Tag >
201
262
</ Cell >
@@ -241,14 +302,22 @@ const SearchWrapper = styled('div')`
241
302
` ;
242
303
243
304
const StyledPanelTable = styled ( PanelTable ) `
244
- grid-template-columns: 1fr repeat(3, minmax(115px, min-content) );
305
+ grid-template-columns: 1fr repeat(4, min-content);
245
306
` ;
246
307
247
308
const Cell = styled ( 'div' ) < { right ?: boolean } > `
248
309
display: flex;
249
310
align-items: center;
250
311
align-self: stretch;
312
+ gap: ${ space ( 0.5 ) } ;
251
313
justify-content: ${ p => ( p . right ? 'flex-end' : 'flex-start' ) } ;
252
314
` ;
253
315
316
+ const StyledIconWarning = styled ( IconWarning ) `
317
+ margin-top: ${ space ( 0.5 ) } ;
318
+ &:hover {
319
+ cursor: pointer;
320
+ }
321
+ ` ;
322
+
254
323
export default ProjectMetrics ;
0 commit comments