4
4
* See License.AGPL.txt in the project root for license information.
5
5
*/
6
6
7
- import { forwardRef , useCallback , useEffect , useState } from "react" ;
8
- import { getGitpodService , gitpodHostUrl } from "../service/service" ;
9
- import {
10
- ListUsageRequest ,
11
- Ordering ,
12
- ListUsageResponse ,
13
- WorkspaceInstanceUsageData ,
14
- Usage ,
15
- } from "@gitpod/gitpod-protocol/lib/usage" ;
7
+ import { WorkspaceType } from "@gitpod/gitpod-protocol" ;
16
8
import { AttributionId } from "@gitpod/gitpod-protocol/lib/attribution" ;
17
- import { Item , ItemField , ItemsList } from "../components/ItemsList" ;
18
- import Pagination from "../Pagination/Pagination" ;
19
- import Header from "../components/Header" ;
20
9
import { ErrorCodes } from "@gitpod/gitpod-protocol/lib/messaging/error" ;
10
+ import { ListUsageRequest , Ordering , Usage , WorkspaceInstanceUsageData } from "@gitpod/gitpod-protocol/lib/usage" ;
11
+ import dayjs from "dayjs" ;
12
+ import { forwardRef , useEffect , useMemo , useState } from "react" ;
13
+ import DatePicker from "react-datepicker" ;
14
+ import "react-datepicker/dist/react-datepicker.css" ;
15
+ import { useLocation } from "react-router" ;
16
+ import Header from "../components/Header" ;
17
+ import { Item , ItemField , ItemsList } from "../components/ItemsList" ;
18
+ import { useListUsage } from "../data/usage/usage-query" ;
19
+ import { useWorkspaceClasses } from "../data/workspaces/workspace-classes-query" ;
21
20
import Spinner from "../icons/Spinner.svg" ;
22
21
import { ReactComponent as UsageIcon } from "../images/usage-default.svg" ;
22
+ import Pagination from "../Pagination/Pagination" ;
23
23
import { toRemoteURL } from "../projects/render-utils" ;
24
- import { WorkspaceType } from "@gitpod/gitpod-protocol" ;
25
- import DatePicker from "react-datepicker" ;
26
- import "react-datepicker/dist/react-datepicker.css" ;
24
+ import { gitpodHostUrl } from "../service/service" ;
27
25
import "./react-datepicker.css" ;
28
- import { useLocation } from "react-router" ;
29
- import dayjs from "dayjs" ;
30
26
import { Heading1 , Heading2 , Subheading } from "./typography/headings" ;
31
- import { useWorkspaceClasses } from "../data/workspaces/workspace-classes-query" ;
32
27
33
28
interface UsageViewProps {
34
29
attributionId : AttributionId ;
35
30
}
36
31
37
32
function UsageView ( { attributionId } : UsageViewProps ) {
38
- const [ usagePage , setUsagePage ] = useState < ListUsageResponse | undefined > ( undefined ) ;
33
+ const [ page , setPage ] = useState ( 1 ) ;
39
34
const [ errorMessage , setErrorMessage ] = useState ( "" ) ;
40
35
const startOfCurrentMonth = dayjs ( ) . startOf ( "month" ) ;
41
36
const [ startDate , setStartDate ] = useState ( startOfCurrentMonth ) ;
42
37
const [ endDate , setEndDate ] = useState ( dayjs ( ) ) ;
43
- const [ totalCreditsUsed , setTotalCreditsUsed ] = useState < number > ( 0 ) ;
44
- const [ isLoading , setIsLoading ] = useState < boolean > ( true ) ;
45
38
const supportedClasses = useWorkspaceClasses ( ) ;
46
-
47
39
const location = useLocation ( ) ;
48
40
useEffect ( ( ) => {
49
41
const match = / # ( \d { 4 } - \d { 2 } - \d { 2 } ) : ( \d { 4 } - \d { 2 } - \d { 2 } ) / . exec ( location . hash ) ;
@@ -56,39 +48,29 @@ function UsageView({ attributionId }: UsageViewProps) {
56
48
}
57
49
}
58
50
} , [ location ] ) ;
51
+ const request = useMemo ( ( ) => {
52
+ const request : ListUsageRequest = {
53
+ attributionId : AttributionId . render ( attributionId ) ,
54
+ from : startDate . startOf ( "day" ) . valueOf ( ) ,
55
+ to : endDate . endOf ( "day" ) . valueOf ( ) ,
56
+ order : Ordering . ORDERING_DESCENDING ,
57
+ pagination : {
58
+ perPage : 50 ,
59
+ page,
60
+ } ,
61
+ } ;
62
+ return request ;
63
+ } , [ attributionId , endDate , page , startDate ] ) ;
64
+ const usagePage = useListUsage ( request ) ;
65
+
66
+ if ( usagePage . error ) {
67
+ if ( ( usagePage . error as any ) . code === ErrorCodes . PERMISSION_DENIED ) {
68
+ setErrorMessage ( "Access to usage details is restricted to team owners." ) ;
69
+ } else {
70
+ setErrorMessage ( `Error: ${ usagePage . error ?. message } ` ) ;
71
+ }
72
+ }
59
73
60
- const loadPage = useCallback (
61
- async ( page : number = 1 ) => {
62
- if ( usagePage === undefined ) {
63
- setIsLoading ( true ) ;
64
- setTotalCreditsUsed ( 0 ) ;
65
- }
66
- const request : ListUsageRequest = {
67
- attributionId : AttributionId . render ( attributionId ) ,
68
- from : startDate . startOf ( "day" ) . valueOf ( ) ,
69
- to : endDate . endOf ( "day" ) . valueOf ( ) ,
70
- order : Ordering . ORDERING_DESCENDING ,
71
- pagination : {
72
- perPage : 50 ,
73
- page,
74
- } ,
75
- } ;
76
- try {
77
- const page = await getGitpodService ( ) . server . listUsage ( request ) ;
78
- setUsagePage ( page ) ;
79
- setTotalCreditsUsed ( page . creditsUsed ) ;
80
- } catch ( error ) {
81
- if ( error . code === ErrorCodes . PERMISSION_DENIED ) {
82
- setErrorMessage ( "Access to usage details is restricted to team owners." ) ;
83
- } else {
84
- setErrorMessage ( `Error: ${ error ?. message } ` ) ;
85
- }
86
- } finally {
87
- setIsLoading ( false ) ;
88
- }
89
- } ,
90
- [ attributionId , endDate , startDate , usagePage ] ,
91
- ) ;
92
74
useEffect ( ( ) => {
93
75
if ( startDate . isAfter ( endDate ) ) {
94
76
setErrorMessage ( "The start date needs to be before the end date." ) ;
@@ -99,8 +81,8 @@ function UsageView({ attributionId }: UsageViewProps) {
99
81
return ;
100
82
}
101
83
setErrorMessage ( "" ) ;
102
- loadPage ( 1 ) ;
103
- } , [ startDate , endDate , loadPage ] ) ;
84
+ setPage ( 1 ) ;
85
+ } , [ startDate , endDate , setPage ] ) ;
104
86
105
87
const getType = ( type : WorkspaceType ) => {
106
88
if ( type === "regular" ) {
@@ -172,7 +154,8 @@ function UsageView({ attributionId }: UsageViewProps) {
172
154
return new Date ( time ) . toLocaleDateString ( undefined , options ) . replace ( "at " , "" ) ;
173
155
} ;
174
156
175
- const currentPaginatedResults = usagePage ?. usageEntriesList . filter ( ( u ) => u . kind === "workspaceinstance" ) ?? [ ] ;
157
+ const currentPaginatedResults =
158
+ usagePage . data ?. usageEntriesList . filter ( ( u ) => u . kind === "workspaceinstance" ) ?? [ ] ;
176
159
const DateDisplay = forwardRef ( ( arg : any , ref : any ) => (
177
160
< div
178
161
className = "px-2 py-0.5 text-gray-500 bg-gray-50 dark:text-gray-400 dark:bg-gray-800 rounded-md cursor-pointer flex items-center hover:bg-gray-100 dark:hover:bg-gray-700"
@@ -253,21 +236,21 @@ function UsageView({ attributionId }: UsageViewProps) {
253
236
< div className = "text-base text-gray-500 truncate" > Previous Months</ div >
254
237
{ getBillingHistory ( ) }
255
238
</ div >
256
- { ! isLoading && (
239
+ { ! usagePage . isLoading && (
257
240
< div >
258
241
< div className = "flex flex-col truncate" >
259
242
< div className = "text-base text-gray-500" > Credits</ div >
260
243
< div className = "flex text-lg text-gray-600 font-semibold" >
261
244
< span className = "dark:text-gray-400" >
262
- { totalCreditsUsed . toLocaleString ( ) }
245
+ { usagePage . data ?. creditsUsed . toLocaleString ( ) }
263
246
</ span >
264
247
</ div >
265
248
</ div >
266
249
</ div >
267
250
) }
268
251
</ div >
269
252
</ div >
270
- { ! isLoading &&
253
+ { ! usagePage . isLoading &&
271
254
( usagePage === undefined || currentPaginatedResults . length === 0 ) &&
272
255
! errorMessage && (
273
256
< div className = "flex flex-col w-full mb-8" >
@@ -282,13 +265,13 @@ function UsageView({ attributionId }: UsageViewProps) {
282
265
</ Subheading >
283
266
</ div >
284
267
) }
285
- { isLoading && (
268
+ { usagePage . isLoading && (
286
269
< div className = "flex items-center justify-center w-full space-x-2 text-gray-400 text-sm pt-16 pb-40" >
287
270
< img alt = "Loading Spinner" className = "h-4 w-4 animate-spin" src = { Spinner } />
288
271
< span > Fetching usage...</ span >
289
272
</ div >
290
273
) }
291
- { ! isLoading && currentPaginatedResults . length > 0 && (
274
+ { ! usagePage . isLoading && currentPaginatedResults . length > 0 && (
292
275
< div className = "flex flex-col w-full mb-8" >
293
276
< ItemsList className = "mt-2 text-gray-400 dark:text-gray-500" >
294
277
< Item
@@ -402,13 +385,15 @@ function UsageView({ attributionId }: UsageViewProps) {
402
385
) ;
403
386
} ) }
404
387
</ ItemsList >
405
- { usagePage && usagePage . pagination && usagePage . pagination . totalPages > 1 && (
406
- < Pagination
407
- currentPage = { usagePage . pagination . page }
408
- setPage = { ( page ) => loadPage ( page ) }
409
- totalNumberOfPages = { usagePage . pagination . totalPages }
410
- />
411
- ) }
388
+ { usagePage . data &&
389
+ usagePage . data . pagination &&
390
+ usagePage . data . pagination . totalPages > 1 && (
391
+ < Pagination
392
+ currentPage = { usagePage . data . pagination . page }
393
+ setPage = { setPage }
394
+ totalNumberOfPages = { usagePage . data . pagination . totalPages }
395
+ />
396
+ ) }
412
397
</ div >
413
398
) }
414
399
</ div >
0 commit comments