@@ -22,14 +22,22 @@ import { ErrorCodes } from "@gitpod/gitpod-protocol/lib/messaging/error";
22
22
import { ReactComponent as CreditsSvg } from "../images/credits.svg" ;
23
23
import { ReactComponent as Spinner } from "../icons/Spinner.svg" ;
24
24
import { ReactComponent as SortArrow } from "../images/sort-arrow.svg" ;
25
+ import { ReactComponent as UsageIcon } from "../images/usage-default.svg" ;
25
26
import { BillingMode } from "@gitpod/gitpod-protocol/lib/billing-mode" ;
27
+ import { toRemoteURL } from "../projects/render-utils" ;
28
+ import { User } from "@gitpod/gitpod-protocol" ;
29
+
30
+ interface ExtendedBillableSession extends BillableSession {
31
+ contextURL : string ;
32
+ user ?: User ;
33
+ }
26
34
27
35
function TeamUsage ( ) {
28
36
const { teams } = useContext ( TeamsContext ) ;
29
37
const location = useLocation ( ) ;
30
38
const team = getCurrentTeam ( location , teams ) ;
31
39
const [ teamBillingMode , setTeamBillingMode ] = useState < BillingMode | undefined > ( undefined ) ;
32
- const [ billedUsage , setBilledUsage ] = useState < BillableSession [ ] > ( [ ] ) ;
40
+ const [ billedUsage , setBilledUsage ] = useState < ExtendedBillableSession [ ] > ( [ ] ) ;
33
41
const [ currentPage , setCurrentPage ] = useState ( 1 ) ;
34
42
const [ resultsPerPage ] = useState ( 50 ) ;
35
43
const [ errorMessage , setErrorMessage ] = useState ( "" ) ;
@@ -55,6 +63,9 @@ function TeamUsage() {
55
63
if ( ! team ) {
56
64
return ;
57
65
}
66
+ if ( billedUsage . length === 0 ) {
67
+ setIsLoading ( true ) ;
68
+ }
58
69
( async ( ) => {
59
70
const attributionId = AttributionId . render ( { kind : "team" , teamId : team . id } ) ;
60
71
const request : BillableSessionRequest = {
@@ -64,8 +75,20 @@ function TeamUsage() {
64
75
to : endDateOfBillMonth ,
65
76
} ;
66
77
try {
67
- const billedUsageResult = await getGitpodService ( ) . server . listBilledUsage ( request ) ;
68
- setBilledUsage ( billedUsageResult ) ;
78
+ const { server } = getGitpodService ( ) ;
79
+ const billedUsageResult = await server . listBilledUsage ( request ) ;
80
+ const extendedResult = await Promise . all (
81
+ billedUsageResult . map ( async ( res ) => {
82
+ const ws = await server . getWorkspaceForBillableSession ( res . workspaceId , attributionId ) ;
83
+ if ( ! res . userId ) {
84
+ return Object . assign ( res , { contextURL : ws . contextURL } ) ;
85
+ } else {
86
+ const user = await server . getUserForBillableSession ( res . userId , res . attributionId ) ;
87
+ return Object . assign ( res , { contextURL : ws . contextURL , user : user } ) ;
88
+ }
89
+ } ) ,
90
+ ) ;
91
+ setBilledUsage ( extendedResult ) ;
69
92
} catch ( error ) {
70
93
if ( error . code === ErrorCodes . PERMISSION_DENIED ) {
71
94
setErrorMessage ( "Access to usage details is restricted to team owners." ) ;
@@ -141,7 +164,7 @@ function TeamUsage() {
141
164
const displayTime = ( time : string ) => {
142
165
const options : Intl . DateTimeFormatOptions = {
143
166
day : "numeric" ,
144
- month : "long " ,
167
+ month : "short " ,
145
168
year : "numeric" ,
146
169
hour : "numeric" ,
147
170
minute : "numeric" ,
@@ -184,7 +207,7 @@ function TeamUsage() {
184
207
</ div >
185
208
</ div >
186
209
</ div >
187
- { billedUsage . length === 0 && ! errorMessage && ! isLoading && (
210
+ { ! isLoading && billedUsage . length === 0 && ! errorMessage && (
188
211
< div className = "flex flex-col w-full mb-8" >
189
212
< h3 className = "text-center text-gray-500 mt-8" > No sessions found.</ h3 >
190
213
< p className = "text-center text-gray-500 mt-1" >
@@ -212,26 +235,23 @@ function TeamUsage() {
212
235
{ billedUsage . length > 0 && ! isLoading && (
213
236
< div className = "flex flex-col w-full mb-8" >
214
237
< ItemsList className = "mt-2 text-gray-500" >
215
- < Item header = { false } className = "grid grid-cols-5 bg-gray-100 mb-5" >
216
- < ItemField className = "my-auto" >
238
+ < Item header = { false } className = "grid grid-cols-12 gap-x-3 bg-gray-100 mb-5" >
239
+ < ItemField className = "col-span-2 my-auto" >
217
240
< span > Type</ span >
218
241
</ ItemField >
219
- < ItemField className = "my-auto" >
220
- < span > Class </ span >
242
+ < ItemField className = "col-span-5 my-auto" >
243
+ < span > ID </ span >
221
244
</ ItemField >
222
245
< ItemField className = "my-auto" >
223
- < span > Usage</ span >
224
- </ ItemField >
225
- < ItemField className = "flex my-auto" >
226
- < CreditsSvg className = "my-auto mr-1" />
227
246
< span > Credits</ span >
228
247
</ ItemField >
229
- < ItemField className = "my-auto cursor-pointer" >
248
+ < ItemField className = "my-auto" />
249
+ < ItemField className = "col-span-3 my-auto cursor-pointer" >
230
250
< span
231
251
className = "flex my-auto"
232
252
onClick = { ( ) => setIsStartedTimeDescending ( ! isStartedTimeDescending ) }
233
253
>
234
- Started
254
+ Timestamp
235
255
< SortArrow
236
256
className = { `h-4 w-4 my-auto ${
237
257
isStartedTimeDescending ? "" : " transform rotate-180"
@@ -241,30 +261,66 @@ function TeamUsage() {
241
261
</ ItemField >
242
262
</ Item >
243
263
{ currentPaginatedResults &&
244
- currentPaginatedResults . map ( ( usage ) => (
245
- < div
246
- key = { usage . instanceId }
247
- className = "flex p-3 grid grid-cols-5 justify-between transition ease-in-out rounded-xl focus:bg-gitpod-kumquat-light"
248
- >
249
- < div className = "my-auto" >
250
- < span > { getType ( usage . workspaceType ) } </ span >
251
- </ div >
252
- < div className = "my-auto" >
253
- < span className = "text-gray-400" > { usage . workspaceClass } </ span >
254
- </ div >
255
- < div className = "my-auto" >
256
- < span className = "text-gray-700" > { getMinutes ( usage ) } </ span >
257
- </ div >
258
- < div className = "my-auto" >
259
- < span className = "text-gray-700" > { usage . credits . toFixed ( 1 ) } </ span >
260
- </ div >
261
- < div className = "my-auto" >
262
- < span className = "text-gray-400" >
263
- { displayTime ( usage . startTime ) }
264
- </ span >
264
+ currentPaginatedResults . map ( ( usage ) => {
265
+ return (
266
+ < div
267
+ key = { usage . instanceId }
268
+ className = "flex p-3 grid grid-cols-12 gap-x-3 justify-between transition ease-in-out rounded-xl focus:bg-gitpod-kumquat-light"
269
+ >
270
+ < div className = "flex flex-col col-span-2 my-auto" >
271
+ < span className = "text-gray-700 dark:text-gray-400" >
272
+ { getType ( usage . workspaceType ) }
273
+ </ span >
274
+ < span className = "text-sm text-gray-400 dark:text-gray-600" >
275
+ { usage . workspaceClass }
276
+ </ span >
277
+ </ div >
278
+ < div className = "flex flex-col col-span-5 my-auto" >
279
+ < span className = "truncate text-gray-700 dark:text-gray-400" >
280
+ { usage . workspaceId }
281
+ </ span >
282
+ < span className = "text-sm truncate text-gray-400 dark:text-gray-600" >
283
+ { toRemoteURL ( usage . contextURL ) }
284
+ </ span >
285
+ </ div >
286
+ < div className = "flex flex-col my-auto" >
287
+ < span className = "text-right text-gray-700 dark:text-gray-400" >
288
+ { usage . credits . toFixed ( 1 ) }
289
+ </ span >
290
+ < span className = "text-right truncate text-sm text-gray-400 dark:text-gray-600" >
291
+ { getMinutes ( usage ) }
292
+ </ span >
293
+ </ div >
294
+ < div className = "my-auto" />
295
+ < div className = "flex flex-col col-span-3 my-auto" >
296
+ < span className = "text-gray-400 truncate" >
297
+ { displayTime ( usage . startTime ) }
298
+ </ span >
299
+ < div className = "flex" >
300
+ { usage . workspaceType === "prebuild" ? (
301
+ < UsageIcon className = "my-auto" />
302
+ ) : (
303
+ ""
304
+ ) }
305
+ { usage . workspaceType === "prebuild" ? (
306
+ < span className = "text-sm text-gray-400" > Gitpod</ span >
307
+ ) : (
308
+ < div className = "flex" >
309
+ < img
310
+ className = "my-auto rounded-full w-4 h-4 inline-block align-text-bottom mr-2 overflow-hidden"
311
+ src = { usage . user ?. avatarUrl || "" }
312
+ alt = "user avatar"
313
+ />
314
+ < span className = "text-sm text-gray-400" >
315
+ { usage . user ?. name }
316
+ </ span >
317
+ </ div >
318
+ ) }
319
+ </ div >
320
+ </ div >
265
321
</ div >
266
- </ div >
267
- ) ) }
322
+ ) ;
323
+ } ) }
268
324
</ ItemsList >
269
325
{ billedUsage . length > resultsPerPage && (
270
326
< Pagination
0 commit comments