Skip to content

Commit 7969cbd

Browse files
committed
[dashboard] flexible usage range
1 parent 8d8cdbf commit 7969cbd

File tree

4 files changed

+185
-17
lines changed

4 files changed

+185
-17
lines changed

components/dashboard/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,15 @@
99
"@gitpod/public-api": "0.1.5",
1010
"@stripe/react-stripe-js": "^1.7.2",
1111
"@stripe/stripe-js": "^1.29.0",
12+
"@types/react-datepicker": "^4.8.0",
1213
"configcat-js": "^6.0.0",
1314
"countries-list": "^2.6.1",
1415
"dayjs": "^1.11.5",
1516
"js-cookie": "^3.0.1",
1617
"monaco-editor": "^0.25.2",
1718
"query-string": "^7.1.1",
1819
"react": "^17.0.1",
20+
"react-datepicker": "^4.8.0",
1921
"react-dom": "^17.0.1",
2022
"react-intl-tel-input": "^8.2.0",
2123
"react-router-dom": "^5.2.0",

components/dashboard/src/components/UsageView.tsx

Lines changed: 83 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* See License-AGPL.txt in the project root for license information.
55
*/
66

7-
import { useEffect, useState } from "react";
7+
import { forwardRef, useEffect, useState } from "react";
88
import { getGitpodService, gitpodHostUrl } from "../service/service";
99
import {
1010
ListUsageRequest,
@@ -25,6 +25,10 @@ import { toRemoteURL } from "../projects/render-utils";
2525
import { WorkspaceType } from "@gitpod/gitpod-protocol";
2626
import PillLabel from "./PillLabel";
2727
import { SupportedWorkspaceClass } from "@gitpod/gitpod-protocol/lib/workspace-class";
28+
import DatePicker from "react-datepicker";
29+
import "react-datepicker/dist/react-datepicker.css";
30+
import "./react-datepicker.css";
31+
import { useLocation } from "react-router";
2832

2933
interface UsageViewProps {
3034
attributionId: AttributionId;
@@ -36,22 +40,37 @@ function UsageView({ attributionId }: UsageViewProps) {
3640
const today = new Date();
3741
const startOfCurrentMonth = new Date(today.getFullYear(), today.getMonth(), 1);
3842
const timestampStartOfCurrentMonth = startOfCurrentMonth.getTime();
39-
const [startDateOfBillMonth, setStartDateOfBillMonth] = useState(timestampStartOfCurrentMonth);
40-
const [endDateOfBillMonth, setEndDateOfBillMonth] = useState(Date.now());
43+
const [startDate, setStartDate] = useState(timestampStartOfCurrentMonth);
44+
const [startDateError, setStartDateError] = useState("");
45+
const [endDate, setEndDate] = useState(Date.now());
4146
const [totalCreditsUsed, setTotalCreditsUsed] = useState<number>(0);
4247
const [isLoading, setIsLoading] = useState<boolean>(true);
4348
const [supportedClasses, setSupportedClasses] = useState<SupportedWorkspaceClass[]>([]);
4449

50+
const location = useLocation();
4551
useEffect(() => {
52+
const match = /#(\d{4}-\d{2}-\d{2}):(\d{4}-\d{2}-\d{2})/.exec(location.hash);
53+
if (match) {
54+
try {
55+
setStartDate(Date.parse(match[1]));
56+
setEndDate(Date.parse(match[2]));
57+
} catch (e) {
58+
console.error(e);
59+
}
60+
}
4661
(async () => {
4762
const classes = await getGitpodService().server.getSupportedWorkspaceClasses();
4863
setSupportedClasses(classes);
4964
})();
50-
}, []);
51-
65+
}, [location]);
66+
// edit
5267
useEffect(() => {
68+
if (startDate >= endDate) {
69+
setStartDateError("The start date needs to be smaller than the end date.");
70+
return;
71+
}
5372
loadPage(1);
54-
}, [startDateOfBillMonth, endDateOfBillMonth]);
73+
}, [startDate, endDate]);
5574

5675
const loadPage = async (page: number = 1) => {
5776
if (usagePage === undefined) {
@@ -60,8 +79,8 @@ function UsageView({ attributionId }: UsageViewProps) {
6079
}
6180
const request: ListUsageRequest = {
6281
attributionId: AttributionId.render(attributionId),
63-
from: startDateOfBillMonth,
64-
to: endDateOfBillMonth,
82+
from: startDate,
83+
to: endDate,
6584
order: Ordering.ORDERING_DESCENDING,
6685
pagination: {
6786
perPage: 50,
@@ -119,8 +138,8 @@ function UsageView({ attributionId }: UsageViewProps) {
119138
};
120139

121140
const handleMonthClick = (start: any, end: any) => {
122-
setStartDateOfBillMonth(start);
123-
setEndDateOfBillMonth(end);
141+
setStartDate(start);
142+
setEndDate(end);
124143
};
125144

126145
const getBillingHistory = () => {
@@ -160,13 +179,61 @@ function UsageView({ attributionId }: UsageViewProps) {
160179

161180
const headerTitle = attributionId.kind === "team" ? "Team Usage" : "Personal Usage";
162181

182+
const FromLink = forwardRef((arg: any, ref: any) => (
183+
<div
184+
className="text-blue-500 hover:text-blue-600 dark:text-blue-400 dark:hover:text-blue-500 truncate cursor-pointer"
185+
onClick={arg.onClick}
186+
ref={ref}
187+
>
188+
{arg.value}
189+
</div>
190+
));
191+
const ToLink = forwardRef((arg: any, ref: any) => (
192+
<div
193+
className="text-blue-500 hover:text-blue-600 dark:text-blue-400 dark:hover:text-blue-500 truncate cursor-pointer"
194+
onClick={arg.onClick}
195+
ref={ref}
196+
>
197+
{arg.value}
198+
</div>
199+
));
200+
163201
return (
164202
<>
165203
<Header
166-
title={headerTitle}
167-
subtitle={`${new Date(startDateOfBillMonth).toLocaleDateString()} - ${new Date(
168-
endDateOfBillMonth,
169-
).toLocaleDateString()} (updated every 15 minutes).`}
204+
title={
205+
<div className="flex items-baseline">
206+
<h1 className="tracking-wide">{headerTitle}</h1>
207+
<h2 className="ml-3">(updated every 15 minutes).</h2>
208+
</div>
209+
}
210+
subtitle={
211+
<h2 className="tracking-wide flex mt-3">
212+
<h2 className="mr-2">Period: </h2>
213+
{startDateError}
214+
<DatePicker
215+
selected={new Date(startDate)}
216+
onChange={(date) => date && setStartDate(date.getTime())}
217+
selectsStart
218+
startDate={new Date(startDate)}
219+
endDate={new Date(endDate)}
220+
maxDate={new Date(endDate)}
221+
customInput={<FromLink />}
222+
dateFormat={"dd/MM/yyyy"}
223+
/>
224+
<div className="mx-2">-</div>
225+
<DatePicker
226+
selected={new Date(endDate)}
227+
onChange={(date) => date && setEndDate(date.getTime())}
228+
selectsEnd
229+
startDate={new Date(startDate)}
230+
endDate={new Date(endDate)}
231+
minDate={new Date(startDate)}
232+
customInput={<ToLink />}
233+
dateFormat={"dd/MM/yyyy"}
234+
/>
235+
</h2>
236+
}
170237
/>
171238
<div className="app-container pt-5">
172239
{errorMessage && <p className="text-base">{errorMessage}</p>}
@@ -236,10 +303,10 @@ function UsageView({ attributionId }: UsageViewProps) {
236303
workspaces
237304
</a>{" "}
238305
in{" "}
239-
{new Date(startDateOfBillMonth).toLocaleString("default", {
306+
{new Date(startDate).toLocaleString("default", {
240307
month: "long",
241308
})}{" "}
242-
{new Date(startDateOfBillMonth).getFullYear()} or checked your other teams?
309+
{new Date(startDate).getFullYear()} or checked your other teams?
243310
</p>
244311
</div>
245312
)}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/**
2+
* Copyright (c) 2022 Gitpod GmbH. All rights reserved.
3+
* Licensed under the GNU Affero General Public License (AGPL).
4+
* See License-AGPL.txt in the project root for license information.
5+
*/
6+
7+
.react-datepicker-wrapper {
8+
width: fit-content !important;
9+
}
10+
11+
.react-datepicker {
12+
border: 0px !important;
13+
}
14+
15+
.react-datepicker div {
16+
@apply dark:bg-gray-900 dark:text-gray-400 dark:border-gray-700 text-gray-600
17+
}
18+
19+
.react-datepicker div.react-datepicker__day--in-selecting-range {
20+
@apply dark:bg-gray-600 dark:text-gray-400 text-gray-200
21+
}
22+
23+
.react-datepicker div.react-datepicker__day--selected {
24+
@apply dark:bg-gray-300 dark:text-gray-800 text-gray-200
25+
}
26+
27+
.react-datepicker div.react-datepicker__day--selecting-range-start {
28+
@apply dark:bg-gray-300 dark:text-gray-800
29+
}
30+
31+
.react-datepicker div.react-datepicker__day--selecting-range-end {
32+
@apply dark:bg-gray-300 dark:text-gray-800
33+
}
34+
35+
.react-datepicker button {
36+
@apply dark:bg-gray-800 dark:text-gray-400
37+
}

yarn.lock

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2099,6 +2099,11 @@
20992099
schema-utils "^2.6.5"
21002100
source-map "^0.7.3"
21012101

2102+
"@popperjs/core@^2.9.2":
2103+
version "2.11.6"
2104+
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.6.tgz#cee20bd55e68a1720bdab363ecf0c821ded4cd45"
2105+
integrity sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==
2106+
21022107
"@probot/get-private-key@^1.1.0", "@probot/get-private-key@^1.1.1":
21032108
version "1.1.1"
21042109
resolved "https://registry.npmjs.org/@probot/get-private-key/-/get-private-key-1.1.1.tgz"
@@ -3166,6 +3171,16 @@
31663171
resolved "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz"
31673172
integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==
31683173

3174+
"@types/react-datepicker@^4.8.0":
3175+
version "4.8.0"
3176+
resolved "https://registry.yarnpkg.com/@types/react-datepicker/-/react-datepicker-4.8.0.tgz#0221bd38725b7db64cd08a89f49a93d816c2f691"
3177+
integrity sha512-20uzZsIf4moPAjjHDfPvH8UaOHZBxrkiQZoLS3wgKq8Xhp+95gdercLEdoA7/I8nR9R5Jz2qQkdMIM+Lq4AS1A==
3178+
dependencies:
3179+
"@popperjs/core" "^2.9.2"
3180+
"@types/react" "*"
3181+
date-fns "^2.0.1"
3182+
react-popper "^2.2.5"
3183+
31693184
"@types/react-dom@^17.0.3":
31703185
version "17.0.10"
31713186
resolved "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.10.tgz"
@@ -5514,6 +5529,11 @@ classnames@^2.2.5, classnames@^2.3.1:
55145529
resolved "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz"
55155530
integrity sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==
55165531

5532+
classnames@^2.2.6:
5533+
version "2.3.2"
5534+
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.2.tgz#351d813bf0137fcc6a76a16b88208d2560a0d924"
5535+
integrity sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==
5536+
55175537
clean-css@^4.2.3:
55185538
version "4.2.4"
55195539
resolved "https://registry.npmjs.org/clean-css/-/clean-css-4.2.4.tgz"
@@ -6653,6 +6673,11 @@ date-fns@^2.0.1, date-fns@^2.16.1:
66536673
resolved "https://registry.npmjs.org/date-fns/-/date-fns-2.25.0.tgz"
66546674
integrity sha512-ovYRFnTrbGPD4nqaEqescPEv1mNwvt+UTqI3Ay9SzNtey9NZnYu6E2qCcBBgJ6/2VF1zGGygpyTDITqpQQ5e+w==
66556675

6676+
date-fns@^2.24.0:
6677+
version "2.29.3"
6678+
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.29.3.tgz#27402d2fc67eb442b511b70bbdf98e6411cd68a8"
6679+
integrity sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA==
6680+
66566681
dateformat@^4.5.1:
66576682
version "4.6.3"
66586683
resolved "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz"
@@ -11585,7 +11610,7 @@ longjohn@^0.2.12:
1158511610
dependencies:
1158611611
source-map-support "0.3.2 - 1.0.0"
1158711612

11588-
loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1, loose-envify@^1.4.0:
11613+
loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1, loose-envify@^1.4.0:
1158911614
version "1.4.0"
1159011615
resolved "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz"
1159111616
integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
@@ -14927,6 +14952,18 @@ react-app-polyfill@^2.0.0:
1492714952
regenerator-runtime "^0.13.7"
1492814953
whatwg-fetch "^3.4.1"
1492914954

14955+
react-datepicker@^4.8.0:
14956+
version "4.8.0"
14957+
resolved "https://registry.yarnpkg.com/react-datepicker/-/react-datepicker-4.8.0.tgz#11b8918d085a1ce4781eee4c8e4641b3cd592010"
14958+
integrity sha512-u69zXGHMpxAa4LeYR83vucQoUCJQ6m/WBsSxmUMu/M8ahTSVMMyiyQzauHgZA2NUr9y0FUgOAix71hGYUb6tvg==
14959+
dependencies:
14960+
"@popperjs/core" "^2.9.2"
14961+
classnames "^2.2.6"
14962+
date-fns "^2.24.0"
14963+
prop-types "^15.7.2"
14964+
react-onclickoutside "^6.12.0"
14965+
react-popper "^2.2.5"
14966+
1493014967
react-dev-utils@^11.0.3:
1493114968
version "11.0.4"
1493214969
resolved "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-11.0.4.tgz"
@@ -14971,6 +15008,11 @@ react-error-overlay@^6.0.9:
1497115008
resolved "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.9.tgz"
1497215009
integrity sha512-nQTTcUu+ATDbrSD1BZHr5kgSD4oF8OFjxun8uAaL8RwPBacGBNPf/yAuVVdx17N8XNzRDMrZ9XcKZHCjPW+9ew==
1497315010

15011+
react-fast-compare@^3.0.1:
15012+
version "3.2.0"
15013+
resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.0.tgz#641a9da81b6a6320f270e89724fb45a0b39e43bb"
15014+
integrity sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA==
15015+
1497415016
react-intl-tel-input@^8.2.0:
1497515017
version "8.2.0"
1497615018
resolved "https://registry.yarnpkg.com/react-intl-tel-input/-/react-intl-tel-input-8.2.0.tgz#9e830fbe3bcca5aa5e8cdd84bac80da13e3ab389"
@@ -14992,6 +15034,19 @@ react-is@^17.0.1:
1499215034
resolved "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz"
1499315035
integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==
1499415036

15037+
react-onclickoutside@^6.12.0:
15038+
version "6.12.2"
15039+
resolved "https://registry.yarnpkg.com/react-onclickoutside/-/react-onclickoutside-6.12.2.tgz#8e6cf80c7d17a79f2c908399918158a7b02dda01"
15040+
integrity sha512-NMXGa223OnsrGVp5dJHkuKxQ4czdLmXSp5jSV9OqiCky9LOpPATn3vLldc+q5fK3gKbEHvr7J1u0yhBh/xYkpA==
15041+
15042+
react-popper@^2.2.5:
15043+
version "2.3.0"
15044+
resolved "https://registry.yarnpkg.com/react-popper/-/react-popper-2.3.0.tgz#17891c620e1320dce318bad9fede46a5f71c70ba"
15045+
integrity sha512-e1hj8lL3uM+sgSR4Lxzn5h1GxBlpa4CQz0XLF8kx4MDrDRWY0Ena4c97PUeSX9i5W3UAfDP0z0FXCTQkoXUl3Q==
15046+
dependencies:
15047+
react-fast-compare "^3.0.1"
15048+
warning "^4.0.2"
15049+
1499515050
react-refresh@^0.8.3:
1499615051
version "0.8.3"
1499715052
resolved "https://registry.npmjs.org/react-refresh/-/react-refresh-0.8.3.tgz"
@@ -18059,6 +18114,13 @@ walker@^1.0.7, walker@~1.0.5:
1805918114
dependencies:
1806018115
makeerror "1.0.12"
1806118116

18117+
warning@^4.0.2:
18118+
version "4.0.3"
18119+
resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3"
18120+
integrity sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==
18121+
dependencies:
18122+
loose-envify "^1.0.0"
18123+
1806218124
watch@^1.0.2:
1806318125
version "1.0.2"
1806418126
resolved "https://registry.npmjs.org/watch/-/watch-1.0.2.tgz"

0 commit comments

Comments
 (0)