@@ -11,7 +11,7 @@ import {TextArea} from '@sentry/scraps/textarea';
1111
1212import { addErrorMessage } from 'sentry/actionCreators/indicator' ;
1313import { openConfirmModal } from 'sentry/components/confirm' ;
14- import { DropdownMenu } from 'sentry/components/dropdownMenu' ;
14+ import { DropdownMenu , type MenuItemProps } from 'sentry/components/dropdownMenu' ;
1515import { Duration } from 'sentry/components/duration' ;
1616import { SeerMarkdown } from 'sentry/components/seer/markdown' ;
1717import { ChartContent } from 'sentry/components/seer/markdown/embeds/components/chart' ;
@@ -21,7 +21,6 @@ import {
2121 IconChevron ,
2222 IconClose ,
2323 IconEllipsis ,
24- IconRefresh ,
2524 IconReturn ,
2625 IconSeer ,
2726} from 'sentry/icons' ;
@@ -62,9 +61,15 @@ export function InvestigationCell({
6261 investigation,
6362} : InvestigationCellProps ) {
6463 const organizationSlug = useOrganization ( ) . slug ;
65- const [ panelOpen , setPanelOpen ] = useState ( false ) ;
66- const [ traceExecutionId , setTraceExecutionId ] = useState < string | null > ( null ) ;
67- const [ showPrompt , setShowPrompt ] = useState ( true ) ;
64+ const activeExecutionId = isExecutionActive ( block . currentExecution ?. status )
65+ ? ( block . currentExecution ?. id ?? null )
66+ : null ;
67+ const autoOpenedExecutionId = useRef ( activeExecutionId ) ;
68+ const [ panelOpen , setPanelOpen ] = useState ( Boolean ( activeExecutionId ) ) ;
69+ const [ traceExecutionId , setTraceExecutionId ] = useState < string | null > (
70+ activeExecutionId
71+ ) ;
72+ const [ showPrompt , setShowPrompt ] = useState ( ! activeExecutionId ) ;
6873 const [ prompt , setPrompt ] = useState ( ( ) =>
6974 block . outputStatus === 'notRun' ? block . generationPrompt : ''
7075 ) ;
@@ -108,6 +113,16 @@ export function InvestigationCell({
108113 { onError : ( ) => addErrorMessage ( t ( 'Unable to delete this cell.' ) ) }
109114 ) ;
110115
116+ useEffect ( ( ) => {
117+ if ( ! activeExecutionId || autoOpenedExecutionId . current === activeExecutionId ) {
118+ return ;
119+ }
120+ autoOpenedExecutionId . current = activeExecutionId ;
121+ setPanelOpen ( true ) ;
122+ setTraceExecutionId ( activeExecutionId ) ;
123+ setShowPrompt ( false ) ;
124+ } , [ activeExecutionId ] ) ;
125+
111126 function openPanel ( ) {
112127 setPanelOpen ( true ) ;
113128 if ( block . currentExecution && isExecutionActive ( block . currentExecution . status ) ) {
@@ -128,38 +143,56 @@ export function InvestigationCell({
128143 setPanelOpen ( true ) ;
129144 setTraceExecutionId ( execution . id ) ;
130145 setShowPrompt ( false ) ;
146+ autoOpenedExecutionId . current = execution . id ;
131147 } catch {
132148 // The mutation owns user-facing error handling.
133149 }
134150 }
135151
136- const refinementButton = (
137- < Button
138- size = "xs"
139- variant = "transparent"
140- icon = { < IconSeer size = "xs" /> }
141- aria-label = { t ( 'Ask Seer about %s' , displayTitle ) }
142- disabled = { waitingForDependencies }
143- onClick = { panelOpen ? ( ) => setPanelOpen ( false ) : openPanel }
144- />
152+ const actionItems : MenuItemProps [ ] = [ ] ;
153+ if ( block . kind === 'query' ) {
154+ actionItems . push ( {
155+ key : 'rerun' ,
156+ label : t ( 'Rerun' ) ,
157+ disabled :
158+ ! canRun ||
159+ rerunMutation . isPending ||
160+ isExecutionActive ( block . currentExecution ?. status ) ||
161+ ! ( block . generationPrompt || block . content ) . trim ( ) ,
162+ onAction : ( ) => void rerun ( ) ,
163+ } ) ;
164+ }
165+ actionItems . push (
166+ {
167+ key : 'refine' ,
168+ label : t ( 'Refine' ) ,
169+ disabled : waitingForDependencies ,
170+ onAction : openPanel ,
171+ } ,
172+ {
173+ key : 'delete' ,
174+ label : t ( 'Delete' ) ,
175+ priority : 'danger' ,
176+ disabled :
177+ ! canRun ||
178+ deleteMutation . isPending ||
179+ isExecutionActive ( block . currentExecution ?. status ) ,
180+ onAction : ( ) =>
181+ openConfirmModal ( {
182+ message : t ( 'Are you sure you want to delete this cell?' ) ,
183+ priority : 'danger' ,
184+ confirmText : t ( 'Delete' ) ,
185+ onConfirm : ( ) =>
186+ deleteMutation . mutate ( {
187+ block,
188+ investigationVersion : investigation . version ,
189+ } ) ,
190+ } ) ,
191+ }
145192 ) ;
146193
147- const queryHeaderActions = (
148- < Flex align = "center" gap = "xs" flexShrink = { 0 } >
149- { refinementButton }
150- < Button
151- size = "xs"
152- variant = "transparent"
153- icon = { < IconRefresh size = "xs" /> }
154- aria-label = { t ( 'Rerun %s' , displayTitle ) }
155- busy = { rerunMutation . isPending }
156- disabled = {
157- ! canRun ||
158- isExecutionActive ( block . currentExecution ?. status ) ||
159- ! ( block . generationPrompt || block . content ) . trim ( )
160- }
161- onClick = { ( ) => void rerun ( ) }
162- />
194+ const cellActions = (
195+ < CellActions flexShrink = { 0 } >
163196 < DropdownMenu
164197 position = "bottom-end"
165198 usePortal
@@ -170,30 +203,9 @@ export function InvestigationCell({
170203 icon : < IconEllipsis size = "xs" /> ,
171204 'aria-label' : t ( 'Cell actions for %s' , displayTitle ) ,
172205 } }
173- items = { [
174- {
175- key : 'delete' ,
176- label : t ( 'Delete' ) ,
177- priority : 'danger' ,
178- disabled :
179- ! canRun ||
180- deleteMutation . isPending ||
181- isExecutionActive ( block . currentExecution ?. status ) ,
182- onAction : ( ) =>
183- openConfirmModal ( {
184- message : t ( 'Are you sure you want to delete this cell?' ) ,
185- priority : 'danger' ,
186- confirmText : t ( 'Delete' ) ,
187- onConfirm : ( ) =>
188- deleteMutation . mutate ( {
189- block,
190- investigationVersion : investigation . version ,
191- } ) ,
192- } ) ,
193- } ,
194- ] }
206+ items = { actionItems }
195207 />
196- </ Flex >
208+ </ CellActions >
197209 ) ;
198210
199211 const panel = panelOpen ? (
@@ -223,7 +235,7 @@ export function InvestigationCell({
223235 { block . kind === 'query' ? (
224236 < Fragment >
225237 < QueryResult
226- actions = { queryHeaderActions }
238+ actions = { cellActions }
227239 block = { block }
228240 progressState = { progressState }
229241 />
@@ -233,8 +245,8 @@ export function InvestigationCell({
233245 < Fragment >
234246 < CellResult
235247 block = { block }
248+ actions = { cellActions }
236249 progressState = { progressState }
237- refinementButton = { refinementButton }
238250 streamedMarkdown = {
239251 isExecutionActive ( block . currentExecution ?. status )
240252 ? streamedTextQuery . data ?. partialMarkdown
@@ -249,20 +261,20 @@ export function InvestigationCell({
249261}
250262
251263function CellResult ( {
264+ actions,
252265 block,
253266 progressState,
254- refinementButton,
255267 streamedMarkdown,
256268} : {
269+ actions : React . ReactNode ;
257270 block : InvestigationBlock ;
258271 progressState : CellProgressState ;
259- refinementButton : React . ReactNode ;
260272 streamedMarkdown ?: string | null ;
261273} ) {
262274 const markdown =
263275 streamedMarkdown ?? getTextOutput ( block . output ) ?? ( block . content . trim ( ) || null ) ;
264276 return (
265- < Stack
277+ < CellHoverSurface
266278 position = "relative"
267279 flex = { 1 }
268280 minWidth = { 0 }
@@ -271,15 +283,15 @@ function CellResult({
271283 data-cell-variant = "unbordered"
272284 >
273285 < Container position = "absolute" top = { 0 } right = { 0 } >
274- { refinementButton }
286+ { actions }
275287 </ Container >
276288 < CellExecutionAlert block = { block } />
277289 { markdown ? (
278290 < SeerMarkdown raw = { markdown } />
279291 ) : (
280292 < CellProgress state = { progressState } />
281293 ) }
282- </ Stack >
294+ </ CellHoverSurface >
283295 ) ;
284296}
285297
@@ -292,7 +304,7 @@ function QueryResult({
292304 block : InvestigationBlock ;
293305 progressState : CellProgressState ;
294306} ) {
295- const [ expanded , setExpanded ] = useState ( true ) ;
307+ const [ expanded , setExpanded ] = useState ( block . config . autoRun !== true ) ;
296308 const output = getQueryOutput ( block . output ) ;
297309 const chart =
298310 output ?. preferredView === 'chart' ? getRenderableChart ( output . chart ) : null ;
@@ -304,19 +316,22 @@ function QueryResult({
304316 getChartMetadata ( chart ) ;
305317
306318 return (
307- < Stack width = "100%" gap = "sm" >
308- < QueryDisclosureButton
309- size = "sm"
310- variant = "transparent"
311- icon = { < IconChevron direction = { expanded ? 'down' : 'right' } size = "xs" /> }
312- aria-label = { t ( 'Toggle %s' , title ) }
313- aria-expanded = { expanded }
314- onClick = { ( ) => setExpanded ( value => ! value ) }
315- >
316- < Text data-test-id = "query-cell-title" size = "sm" tabular >
317- { title }
318- </ Text >
319- </ QueryDisclosureButton >
319+ < CellHoverSurface width = "100%" gap = "sm" >
320+ < Flex width = "100%" align = "center" gap = "xs" data-test-id = "query-cell-toolbar" >
321+ < QueryDisclosureButton
322+ size = "sm"
323+ variant = "transparent"
324+ icon = { < IconChevron direction = { expanded ? 'down' : 'right' } size = "xs" /> }
325+ aria-label = { t ( 'Toggle %s' , title ) }
326+ aria-expanded = { expanded }
327+ onClick = { ( ) => setExpanded ( value => ! value ) }
328+ >
329+ < Text data-test-id = "query-cell-title" size = "sm" tabular >
330+ { title }
331+ </ Text >
332+ </ QueryDisclosureButton >
333+ { actions }
334+ </ Flex >
320335 { expanded ? (
321336 < Stack
322337 width = "100%"
@@ -348,7 +363,6 @@ function QueryResult({
348363 </ Text >
349364 ) : null }
350365 </ Stack >
351- { actions }
352366 </ Flex >
353367 < Container width = "100%" overflow = "hidden" padding = { chart ? 'md lg' : '0' } >
354368 < CellExecutionAlert block = { block } />
@@ -364,7 +378,7 @@ function QueryResult({
364378 </ Container >
365379 </ Stack >
366380 ) : null }
367- </ Stack >
381+ </ CellHoverSurface >
368382 ) ;
369383}
370384
@@ -449,6 +463,15 @@ function getCellProgressState(
449463 return 'waiting' ;
450464}
451465
466+ export function shouldDisplayInvestigationBlock (
467+ block : InvestigationBlock ,
468+ blocks : InvestigationBlock [ ]
469+ ) {
470+ // Waiting cells have no useful content yet. Dependency failures and cancellations
471+ // remain visible so users can understand why downstream work stopped.
472+ return getCellProgressState ( block , blocks ) !== 'waiting' ;
473+ }
474+
452475export function shouldPollInvestigationBlocks ( blocks : InvestigationBlock [ ] ) {
453476 return blocks . some (
454477 block =>
@@ -689,7 +712,7 @@ function RefinementPanel({
689712
690713 return (
691714 < RefinementDisclosure defaultExpanded size = "sm" >
692- < Disclosure . Title
715+ < AgentActivityDisclosureTitle
693716 leadingItems = { < IconSeer size = "xs" animation = { active ? 'waiting' : undefined } /> }
694717 trailingItems = {
695718 < Flex align = "center" gap = "sm" >
@@ -704,8 +727,8 @@ function RefinementPanel({
704727 </ Flex >
705728 }
706729 >
707- < Text monospace > { getExecutionTitle ( status ) } </ Text >
708- </ Disclosure . Title >
730+ < AgentActivityTitle monospace > { getExecutionTitle ( status ) } </ AgentActivityTitle >
731+ </ AgentActivityDisclosureTitle >
709732 < RefinementDisclosureContent >
710733 < Stack gap = "md" >
711734 < Transcript
@@ -1102,11 +1125,32 @@ function getSeriesName(series: {label: string} | {name: string}) {
11021125}
11031126
11041127const QueryDisclosureButton = styled ( Button ) `
1105- width: 100% ;
1128+ flex: 1 ;
11061129 justify-content: flex-start;
1130+ padding-inline: ${ p => p . theme . space . xs } ;
11071131 text-align: left;
11081132` ;
11091133
1134+ const CellActions = styled ( Flex ) `
1135+ opacity: 0;
1136+ pointer-events: none;
1137+ ` ;
1138+
1139+ const CellHoverSurface = styled ( Stack ) `
1140+ &:hover ${ CellActions } ,
1141+ &:focus-within ${ CellActions } {
1142+ opacity: 1;
1143+ pointer-events: auto;
1144+ }
1145+
1146+ @media (hover: none) {
1147+ ${ CellActions } {
1148+ opacity: 1;
1149+ pointer-events: auto;
1150+ }
1151+ }
1152+ ` ;
1153+
11101154const QueryTable = styled ( 'table' ) `
11111155 min-width: 100%;
11121156 border-collapse: collapse;
@@ -1115,6 +1159,24 @@ const QueryTable = styled('table')`
11151159const RefinementDisclosure = styled ( Disclosure ) `
11161160 width: 100%;
11171161 margin-top: ${ p => p . theme . space . lg } ;
1162+
1163+ & > div:first-child {
1164+ padding-inline: ${ p => p . theme . space [ '2xs' ] } ;
1165+ }
1166+ ` ;
1167+
1168+ const AgentActivityDisclosureTitle = styled ( Disclosure . Title ) `
1169+ padding-inline: 0;
1170+ ` ;
1171+
1172+ const AgentActivityTitle = styled ( Text ) `
1173+ font-size: ${ p => p . theme . font . size . sm } ;
1174+ font-style: normal;
1175+ font-weight: 700;
1176+ line-height: ${ p => p . theme . font . lineHeight . fixed } ;
1177+ letter-spacing: 0;
1178+ vertical-align: middle;
1179+ font-variant-numeric: lining-nums tabular-nums;
11181180` ;
11191181
11201182const RefinementPrompt = styled ( 'div' ) `
0 commit comments