Skip to content

Commit 0b1d4ca

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

File tree

10 files changed

+388
-34
lines changed

10 files changed

+388
-34
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: 96 additions & 11 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,13 +47,23 @@ 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 (
66+
<<<<<<< HEAD
5767
<PageWithSubMenu
5868
subMenu={getTeamSettingsMenu({ team, showPaymentUI, showUsageBasedUI })}
5969
title="Usage"
@@ -113,11 +123,86 @@ function TeamUsage() {
113123
</div>
114124
<div className="pr-2">
115125
<Arrow up={false} />
126+
=======
127+
<>
128+
<Header title="Usage" subtitle="Manage team usage." />
129+
<div className="app-container">
130+
<div className="flex space-x-16">
131+
<div className="flex">
132+
<div className="space-y-8 mt-6 mb-6">
133+
<div className="flex flex-col truncate">
134+
<div className="text-base text-gray-500 truncate">Period</div>
135+
<div className="mr-3 text-lg text-gray-600 font-semibold truncate">June 2022</div>
136+
</div>
137+
<div className="flex flex-col truncate">
138+
<div className="text-base text-gray-500 truncate">Monthly usage</div>
139+
<div className="mr-3 text-lg text-gray-600 font-semibold truncate">
140+
{calculateTotalUsage()} Total Credits
141+
</div>
142+
</div>
143+
>>>>>>> f119f0fdc (pagination)
116144
</div>
117145
</div>
118-
))}
119-
</ItemsList>
120-
</PageWithSubMenu>
146+
<div className="flex flex-col w-full mb-8">
147+
<h3>All Usage</h3>
148+
<span className="text-gray-500 mb-5">View usage details of all team members.</span>
149+
<ItemsList className="mt-2 text-gray-500">
150+
<Item header={false} className="grid grid-cols-5 bg-gray-100 mb-5">
151+
<ItemField className="my-auto">
152+
<span>Type</span>
153+
</ItemField>
154+
<ItemField className="my-auto">
155+
<span>Class</span>
156+
</ItemField>
157+
<ItemField className="my-auto">
158+
<span>Usage</span>
159+
</ItemField>
160+
<ItemField className="my-auto">
161+
<span>Credits</span>
162+
</ItemField>
163+
<ItemField className="my-auto" />
164+
</Item>
165+
{currentPaginatedResults.map((usage) => (
166+
<div
167+
key={usage.instanceId}
168+
className="flex p-3 grid grid-cols-5 justify-between transition ease-in-out rounded-xl focus:bg-gitpod-kumquat-light"
169+
>
170+
<div className="my-auto">
171+
<span>{getType(usage.workspaceType)}</span>
172+
</div>
173+
<div className="my-auto">
174+
<span className="text-gray-400">{usage.workspaceClass}</span>
175+
</div>
176+
<div className="my-auto">
177+
<span className="text-gray-700">
178+
{getMinutes(
179+
new Date(usage.endTime).getTime(),
180+
new Date(usage.startTime).getTime(),
181+
)}
182+
</span>
183+
</div>
184+
<div className="my-auto">
185+
<span className="text-gray-700">{usage.credits.toFixed(1)}</span>
186+
</div>
187+
<div className="my-auto">
188+
<span className="text-gray-400">
189+
{moment(new Date(usage.startTime).toDateString()).fromNow()}
190+
</span>
191+
</div>
192+
</div>
193+
))}
194+
</ItemsList>
195+
{billedUsage.length > resultsPerPage && (
196+
<Pagination
197+
currentPage={currentPage}
198+
setCurrentPage={setCurrentPage}
199+
numberOfPages={numberOfPages}
200+
/>
201+
)}
202+
</div>
203+
</div>
204+
</div>
205+
</>
121206
);
122207
}
123208

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)