@@ -2,17 +2,21 @@ import type {ComponentProps, SyntheticEvent} from 'react';
2
2
import { Fragment , memo , useCallback , useLayoutEffect , useRef , useState } from 'react' ;
3
3
import { useTheme } from '@emotion/react' ;
4
4
5
+ import { Button } from 'sentry/components/core/button' ;
5
6
import { EmptyStreamWrapper } from 'sentry/components/emptyStateWarning' ;
6
7
import LoadingIndicator from 'sentry/components/loadingIndicator' ;
7
- import { IconWarning } from 'sentry/icons' ;
8
+ import { IconAdd , IconJson , IconSpan , IconSubtract , IconWarning } from 'sentry/icons' ;
8
9
import { IconChevron } from 'sentry/icons/iconChevron' ;
9
10
import { t } from 'sentry/locale' ;
11
+ import { space } from 'sentry/styles/space' ;
10
12
import { defined } from 'sentry/utils' ;
11
13
import { trackAnalytics } from 'sentry/utils/analytics' ;
12
14
import type { TableDataRow } from 'sentry/utils/discover/discoverQuery' ;
13
15
import type { EventsMetaType } from 'sentry/utils/discover/eventView' ;
14
16
import { FieldValueType } from 'sentry/utils/fields' ;
17
+ import useCopyToClipboard from 'sentry/utils/useCopyToClipboard' ;
15
18
import { useLocation } from 'sentry/utils/useLocation' ;
19
+ import { useNavigate } from 'sentry/utils/useNavigate' ;
16
20
import useOrganization from 'sentry/utils/useOrganization' ;
17
21
import useProjectFromId from 'sentry/utils/useProjectFromId' ;
18
22
import CellAction , {
@@ -28,10 +32,10 @@ import {
28
32
useSetLogsAutoRefresh ,
29
33
} from 'sentry/views/explore/contexts/logs/logsAutoRefreshContext' ;
30
34
import {
35
+ stripLogParamsFromLocation ,
36
+ useLogsAddSearchFilter ,
31
37
useLogsAnalyticsPageSource ,
32
38
useLogsFields ,
33
- useLogsSearch ,
34
- useSetLogsSearch ,
35
39
} from 'sentry/views/explore/contexts/logs/logsPageParams' ;
36
40
import {
37
41
DEFAULT_TRACE_ITEM_HOVER_TIMEOUT ,
@@ -51,6 +55,8 @@ import {
51
55
DetailsWrapper ,
52
56
getLogColors ,
53
57
LogAttributeTreeWrapper ,
58
+ LogDetailTableActionsButtonBar ,
59
+ LogDetailTableActionsCell ,
54
60
LogDetailTableBodyCell ,
55
61
LogFirstCellContent ,
56
62
LogsTableBodyFirstCell ,
@@ -69,9 +75,13 @@ import {
69
75
} from 'sentry/views/explore/logs/useLogsQuery' ;
70
76
import {
71
77
adjustAliases ,
78
+ adjustLogTraceID ,
72
79
getLogRowItem ,
73
80
getLogSeverityLevel ,
81
+ ourlogToJson ,
74
82
} from 'sentry/views/explore/logs/utils' ;
83
+ import { TraceViewSources } from 'sentry/views/performance/newTraceDetails/traceHeader/breadcrumbs' ;
84
+ import { getTraceDetailsUrl } from 'sentry/views/performance/traceDetails/utils' ;
75
85
76
86
type LogsRowProps = {
77
87
dataRow : OurLogsResponseItem ;
@@ -124,8 +134,6 @@ export const LogRowContent = memo(function LogRowContent({
124
134
const location = useLocation ( ) ;
125
135
const organization = useOrganization ( ) ;
126
136
const fields = useLogsFields ( ) ;
127
- const search = useLogsSearch ( ) ;
128
- const setLogsSearch = useSetLogsSearch ( ) ;
129
137
const autorefreshEnabled = useLogsAutoRefreshEnabled ( ) ;
130
138
const setAutorefresh = useSetLogsAutoRefresh ( ) ;
131
139
const measureRef = useRef < HTMLTableRowElement > ( null ) ;
@@ -186,22 +194,7 @@ export const LogRowContent = memo(function LogRowContent({
186
194
}
187
195
} , [ isExpanded , onExpandHeight , dataRow ] ) ;
188
196
189
- const addSearchFilter = useCallback (
190
- ( {
191
- key,
192
- value,
193
- negated,
194
- } : {
195
- key : string ;
196
- value : string | number | boolean ;
197
- negated ?: boolean ;
198
- } ) => {
199
- const newSearch = search . copy ( ) ;
200
- newSearch . addFilterValue ( `${ negated ? '!' : '' } ${ key } ` , String ( value ) ) ;
201
- setLogsSearch ( newSearch ) ;
202
- } ,
203
- [ setLogsSearch , search ]
204
- ) ;
197
+ const addSearchFilter = useLogsAddSearchFilter ( ) ;
205
198
const theme = useTheme ( ) ;
206
199
207
200
const severityNumber = dataRow [ OurLogKnownFieldKey . SEVERITY_NUMBER ] ;
@@ -383,6 +376,13 @@ function LogRowDetails({
383
376
} ) {
384
377
const location = useLocation ( ) ;
385
378
const organization = useOrganization ( ) ;
379
+ const navigate = useNavigate ( ) ;
380
+ const { onClick : betterCopyToClipboard } = useCopyToClipboard ( {
381
+ text : ourlogToJson ( dataRow ) ,
382
+ successMessage : t ( 'Copied!' ) ,
383
+ errorMessage : t ( 'Failed to copy' ) ,
384
+ } ) ;
385
+ const addSearchFilter = useLogsAddSearchFilter ( ) ;
386
386
const project = useProjectFromId ( {
387
387
project_id : '' + dataRow [ OurLogKnownFieldKey . PROJECT_ID ] ,
388
388
} ) ;
@@ -469,6 +469,88 @@ function LogRowDetails({
469
469
</ Fragment >
470
470
) }
471
471
</ LogDetailTableBodyCell >
472
+ { ! isPending && data && (
473
+ < LogDetailTableActionsCell
474
+ colSpan = { colSpan }
475
+ style = { {
476
+ alignItems : 'center' ,
477
+ justifyContent : 'space-between' ,
478
+ flexDirection : 'row' ,
479
+ } }
480
+ >
481
+ < LogDetailTableActionsButtonBar >
482
+ < Button
483
+ priority = "link"
484
+ size = "sm"
485
+ borderless
486
+ onClick = { ( ) => {
487
+ addSearchFilter ( {
488
+ key : OurLogKnownFieldKey . MESSAGE ,
489
+ value : dataRow [ OurLogKnownFieldKey . MESSAGE ] ,
490
+ } ) ;
491
+ } }
492
+ >
493
+ < IconAdd size = "md" style = { { paddingRight : space ( 0.5 ) } } />
494
+ { t ( 'Add to filter' ) }
495
+ </ Button >
496
+ < Button
497
+ priority = "link"
498
+ size = "sm"
499
+ borderless
500
+ onClick = { ( ) => {
501
+ addSearchFilter ( {
502
+ key : OurLogKnownFieldKey . MESSAGE ,
503
+ value : dataRow [ OurLogKnownFieldKey . MESSAGE ] ,
504
+ negated : true ,
505
+ } ) ;
506
+ } }
507
+ >
508
+ < IconSubtract size = "md" style = { { paddingRight : space ( 0.5 ) } } />
509
+ { t ( 'Exclude from filter' ) }
510
+ </ Button >
511
+ </ LogDetailTableActionsButtonBar >
512
+
513
+ < LogDetailTableActionsButtonBar >
514
+ < Button
515
+ priority = "link"
516
+ size = "sm"
517
+ borderless
518
+ onClick = { ( ) => {
519
+ betterCopyToClipboard ( ) ;
520
+ } }
521
+ >
522
+ < IconJson size = "md" style = { { paddingRight : space ( 0.5 ) } } />
523
+ { t ( 'Copy as JSON' ) }
524
+ </ Button >
525
+ < Button
526
+ priority = "link"
527
+ size = "sm"
528
+ borderless
529
+ onClick = { ( ) => {
530
+ const traceId = adjustLogTraceID ( dataRow [ OurLogKnownFieldKey . TRACE_ID ] ) ;
531
+ const locationStripped = stripLogParamsFromLocation ( location ) ;
532
+ const timestamp = dataRow [ OurLogKnownFieldKey . TIMESTAMP ] ;
533
+ const target = getTraceDetailsUrl ( {
534
+ traceSlug : traceId ,
535
+ spanId : dataRow [ OurLogKnownFieldKey . SPAN_ID ] as string | undefined ,
536
+ timestamp :
537
+ typeof timestamp === 'string' || typeof timestamp === 'number'
538
+ ? timestamp
539
+ : undefined ,
540
+ organization,
541
+ dateSelection : locationStripped ,
542
+ location : locationStripped ,
543
+ source : TraceViewSources . LOGS ,
544
+ } ) ;
545
+ navigate ( target ) ;
546
+ } }
547
+ >
548
+ < IconSpan size = "md" style = { { paddingRight : space ( 0.5 ) } } />
549
+ { t ( 'View Trace' ) }
550
+ </ Button >
551
+ </ LogDetailTableActionsButtonBar >
552
+ </ LogDetailTableActionsCell >
553
+ ) }
472
554
</ DetailsWrapper >
473
555
) ;
474
556
}
0 commit comments