1- import { Fragment , useMemo , useState } from 'react' ;
1+ import { Fragment , useCallback , useMemo , useState } from 'react' ;
22import styled from '@emotion/styled' ;
33
44import { Tag } from '@sentry/scraps/badge' ;
55import { Container , Flex } from '@sentry/scraps/layout' ;
66import { Heading , Text } from '@sentry/scraps/text' ;
77
8+ import { bulkUpdate } from 'sentry/actionCreators/group' ;
9+ import { openConfirmModal } from 'sentry/components/confirm' ;
810import { Button } from 'sentry/components/core/button' ;
911import { ButtonBar } from 'sentry/components/core/button/buttonBar' ;
1012import { Checkbox } from 'sentry/components/core/checkbox' ;
@@ -31,6 +33,7 @@ import {
3133 IconClock ,
3234 IconClose ,
3335 IconCopy ,
36+ IconEllipsis ,
3437 IconFire ,
3538 IconFix ,
3639 IconSeer ,
@@ -40,8 +43,10 @@ import {
4043import { t , tn } from 'sentry/locale' ;
4144import { space } from 'sentry/styles/space' ;
4245import type { Group } from 'sentry/types/group' ;
46+ import { GroupStatus , GroupSubstatus } from 'sentry/types/group' ;
4347import { getMessage , getTitle } from 'sentry/utils/events' ;
4448import { useApiQuery } from 'sentry/utils/queryClient' ;
49+ import useApi from 'sentry/utils/useApi' ;
4550import useCopyToClipboard from 'sentry/utils/useCopyToClipboard' ;
4651import useOrganization from 'sentry/utils/useOrganization' ;
4752import usePageFilters from 'sentry/utils/usePageFilters' ;
@@ -361,7 +366,9 @@ function ClusterCard({
361366 onTagClick ?: ( tag : string ) => void ;
362367 selectedTags ?: Set < string > ;
363368} ) {
369+ const api = useApi ( ) ;
364370 const organization = useOrganization ( ) ;
371+ const { selection} = usePageFilters ( ) ;
365372 const [ showDescription , setShowDescription ] = useState ( false ) ;
366373 const clusterStats = useClusterStats ( cluster . group_ids ) ;
367374 const { copy} = useCopyToClipboard ( ) ;
@@ -379,9 +386,64 @@ function ClusterCard({
379386 copy ( formatClusterInfoForClipboard ( cluster ) ) ;
380387 } ;
381388
389+ const handleResolve = useCallback ( ( ) => {
390+ openConfirmModal ( {
391+ header : t ( 'Resolve All Issues in Cluster' ) ,
392+ message : t (
393+ 'Are you sure you want to resolve all %s issues in this cluster?.' ,
394+ cluster . group_ids . length
395+ ) ,
396+ confirmText : t ( 'Resolve All' ) ,
397+ onConfirm : ( ) => {
398+ bulkUpdate (
399+ api ,
400+ {
401+ orgId : organization . slug ,
402+ itemIds : cluster . group_ids . map ( String ) ,
403+ data : { status : GroupStatus . RESOLVED } ,
404+ project : selection . projects ,
405+ environment : selection . environments ,
406+ ...selection . datetime ,
407+ } ,
408+ { }
409+ ) ;
410+ } ,
411+ } ) ;
412+ } , [ api , cluster . group_ids , organization . slug , selection ] ) ;
413+
414+ const handleArchive = useCallback ( ( ) => {
415+ openConfirmModal ( {
416+ header : t ( 'Archive All Issues in Cluster' ) ,
417+ message : t (
418+ 'Are you sure you want to archive all %s issues in this cluster?.' ,
419+ cluster . group_ids . length
420+ ) ,
421+ confirmText : t ( 'Archive All' ) ,
422+ onConfirm : ( ) => {
423+ bulkUpdate (
424+ api ,
425+ {
426+ orgId : organization . slug ,
427+ itemIds : cluster . group_ids . map ( String ) ,
428+ data : {
429+ status : GroupStatus . IGNORED ,
430+ statusDetails : { } ,
431+ substatus : GroupSubstatus . ARCHIVED_UNTIL_ESCALATING ,
432+ } ,
433+ project : selection . projects ,
434+ environment : selection . environments ,
435+ ...selection . datetime ,
436+ } ,
437+ { }
438+ ) ;
439+ } ,
440+ } ) ;
441+ } , [ api , cluster . group_ids , organization . slug , selection ] ) ;
442+
443+ const handleDismiss = ( ) => { } ;
444+
382445 return (
383446 < CardContainer >
384- { /* Zone 1: Title + Description (Primary Focus) */ }
385447 < CardHeader >
386448 < ClusterTitle > { renderWithInlineCode ( cluster . title ) } </ ClusterTitle >
387449 < ClusterTags
@@ -392,7 +454,12 @@ function ClusterCard({
392454 { cluster . description && (
393455 < Fragment >
394456 { showDescription ? (
395- < DescriptionText > { cluster . description } </ DescriptionText >
457+ < Fragment >
458+ < DescriptionText > { cluster . description } </ DescriptionText >
459+ < ReadMoreButton onClick = { ( ) => setShowDescription ( false ) } >
460+ { t ( 'Collapse summary' ) }
461+ </ ReadMoreButton >
462+ </ Fragment >
396463 ) : (
397464 < ReadMoreButton onClick = { ( ) => setShowDescription ( true ) } >
398465 { t ( 'View summary' ) }
@@ -402,7 +469,6 @@ function ClusterCard({
402469 ) }
403470 </ CardHeader >
404471
405- { /* Zone 2: Stats (Secondary Context) */ }
406472 < ClusterStatsBar >
407473 { cluster . fixability_score !== null && cluster . fixability_score !== undefined && (
408474 < StatItem >
@@ -469,7 +535,6 @@ function ClusterCard({
469535 ) }
470536 </ ClusterStatsBar >
471537
472- { /* Zone 3: Nested Issues (Detail Content) */ }
473538 < IssuesSection >
474539 < IssuesSectionHeader >
475540 < Text size = "sm" bold uppercase >
@@ -481,7 +546,6 @@ function ClusterCard({
481546 </ IssuesList >
482547 </ IssuesSection >
483548
484- { /* Zone 4: Actions (Tertiary) */ }
485549 < CardFooter >
486550 < ButtonBar merged gap = "0" >
487551 < SeerButton
@@ -520,6 +584,34 @@ function ClusterCard({
520584 { t ( 'View All Issues' ) + ` (${ cluster . group_ids . length } )` }
521585 </ Button >
522586 </ Link >
587+ < DropdownMenu
588+ items = { [
589+ {
590+ key : 'resolve' ,
591+ label : t ( 'Resolve All' ) ,
592+ onAction : handleResolve ,
593+ } ,
594+ {
595+ key : 'archive' ,
596+ label : t ( 'Archive All' ) ,
597+ onAction : handleArchive ,
598+ } ,
599+ {
600+ key : 'dismiss' ,
601+ label : t ( 'Dismiss' ) ,
602+ onAction : handleDismiss ,
603+ } ,
604+ ] }
605+ trigger = { triggerProps => (
606+ < Button
607+ { ...triggerProps }
608+ size = "sm"
609+ icon = { < IconEllipsis size = "sm" /> }
610+ aria-label = { t ( 'More actions' ) }
611+ />
612+ ) }
613+ position = "bottom-end"
614+ />
523615 </ CardFooter >
524616 </ CardContainer >
525617 ) ;
0 commit comments