@@ -8,20 +8,25 @@ import {t} from 'sentry/locale';
88import { space } from 'sentry/styles/space' ;
99import type { PageFilters } from 'sentry/types/core' ;
1010import { COLUMN_BREAKPOINTS } from 'sentry/views/issueList/actions/utils' ;
11+ import { IssueSortOptions } from 'sentry/views/issueList/utils' ;
1112
1213type Props = {
1314 isReprocessingQuery : boolean ;
1415 onSelectStatsPeriod : ( statsPeriod : string ) => void ;
1516 selection : PageFilters ;
1617 statsPeriod : string ;
1718 isSavedSearchesOpen ?: boolean ;
19+ onSortChange ?: ( sort : string ) => void ;
20+ sort ?: string ;
1821} ;
1922
2023function Headers ( {
2124 selection,
2225 statsPeriod,
2326 onSelectStatsPeriod,
2427 isReprocessingQuery,
28+ onSortChange,
29+ sort,
2530} : Props ) {
2631 return (
2732 < Fragment >
@@ -33,15 +38,33 @@ function Headers({
3338 </ Fragment >
3439 ) : (
3540 < Fragment >
36- < LastSeenLabel breakpoint = { COLUMN_BREAKPOINTS . LAST_SEEN } align = "right" >
37- { t ( 'Last Seen' ) }
38- </ LastSeenLabel >
39- < FirstSeenLabel breakpoint = { COLUMN_BREAKPOINTS . FIRST_SEEN } align = "right" >
40- { t ( 'Age' ) }
41- </ FirstSeenLabel >
41+ < SortableHeader
42+ breakpoint = { COLUMN_BREAKPOINTS . LAST_SEEN }
43+ align = "right"
44+ sortOption = { IssueSortOptions . DATE }
45+ currentSort = { sort }
46+ onSortChange = { onSortChange }
47+ label = { t ( 'Last Seen' ) }
48+ width = "86px"
49+ />
50+ < SortableHeader
51+ breakpoint = { COLUMN_BREAKPOINTS . FIRST_SEEN }
52+ align = "right"
53+ sortOption = { IssueSortOptions . NEW }
54+ currentSort = { sort }
55+ onSortChange = { onSortChange }
56+ label = { t ( 'Age' ) }
57+ width = "50px"
58+ />
4259 < GraphLabel breakpoint = { COLUMN_BREAKPOINTS . TREND } >
4360 < Flex flex = "1" justify = "between" >
44- { t ( 'Trend' ) }
61+ < SortableHeaderText
62+ sortOption = { IssueSortOptions . TRENDS }
63+ currentSort = { sort }
64+ onSortChange = { onSortChange }
65+ >
66+ { t ( 'Trend' ) }
67+ </ SortableHeaderText >
4568 < GraphToggles >
4669 { selection . datetime . period !== '24h' && (
4770 < GraphToggle
@@ -60,12 +83,24 @@ function Headers({
6083 </ GraphToggles >
6184 </ Flex >
6285 </ GraphLabel >
63- < EventsOrUsersLabel breakpoint = { COLUMN_BREAKPOINTS . EVENTS } align = "right" >
64- { t ( 'Events' ) }
65- </ EventsOrUsersLabel >
66- < EventsOrUsersLabel breakpoint = { COLUMN_BREAKPOINTS . USERS } align = "right" >
67- { t ( 'Users' ) }
68- </ EventsOrUsersLabel >
86+ < SortableHeader
87+ breakpoint = { COLUMN_BREAKPOINTS . EVENTS }
88+ align = "right"
89+ sortOption = { IssueSortOptions . FREQ }
90+ currentSort = { sort }
91+ onSortChange = { onSortChange }
92+ label = { t ( 'Events' ) }
93+ width = "60px"
94+ />
95+ < SortableHeader
96+ breakpoint = { COLUMN_BREAKPOINTS . USERS }
97+ align = "right"
98+ sortOption = { IssueSortOptions . USER }
99+ currentSort = { sort }
100+ onSortChange = { onSortChange }
101+ label = { t ( 'Users' ) }
102+ width = "60px"
103+ />
69104 < PriorityLabel breakpoint = { COLUMN_BREAKPOINTS . PRIORITY } align = "left" >
70105 { t ( 'Priority' ) }
71106 </ PriorityLabel >
@@ -78,6 +113,65 @@ function Headers({
78113 ) ;
79114}
80115
116+ function SortableHeader ( {
117+ breakpoint,
118+ align,
119+ sortOption,
120+ currentSort,
121+ onSortChange,
122+ label,
123+ width,
124+ } : {
125+ align : 'left' | 'right' ;
126+ label : string ;
127+ sortOption : IssueSortOptions ;
128+ width : string ;
129+ breakpoint ?: string ;
130+ currentSort ?: string ;
131+ onSortChange ?: ( sort : string ) => void ;
132+ } ) {
133+ const isActive = currentSort === sortOption ;
134+ const isClickable = ! ! onSortChange ;
135+
136+ return (
137+ < SortableLabel
138+ breakpoint = { breakpoint }
139+ align = { align }
140+ isActive = { isActive }
141+ isClickable = { isClickable }
142+ onClick = { isClickable ? ( ) => onSortChange ( sortOption ) : undefined }
143+ style = { { width} }
144+ >
145+ { label }
146+ </ SortableLabel >
147+ ) ;
148+ }
149+
150+ function SortableHeaderText ( {
151+ sortOption,
152+ currentSort,
153+ onSortChange,
154+ children,
155+ } : {
156+ children : React . ReactNode ;
157+ sortOption : IssueSortOptions ;
158+ currentSort ?: string ;
159+ onSortChange ?: ( sort : string ) => void ;
160+ } ) {
161+ const isActive = currentSort === sortOption ;
162+ const isClickable = ! ! onSortChange ;
163+
164+ return (
165+ < SortableText
166+ isActive = { isActive }
167+ isClickable = { isClickable }
168+ onClick = { isClickable ? ( ) => onSortChange ( sortOption ) : undefined }
169+ >
170+ { children }
171+ </ SortableText >
172+ ) ;
173+ }
174+
81175export default Headers ;
82176
83177const GraphLabel = styled ( IssueStreamHeaderLabel ) `
@@ -106,16 +200,41 @@ const GraphToggle = styled('a')<{active: boolean}>`
106200 }
107201` ;
108202
109- const LastSeenLabel = styled ( IssueStreamHeaderLabel ) `
110- width: 86px;
111- ` ;
112-
113- const FirstSeenLabel = styled ( IssueStreamHeaderLabel ) `
114- width: 50px;
203+ const SortableLabel = styled ( IssueStreamHeaderLabel ) < {
204+ isActive : boolean ;
205+ isClickable : boolean ;
206+ } > `
207+ ${ p =>
208+ p . isClickable &&
209+ `
210+ cursor: pointer;
211+ user-select: none;
212+ &:hover {
213+ color: ${ p . theme . tokens . content . primary } ;
214+ }
215+ ` }
216+ ${ p =>
217+ p . isActive &&
218+ `
219+ color: ${ p . theme . tokens . content . primary } ;
220+ ` }
115221` ;
116222
117- const EventsOrUsersLabel = styled ( IssueStreamHeaderLabel ) `
118- width: 60px;
223+ const SortableText = styled ( 'span' ) < { isActive : boolean ; isClickable : boolean } > `
224+ ${ p =>
225+ p . isClickable &&
226+ `
227+ cursor: pointer;
228+ user-select: none;
229+ &:hover {
230+ color: ${ p . theme . tokens . content . primary } ;
231+ }
232+ ` }
233+ ${ p =>
234+ p . isActive &&
235+ `
236+ color: ${ p . theme . tokens . content . primary } ;
237+ ` }
119238` ;
120239
121240const PriorityLabel = styled ( IssueStreamHeaderLabel ) `
0 commit comments