@@ -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,64 @@ 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 > { getType ( usage . workspaceType ) } </ span >
272
+ < span className = "text-sm text-gray-400" >
273
+ { usage . workspaceClass }
274
+ </ span >
275
+ </ div >
276
+ < div className = "flex flex-col col-span-5 my-auto" >
277
+ < span className = "truncate text-gray-700" >
278
+ { usage . workspaceId }
279
+ </ span >
280
+ < span className = "text-sm truncate text-gray-400" >
281
+ { toRemoteURL ( usage . contextURL ) }
282
+ </ span >
283
+ </ div >
284
+ < div className = "flex flex-col my-auto" >
285
+ < span className = "text-right text-gray-700" >
286
+ { usage . credits . toFixed ( 1 ) }
287
+ </ span >
288
+ < span className = "text-right text-sm text-gray-400" >
289
+ { getMinutes ( usage ) }
290
+ </ span >
291
+ </ div >
292
+ < div className = "my-auto" />
293
+ < div className = "flex flex-col col-span-3 my-auto" >
294
+ < span className = "text-gray-400 truncate" >
295
+ { displayTime ( usage . startTime ) }
296
+ </ span >
297
+ < div className = "flex" >
298
+ { usage . workspaceType === "prebuild" ? (
299
+ < UsageIcon className = "my-auto" />
300
+ ) : (
301
+ ""
302
+ ) }
303
+ { usage . workspaceType === "prebuild" ? (
304
+ < span className = "text-sm text-gray-400" > Gitpod</ span >
305
+ ) : (
306
+ < div className = "flex" >
307
+ < img
308
+ className = "my-auto rounded-full w-4 h-4 inline-block align-text-bottom mr-2 overflow-hidden"
309
+ src = { usage . user ?. avatarUrl || "" }
310
+ alt = "user avatar"
311
+ />
312
+ < span className = "text-sm text-gray-400" >
313
+ { usage . user ?. name }
314
+ </ span >
315
+ </ div >
316
+ ) }
317
+ </ div >
318
+ </ div >
265
319
</ div >
266
- </ div >
267
- ) ) }
320
+ ) ;
321
+ } ) }
268
322
</ ItemsList >
269
323
{ billedUsage . length > resultsPerPage && (
270
324
< Pagination
0 commit comments