Skip to content

Commit b75714b

Browse files
author
Laurie T. Malau
committed
pagination
1 parent 4df9c3e commit b75714b

File tree

10 files changed

+174
-93
lines changed

10 files changed

+174
-93
lines changed

components/dashboard/src/Menu.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,11 +217,17 @@ export default function Menu() {
217217
link: `/t/${team.slug}/members`,
218218
},
219219
];
220+
if (showUsageBasedUI) {
221+
teamSettingsList.push({
222+
title: "Usage",
223+
link: `/t/${team.slug}/usage`,
224+
});
225+
}
220226
if (currentUserInTeam?.role === "owner") {
221227
teamSettingsList.push({
222228
title: "Settings",
223229
link: `/t/${team.slug}/settings`,
224-
alternatives: getTeamSettingsMenu({ team, showPaymentUI, showUsageBasedUI }).flatMap((e) => e.link),
230+
alternatives: getTeamSettingsMenu({ team, showPaymentUI }).flatMap((e) => e.link),
225231
});
226232
}
227233

components/dashboard/src/components/Arrow.tsx

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

7-
function Arrow(props: { up: boolean; customBorderClasses?: string }) {
7+
function Arrow(props: { direction: string; customBorderClasses?: string }) {
8+
const { direction, customBorderClasses } = props;
9+
10+
// Using any because of known TS bug with bracket notation:
11+
// https://github.com/microsoft/TypeScript/issues/10530
12+
const directionMap: any = {
13+
up: "-135deg",
14+
down: "45deg",
15+
left: "135deg",
16+
right: "315deg",
17+
};
818
return (
919
<span
1020
className={
1121
"mx-2 " +
12-
(props.customBorderClasses ||
22+
(customBorderClasses ||
1323
"border-gray-400 dark:border-gray-500 group-hover:border-gray-600 dark:group-hover:border-gray-400")
1424
}
1525
style={{
@@ -18,7 +28,7 @@ function Arrow(props: { up: boolean; customBorderClasses?: string }) {
1828
padding: 3,
1929
borderWidth: "0 2px 2px 0",
2030
display: "inline-block",
21-
transform: `rotate(${props.up ? "-135deg" : "45deg"})`,
31+
transform: `rotate(${directionMap[direction]})`,
2232
}}
2333
></span>
2434
);

components/dashboard/src/components/DropDown.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ function DropDown(props: DropDownProps) {
4949
>
5050
{props.prefix}
5151
{current}
52-
<Arrow up={false} customBorderClasses={props.renderAsLink ? asLinkArrowBorder : undefined} />
52+
<Arrow direction={"down"} customBorderClasses={props.renderAsLink ? asLinkArrowBorder : undefined} />
5353
</span>
5454
</ContextMenu>
5555
);
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
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+
import Arrow from "./Arrow";
8+
9+
function Pagination(props: { numberOfPages: number; currentPage: number; setCurrentPage: any }) {
10+
const { numberOfPages, currentPage, setCurrentPage } = props;
11+
const availablePageNumbers = [...Array(numberOfPages + 1).keys()].slice(1);
12+
13+
const nextPage = () => {
14+
if (currentPage !== numberOfPages) setCurrentPage(currentPage + 1);
15+
};
16+
const prevPage = () => {
17+
if (currentPage !== 1) setCurrentPage(currentPage - 1);
18+
};
19+
20+
return (
21+
<nav className="mt-16">
22+
<ul className="flex justify-center space-x-4">
23+
<li className="text-gray-400 cursor-pointer">
24+
<span onClick={prevPage}>
25+
<Arrow direction={"left"} />
26+
Previous
27+
</span>
28+
</li>
29+
{availablePageNumbers.map((pn) => (
30+
<li
31+
key={pn}
32+
className={`text-gray-500 cursor-pointer w-8 text-center rounded-md ${
33+
currentPage === pn ? "bg-gray-200" : ""
34+
} `}
35+
>
36+
<span onClick={() => setCurrentPage(pn)}>{pn}</span>
37+
</li>
38+
))}
39+
<li className="text-gray-400 cursor-pointer">
40+
<span onClick={nextPage}>
41+
Next
42+
<Arrow direction={"right"} />
43+
</span>
44+
</li>
45+
</ul>
46+
</nav>
47+
);
48+
}
49+
50+
export default Pagination;

components/dashboard/src/start/StartWorkspace.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -519,7 +519,7 @@ export default class StartWorkspace extends React.Component<StartWorkspaceProps,
519519
>
520520
<button className="secondary">
521521
More Actions...
522-
<Arrow up={false} />
522+
<Arrow direction={"down"} />
523523
</button>
524524
</ContextMenu>
525525
<button

components/dashboard/src/teams/TeamBilling.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export default function TeamBilling() {
3131
const team = getCurrentTeam(location, teams);
3232
const [members, setMembers] = useState<TeamMemberInfo[]>([]);
3333
const [teamSubscription, setTeamSubscription] = useState<TeamSubscription2 | undefined>();
34-
const { showPaymentUI, showUsageBasedUI, currency, setCurrency } = useContext(PaymentContext);
34+
const { showPaymentUI, currency, setCurrency } = useContext(PaymentContext);
3535
const [pendingTeamPlan, setPendingTeamPlan] = useState<PendingPlan | undefined>();
3636
const [pollTeamSubscriptionTimeout, setPollTeamSubscriptionTimeout] = useState<NodeJS.Timeout | undefined>();
3737

@@ -140,7 +140,7 @@ export default function TeamBilling() {
140140

141141
return (
142142
<PageWithSubMenu
143-
subMenu={getTeamSettingsMenu({ team, showPaymentUI, showUsageBasedUI })}
143+
subMenu={getTeamSettingsMenu({ team, showPaymentUI })}
144144
title="Billing"
145145
subtitle="Manage team billing and plans."
146146
>

components/dashboard/src/teams/TeamSettings.tsx

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ import { getGitpodService, gitpodHostUrl } from "../service/service";
1515
import { UserContext } from "../user-context";
1616
import { getCurrentTeam, TeamsContext } from "./teams-context";
1717

18-
export function getTeamSettingsMenu(params: { team?: Team; showPaymentUI?: boolean; showUsageBasedUI?: boolean }) {
19-
const { team, showPaymentUI, showUsageBasedUI } = params;
18+
export function getTeamSettingsMenu(params: { team?: Team; showPaymentUI?: boolean }) {
19+
const { team, showPaymentUI } = params;
2020
return [
2121
{
2222
title: "General",
@@ -30,14 +30,6 @@ export function getTeamSettingsMenu(params: { team?: Team; showPaymentUI?: boole
3030
},
3131
]
3232
: []),
33-
...(showUsageBasedUI
34-
? [
35-
{
36-
title: "Usage",
37-
link: [`/t/${team?.slug}/usage`],
38-
},
39-
]
40-
: []),
4133
];
4234
}
4335

@@ -49,7 +41,7 @@ export default function TeamSettings() {
4941
const { user } = useContext(UserContext);
5042
const location = useLocation();
5143
const team = getCurrentTeam(location, teams);
52-
const { showPaymentUI, showUsageBasedUI } = useContext(PaymentContext);
44+
const { showPaymentUI } = useContext(PaymentContext);
5345

5446
const close = () => setModal(false);
5547

@@ -76,7 +68,7 @@ export default function TeamSettings() {
7668
return (
7769
<>
7870
<PageWithSubMenu
79-
subMenu={getTeamSettingsMenu({ team, showPaymentUI, showUsageBasedUI })}
71+
subMenu={getTeamSettingsMenu({ team, showPaymentUI })}
8072
title="Settings"
8173
subtitle="Manage general team settings."
8274
>

components/dashboard/src/teams/TeamUsage.tsx

Lines changed: 93 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -6,24 +6,24 @@
66

77
import { useContext, useEffect, useState } from "react";
88
import { Redirect, useLocation } from "react-router";
9-
import { PageWithSubMenu } from "../components/PageWithSubMenu";
109
import { getCurrentTeam, TeamsContext } from "./teams-context";
11-
import { getTeamSettingsMenu } from "./TeamSettings";
1210
import { PaymentContext } from "../payment-context";
1311
import { getGitpodService } from "../service/service";
1412
import { BillableSession, BillableWorkspaceType } from "@gitpod/gitpod-protocol/lib/usage";
1513
import { AttributionId } from "@gitpod/gitpod-protocol/lib/attribution";
1614
import { Item, ItemField, ItemsList } from "../components/ItemsList";
1715
import moment from "moment";
18-
import Property from "../admin/Property";
19-
import Arrow from "../components/Arrow";
16+
import Pagination from "../components/Pagination";
17+
import Header from "../components/Header";
2018

2119
function TeamUsage() {
2220
const { teams } = useContext(TeamsContext);
23-
const { showPaymentUI, showUsageBasedUI } = useContext(PaymentContext);
21+
const { showUsageBasedUI } = useContext(PaymentContext);
2422
const location = useLocation();
2523
const team = getCurrentTeam(location, teams);
2624
const [billedUsage, setBilledUsage] = useState<BillableSession[]>([]);
25+
const [currentPage, setCurrentPage] = useState(1);
26+
const [resultsPerPage] = useState(10);
2727

2828
useEffect(() => {
2929
if (!team) {
@@ -47,77 +47,100 @@ function TeamUsage() {
4747
return "Prebuild";
4848
};
4949

50-
const getHours = (endTime: number | undefined, startTime: number) => {
51-
if (!endTime) return "";
50+
const getMinutes = (endTime: number, startTime: number) => {
51+
return Math.floor(endTime - startTime) / (1000 * 60) + " min";
52+
};
5253

53-
return ((endTime - startTime) / (1000 * 60 * 60)).toFixed(1) + "hrs";
54+
const calculateTotalUsage = () => {
55+
let totalCredits = 0;
56+
billedUsage.forEach((session) => (totalCredits += session.credits));
57+
return totalCredits;
5458
};
5559

60+
const lastResultOnCurrentPage = currentPage * resultsPerPage;
61+
const firstResultOnCurrentPage = lastResultOnCurrentPage - resultsPerPage;
62+
const numberOfPages = Math.ceil(billedUsage.length / resultsPerPage);
63+
const currentPaginatedResults = billedUsage.slice(firstResultOnCurrentPage, lastResultOnCurrentPage);
64+
5665
return (
57-
<PageWithSubMenu
58-
subMenu={getTeamSettingsMenu({ team, showPaymentUI, showUsageBasedUI })}
59-
title="Usage"
60-
subtitle="Manage team usage."
61-
>
62-
<div className="flex flex-col w-full">
63-
<div className="flex w-full mt-6 mb-6">
64-
<Property name="Last 30 days">Jun 1 - June 30</Property>
65-
<Property name="Workspaces">4,200 Min</Property>
66-
<Property name="Prebuilds">12,334 Min</Property>
67-
</div>
68-
</div>
69-
<ItemsList className="mt-2 text-gray-500">
70-
<Item header={false} className="grid grid-cols-6 bg-gray-100">
71-
<ItemField className="my-auto">
72-
<span>Type</span>
73-
</ItemField>
74-
<ItemField className="my-auto">
75-
<span>Class</span>
76-
</ItemField>
77-
<ItemField className="my-auto">
78-
<span>Amount</span>
79-
</ItemField>
80-
<ItemField className="my-auto">
81-
<span>Credits</span>
82-
</ItemField>
83-
<ItemField className="my-auto" />
84-
</Item>
85-
{billedUsage.map((usage) => (
86-
<div
87-
key={usage.instanceId}
88-
className="flex p-3 grid grid-cols-6 justify-between transition ease-in-out rounded-xl focus:bg-gitpod-kumquat-light"
89-
>
90-
<div className="my-auto">
91-
<span className={usage.workspaceType === "prebuild" ? "text-orange-400" : "text-green-500"}>
92-
{getType(usage.workspaceType)}
93-
</span>
94-
</div>
95-
<div className="my-auto">
96-
<span className="text-gray-400">{usage.workspaceClass}</span>
97-
</div>
98-
<div className="my-auto">
99-
<span className="text-gray-700">
100-
{getHours(
101-
usage.endTime ? new Date(usage.endTime).getTime() : undefined,
102-
new Date(usage.startTime).getTime(),
103-
)}
104-
</span>
105-
</div>
106-
<div className="my-auto">
107-
<span className="text-gray-700">{usage.credits.toFixed(1)}</span>
108-
</div>
109-
<div className="my-auto">
110-
<span className="text-gray-400">
111-
{moment(new Date(usage.startTime).toDateString()).fromNow()}
112-
</span>
113-
</div>
114-
<div className="pr-2">
115-
<Arrow up={false} />
66+
<>
67+
<Header title="Usage" subtitle="Manage team usage." />
68+
<div className="app-container">
69+
<div className="flex space-x-16">
70+
<div className="flex">
71+
<div className="space-y-8 mt-6 mb-6">
72+
<div className="flex flex-col truncate">
73+
<div className="text-base text-gray-500 truncate">Period</div>
74+
<div className="mr-3 text-lg text-gray-600 font-semibold truncate">June 2022</div>
75+
</div>
76+
<div className="flex flex-col truncate">
77+
<div className="text-base text-gray-500 truncate">Monthly usage</div>
78+
<div className="mr-3 text-lg text-gray-600 font-semibold truncate">
79+
{calculateTotalUsage()} Total Credits
80+
</div>
81+
</div>
11682
</div>
11783
</div>
118-
))}
119-
</ItemsList>
120-
</PageWithSubMenu>
84+
<div className="flex flex-col w-full mb-8">
85+
<h3>All Usage</h3>
86+
<span className="text-gray-500 mb-5">View usage details of all team members.</span>
87+
<ItemsList className="mt-2 text-gray-500">
88+
<Item header={false} className="grid grid-cols-5 bg-gray-100 mb-5">
89+
<ItemField className="my-auto">
90+
<span>Type</span>
91+
</ItemField>
92+
<ItemField className="my-auto">
93+
<span>Class</span>
94+
</ItemField>
95+
<ItemField className="my-auto">
96+
<span>Usage</span>
97+
</ItemField>
98+
<ItemField className="my-auto">
99+
<span>Credits</span>
100+
</ItemField>
101+
<ItemField className="my-auto" />
102+
</Item>
103+
{currentPaginatedResults.map((usage) => (
104+
<div
105+
key={usage.instanceId}
106+
className="flex p-3 grid grid-cols-5 justify-between transition ease-in-out rounded-xl focus:bg-gitpod-kumquat-light"
107+
>
108+
<div className="my-auto">
109+
<span>{getType(usage.workspaceType)}</span>
110+
</div>
111+
<div className="my-auto">
112+
<span className="text-gray-400">{usage.workspaceClass}</span>
113+
</div>
114+
<div className="my-auto">
115+
<span className="text-gray-700">
116+
{getMinutes(
117+
new Date(usage.endTime).getTime(),
118+
new Date(usage.startTime).getTime(),
119+
)}
120+
</span>
121+
</div>
122+
<div className="my-auto">
123+
<span className="text-gray-700">{usage.credits.toFixed(1)}</span>
124+
</div>
125+
<div className="my-auto">
126+
<span className="text-gray-400">
127+
{moment(new Date(usage.startTime).toDateString()).fromNow()}
128+
</span>
129+
</div>
130+
</div>
131+
))}
132+
</ItemsList>
133+
{billedUsage.length > resultsPerPage && (
134+
<Pagination
135+
currentPage={currentPage}
136+
setCurrentPage={setCurrentPage}
137+
numberOfPages={numberOfPages}
138+
/>
139+
)}
140+
</div>
141+
</div>
142+
</div>
143+
</>
121144
);
122145
}
123146

components/dashboard/src/workspaces/Workspaces.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ export default function () {
158158
className="flex cursor-pointer py-6 px-6 flex-row text-gray-400 bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 rounded-xl mb-2"
159159
>
160160
<div className="pr-2">
161-
<Arrow up={!!showInactive} />
161+
<Arrow direction={showInactive ? "up" : "down"} />
162162
</div>
163163
<div className="flex flex-grow flex-col ">
164164
<div className="font-medium text-gray-500 dark:text-gray-200 truncate">
@@ -168,7 +168,7 @@ export default function () {
168168
</span>
169169
</div>
170170
<div className="text-sm flex-auto">
171-
Workspaces that have been stopped for more than 24 hours. Inactive
171+
Workspaces that have been stopped for more than 24 hours. Inactive
172172
workspaces are automatically deleted after 14 days.{" "}
173173
<a
174174
className="gp-link"

0 commit comments

Comments
 (0)