1
- import { useCallback } from 'react' ;
1
+ import { useCallback , useMemo } from 'react' ;
2
2
3
3
import type { Actor } from 'sentry/types/core' ;
4
4
import { defined } from 'sentry/utils' ;
5
5
import { useApiQuery , useQueryClient } from 'sentry/utils/queryClient' ;
6
6
import useOrganization from 'sentry/utils/useOrganization' ;
7
7
import type { Mode } from 'sentry/views/explore/contexts/pageParamsContext/mode' ;
8
8
9
- type Query = {
9
+ export type RawGroupBy = {
10
+ groupBy : string ;
11
+ } ;
12
+
13
+ function isRawGroupBy ( value : any ) : value is RawGroupBy {
14
+ return typeof value === 'object' && typeof value . groupBy === 'string' ;
15
+ }
16
+
17
+ export type RawVisualize = {
18
+ yAxes : string [ ] ;
19
+ chartType ?: number ;
20
+ } ;
21
+
22
+ export function isRawVisualize ( value : any ) : value is RawVisualize {
23
+ return (
24
+ typeof value === 'object' &&
25
+ Array . isArray ( value . yAxes ) &&
26
+ value . yAxes . every ( ( v : any ) => typeof v === 'string' )
27
+ ) ;
28
+ }
29
+
30
+ type ReadableQuery = {
10
31
fields : string [ ] ;
11
- groupby : string [ ] ;
12
32
mode : Mode ;
13
33
orderby : string ;
14
34
query : string ;
15
- visualize : Array < {
16
- chartType : number ;
17
- yAxes : string [ ] ;
18
- } > ;
35
+
36
+ // a query can have either
37
+ // - `aggregateField` which contains a list of group bys and visualizes merged together
38
+ // - `groupby` and `visualize` which contains the group bys and visualizes separately
39
+ aggregateField ?: Array < RawGroupBy | RawVisualize > ;
40
+ groupby ?: string [ ] ;
41
+ visualize ?: RawVisualize [ ] ;
19
42
} ;
20
43
44
+ class Query {
45
+ fields : string [ ] ;
46
+ mode : Mode ;
47
+ orderby : string ;
48
+ query : string ;
49
+
50
+ aggregateField : Array < RawGroupBy | RawVisualize > ;
51
+ groupby : string [ ] ;
52
+ visualize : RawVisualize [ ] ;
53
+
54
+ constructor ( query : ReadableQuery ) {
55
+ this . fields = query . fields ;
56
+ this . mode = query . mode ;
57
+ this . orderby = query . orderby ;
58
+ this . query = query . query ;
59
+
60
+ // for compatibility, we ensure that aggregate fields, group bys and visualizes are all populated
61
+ // we ensure that group bys + visualizes = aggregate fields
62
+ this . groupby =
63
+ query . aggregateField
64
+ ?. filter < RawGroupBy > ( isRawGroupBy )
65
+ . map ( groupBy => groupBy . groupBy ) ??
66
+ query . groupby ??
67
+ [ ] ;
68
+ this . visualize =
69
+ query . aggregateField ?. filter < RawVisualize > ( isRawVisualize ) ?? query . visualize ?? [ ] ;
70
+ this . aggregateField = defined ( query . aggregateField )
71
+ ? query . aggregateField
72
+ : [ ...this . groupby . map ( groupBy => ( { groupBy} ) ) , ...this . visualize ] ;
73
+ }
74
+ }
75
+
21
76
export type SortOption =
22
77
| 'name'
23
78
| '-name'
@@ -30,7 +85,7 @@ export type SortOption =
30
85
| 'mostStarred' ;
31
86
32
87
// Comes from ExploreSavedQueryModelSerializer
33
- export type SavedQuery = {
88
+ type ReadableSavedQuery = {
34
89
dateAdded : string ;
35
90
dateUpdated : string ;
36
91
id : number ;
@@ -39,7 +94,7 @@ export type SavedQuery = {
39
94
name : string ;
40
95
position : number | null ;
41
96
projects : number [ ] ;
42
- query : [ Query , ...Query [ ] ] ;
97
+ query : [ ReadableQuery , ...ReadableQuery [ ] ] ;
43
98
queryDataset : string ;
44
99
starred : boolean ;
45
100
createdBy ?: Actor ;
@@ -50,6 +105,49 @@ export type SavedQuery = {
50
105
start ?: string ;
51
106
} ;
52
107
108
+ export class SavedQuery {
109
+ dateAdded : string ;
110
+ dateUpdated : string ;
111
+ id : number ;
112
+ interval : string ;
113
+ lastVisited : string ;
114
+ name : string ;
115
+ position : number | null ;
116
+ projects : number [ ] ;
117
+ query : [ Query , ...Query [ ] ] ;
118
+ queryDataset : string ;
119
+ starred : boolean ;
120
+ createdBy ?: Actor ;
121
+ end ?: string ;
122
+ environment ?: string [ ] ;
123
+ isPrebuilt ?: boolean ;
124
+ range ?: string ;
125
+ start ?: string ;
126
+
127
+ constructor ( savedQuery : ReadableSavedQuery ) {
128
+ this . dateAdded = savedQuery . dateAdded ;
129
+ this . dateUpdated = savedQuery . dateUpdated ;
130
+ this . id = savedQuery . id ;
131
+ this . interval = savedQuery . interval ;
132
+ this . lastVisited = savedQuery . lastVisited ;
133
+ this . name = savedQuery . name ;
134
+ this . position = savedQuery . position ;
135
+ this . projects = savedQuery . projects ;
136
+ this . query = [
137
+ new Query ( savedQuery . query [ 0 ] ) ,
138
+ ...savedQuery . query . slice ( 1 ) . map ( q => new Query ( q ) ) ,
139
+ ] ;
140
+ this . queryDataset = savedQuery . queryDataset ;
141
+ this . starred = savedQuery . starred ;
142
+ this . createdBy = savedQuery . createdBy ;
143
+ this . end = savedQuery . end ;
144
+ this . environment = savedQuery . environment ;
145
+ this . isPrebuilt = savedQuery . isPrebuilt ;
146
+ this . range = savedQuery . range ;
147
+ this . start = savedQuery . start ;
148
+ }
149
+ }
150
+
53
151
type Props = {
54
152
cursor ?: string ;
55
153
exclude ?: 'owned' | 'shared' ;
@@ -69,7 +167,7 @@ export function useGetSavedQueries({
69
167
} : Props ) {
70
168
const organization = useOrganization ( ) ;
71
169
72
- const { data, isLoading, getResponseHeader, ...rest } = useApiQuery < SavedQuery [ ] > (
170
+ const { data, isLoading, getResponseHeader, ...rest } = useApiQuery < ReadableSavedQuery [ ] > (
73
171
[
74
172
`/organizations/${ organization . slug } /explore/saved/` ,
75
173
{
@@ -90,7 +188,8 @@ export function useGetSavedQueries({
90
188
91
189
const pageLinks = getResponseHeader ?.( 'Link' ) ;
92
190
93
- return { data, isLoading, pageLinks, ...rest } ;
191
+ const savedQueries = useMemo ( ( ) => data ?. map ( q => new SavedQuery ( q ) ) , [ data ] ) ;
192
+ return { data : savedQueries , isLoading, pageLinks, ...rest } ;
94
193
}
95
194
96
195
export function useInvalidateSavedQueries ( ) {
@@ -106,14 +205,15 @@ export function useInvalidateSavedQueries() {
106
205
107
206
export function useGetSavedQuery ( id ?: string ) {
108
207
const organization = useOrganization ( ) ;
109
- const { data, isLoading, ...rest } = useApiQuery < SavedQuery > (
208
+ const { data, isLoading, ...rest } = useApiQuery < ReadableSavedQuery > (
110
209
[ `/organizations/${ organization . slug } /explore/saved/${ id } /` ] ,
111
210
{
112
211
staleTime : 0 ,
113
212
enabled : defined ( id ) ,
114
213
}
115
214
) ;
116
- return { data, isLoading, ...rest } ;
215
+ const savedQuery = useMemo ( ( ) => ( defined ( data ) ? new SavedQuery ( data ) : data ) , [ data ] ) ;
216
+ return { data : savedQuery , isLoading, ...rest } ;
117
217
}
118
218
119
219
export function useInvalidateSavedQuery ( id ?: string ) {
0 commit comments