1
+ import React , { useState , useEffect } from 'react' ;
2
+ import { Card , Button , Divider , Alert , message , Table , Tag , Input , Space , Tooltip } from 'antd' ;
3
+ import { SyncOutlined , CloudUploadOutlined , CodeOutlined } from '@ant-design/icons' ;
4
+ import Title from 'antd/lib/typography/Title' ;
5
+ import { Environment } from '../types/environment.types' ;
6
+ import { Workspace } from '../types/workspace.types' ;
7
+ import { Query } from '../types/query.types' ;
8
+ import { getMergedWorkspaceQueries } from '../services/query.service' ;
9
+ import { Switch , Spin , Empty } from 'antd' ;
10
+ import { ManagedObjectType , setManagedObject , unsetManagedObject } from '../services/managed-objects.service' ;
11
+ import { useDeployModal } from '../context/DeployModalContext' ;
12
+ import { queryConfig } from '../config/query.config' ;
13
+
14
+ const { Search } = Input ;
15
+
16
+ interface QueriesTabProps {
17
+ environment : Environment ;
18
+ workspace : Workspace ;
19
+ }
20
+
21
+ const QueriesTab : React . FC < QueriesTabProps > = ( { environment, workspace } ) => {
22
+ const [ queries , setQueries ] = useState < Query [ ] > ( [ ] ) ;
23
+ const [ stats , setStats ] = useState ( {
24
+ total : 0 ,
25
+ managed : 0 ,
26
+ unmanaged : 0
27
+ } ) ;
28
+ const [ loading , setLoading ] = useState ( false ) ;
29
+ const [ refreshing , setRefreshing ] = useState ( false ) ;
30
+ const [ error , setError ] = useState < string | null > ( null ) ;
31
+ const [ searchText , setSearchText ] = useState ( '' ) ;
32
+ const { openDeployModal } = useDeployModal ( ) ;
33
+
34
+ // Fetch queries
35
+ const fetchQueries = async ( ) => {
36
+ if ( ! workspace . id || ! environment ) return ;
37
+
38
+ setLoading ( true ) ;
39
+ setError ( null ) ;
40
+
41
+ try {
42
+ const result = await getMergedWorkspaceQueries (
43
+ workspace . id ,
44
+ environment . environmentId ,
45
+ environment . environmentApikey ,
46
+ environment . environmentApiServiceUrl !
47
+ ) ;
48
+
49
+ setQueries ( result . queries ) ;
50
+ setStats ( result . stats ) ;
51
+ } catch ( err ) {
52
+ setError ( err instanceof Error ? err . message : "Failed to fetch queries" ) ;
53
+ } finally {
54
+ setLoading ( false ) ;
55
+ setRefreshing ( false ) ;
56
+ }
57
+ } ;
58
+
59
+ useEffect ( ( ) => {
60
+ fetchQueries ( ) ;
61
+ } , [ environment , workspace ] ) ;
62
+
63
+ // Handle refresh
64
+ const handleRefresh = ( ) => {
65
+ setRefreshing ( true ) ;
66
+ fetchQueries ( ) ;
67
+ } ;
68
+
69
+ // Toggle managed status
70
+ const handleToggleManaged = async ( query : Query , checked : boolean ) => {
71
+ setRefreshing ( true ) ;
72
+ try {
73
+ if ( checked ) {
74
+ await setManagedObject (
75
+ query . gid ,
76
+ environment . environmentId ,
77
+ ManagedObjectType . QUERY ,
78
+ query . name
79
+ ) ;
80
+ } else {
81
+ await unsetManagedObject (
82
+ query . gid ,
83
+ environment . environmentId ,
84
+ ManagedObjectType . QUERY
85
+ ) ;
86
+ }
87
+
88
+ // Update the query in state
89
+ const updatedQueries = queries . map ( item => {
90
+ if ( item . id === query . id ) {
91
+ return { ...item , managed : checked } ;
92
+ }
93
+ return item ;
94
+ } ) ;
95
+
96
+ setQueries ( updatedQueries ) ;
97
+
98
+ // Update stats
99
+ const managed = updatedQueries . filter ( q => q . managed ) . length ;
100
+ setStats ( prev => ( {
101
+ ...prev ,
102
+ managed,
103
+ unmanaged : prev . total - managed
104
+ } ) ) ;
105
+
106
+ message . success ( `${ query . name } is now ${ checked ? 'Managed' : 'Unmanaged' } ` ) ;
107
+ return true ;
108
+ } catch ( error ) {
109
+ message . error ( `Failed to change managed status for ${ query . name } ` ) ;
110
+ return false ;
111
+ } finally {
112
+ setRefreshing ( false ) ;
113
+ }
114
+ } ;
115
+
116
+ // Filter queries based on search
117
+ const filteredQueries = searchText
118
+ ? queries . filter ( query =>
119
+ query . name . toLowerCase ( ) . includes ( searchText . toLowerCase ( ) ) ||
120
+ query . id . toLowerCase ( ) . includes ( searchText . toLowerCase ( ) ) )
121
+ : queries ;
122
+
123
+ // Table columns
124
+ const columns = [
125
+ {
126
+ title : 'Name' ,
127
+ dataIndex : 'name' ,
128
+ key : 'name' ,
129
+ render : ( text : string ) => < span className = "query-name" > { text } </ span >
130
+ } ,
131
+ {
132
+ title : 'ID' ,
133
+ dataIndex : 'id' ,
134
+ key : 'id' ,
135
+ ellipsis : true ,
136
+ } ,
137
+ {
138
+ title : 'Creator' ,
139
+ dataIndex : 'creatorName' ,
140
+ key : 'creatorName' ,
141
+ } ,
142
+ {
143
+ title : 'Managed' ,
144
+ key : 'managed' ,
145
+ render : ( _ : any , query : Query ) => (
146
+ < Switch
147
+ checked = { ! ! query . managed }
148
+ onChange = { ( checked : boolean ) => handleToggleManaged ( query , checked ) }
149
+ loading = { refreshing }
150
+ size = "small"
151
+ />
152
+ ) ,
153
+ } ,
154
+ {
155
+ title : 'Actions' ,
156
+ key : 'actions' ,
157
+ render : ( _ : any , query : Query ) => (
158
+ < Space onClick = { ( e ) => e . stopPropagation ( ) } >
159
+ < Tooltip title = { ! query . managed ? "Query must be managed before it can be deployed" : "Deploy this query to another environment" } >
160
+ < Button
161
+ type = "primary"
162
+ size = "small"
163
+ icon = { < CloudUploadOutlined /> }
164
+ onClick = { ( ) => openDeployModal ( query , queryConfig , environment ) }
165
+ disabled = { ! query . managed }
166
+ >
167
+ Deploy
168
+ </ Button >
169
+ </ Tooltip >
170
+ </ Space >
171
+ ) ,
172
+ }
173
+ ] ;
174
+
175
+ return (
176
+ < Card >
177
+ { /* Header with refresh button */ }
178
+ < div style = { { display : "flex" , justifyContent : "space-between" , alignItems : "center" , marginBottom : "16px" } } >
179
+ < Title level = { 5 } > Queries in this Workspace</ Title >
180
+ < Button
181
+ icon = { < SyncOutlined spin = { refreshing } /> }
182
+ onClick = { handleRefresh }
183
+ loading = { loading }
184
+ >
185
+ Refresh
186
+ </ Button >
187
+ </ div >
188
+
189
+ { /* Stats display */ }
190
+ < div style = { { display : 'flex' , flexWrap : 'wrap' , gap : '24px' , marginBottom : '16px' } } >
191
+ < div >
192
+ < div style = { { fontSize : '14px' , color : '#8c8c8c' } } > Total Queries</ div >
193
+ < div style = { { fontSize : '24px' , fontWeight : 600 } } > { stats . total } </ div >
194
+ </ div >
195
+ < div >
196
+ < div style = { { fontSize : '14px' , color : '#8c8c8c' } } > Managed</ div >
197
+ < div style = { { fontSize : '24px' , fontWeight : 600 } } > { stats . managed } </ div >
198
+ </ div >
199
+ < div >
200
+ < div style = { { fontSize : '14px' , color : '#8c8c8c' } } > Unmanaged</ div >
201
+ < div style = { { fontSize : '24px' , fontWeight : 600 } } > { stats . unmanaged } </ div >
202
+ </ div >
203
+ </ div >
204
+
205
+ < Divider style = { { margin : "16px 0" } } />
206
+
207
+ { /* Error display */ }
208
+ { error && (
209
+ < Alert
210
+ message = "Error loading queries"
211
+ description = { error }
212
+ type = "error"
213
+ showIcon
214
+ style = { { marginBottom : "16px" } }
215
+ />
216
+ ) }
217
+
218
+ { /* Configuration warnings */ }
219
+ { ( ! environment . environmentApikey || ! environment . environmentApiServiceUrl ) && ! error && (
220
+ < Alert
221
+ message = "Configuration Issue"
222
+ description = "Missing required configuration: API key or API service URL"
223
+ type = "warning"
224
+ showIcon
225
+ style = { { marginBottom : "16px" } }
226
+ />
227
+ ) }
228
+
229
+ { /* Content */ }
230
+ { loading ? (
231
+ < div style = { { display : 'flex' , justifyContent : 'center' , padding : '20px' } } >
232
+ < Spin tip = "Loading queries..." />
233
+ </ div >
234
+ ) : queries . length === 0 ? (
235
+ < Empty
236
+ description = { error || "No queries found in this workspace" }
237
+ image = { Empty . PRESENTED_IMAGE_SIMPLE }
238
+ />
239
+ ) : (
240
+ < >
241
+ { /* Search Bar */ }
242
+ < div style = { { marginBottom : 16 } } >
243
+ < Search
244
+ placeholder = "Search queries by name or ID"
245
+ allowClear
246
+ onSearch = { value => setSearchText ( value ) }
247
+ onChange = { e => setSearchText ( e . target . value ) }
248
+ style = { { width : 300 } }
249
+ />
250
+ { searchText && filteredQueries . length !== queries . length && (
251
+ < div style = { { marginTop : 8 } } >
252
+ Showing { filteredQueries . length } of { queries . length } queries
253
+ </ div >
254
+ ) }
255
+ </ div >
256
+
257
+ < Table
258
+ columns = { columns }
259
+ dataSource = { filteredQueries }
260
+ rowKey = "id"
261
+ pagination = { { pageSize : 10 } }
262
+ size = "middle"
263
+ scroll = { { x : 'max-content' } }
264
+ />
265
+ </ >
266
+ ) }
267
+ </ Card >
268
+ ) ;
269
+ } ;
270
+
271
+ export default QueriesTab ;
0 commit comments