Skip to content

Commit ccc07b2

Browse files
author
Laurie T. Malau
committed
always show for now
1 parent 1a217da commit ccc07b2

File tree

8 files changed

+249
-19
lines changed

8 files changed

+249
-19
lines changed

components/dashboard/src/App.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ const JoinTeam = React.lazy(() => import(/* webpackPrefetch: true */ "./teams/Jo
6868
const Members = React.lazy(() => import(/* webpackPrefetch: true */ "./teams/Members"));
6969
const TeamSettings = React.lazy(() => import(/* webpackPrefetch: true */ "./teams/TeamSettings"));
7070
const TeamBilling = React.lazy(() => import(/* webpackPrefetch: true */ "./teams/TeamBilling"));
71+
const TeamUsage = React.lazy(() => import(/* webpackPrefetch: true */ "./teams/TeamUsage"));
7172
const NewProject = React.lazy(() => import(/* webpackPrefetch: true */ "./projects/NewProject"));
7273
const ConfigureProject = React.lazy(() => import(/* webpackPrefetch: true */ "./projects/ConfigureProject"));
7374
const Projects = React.lazy(() => import(/* webpackPrefetch: true */ "./projects/Projects"));
@@ -440,6 +441,9 @@ function App() {
440441
if (maybeProject === "billing") {
441442
return <TeamBilling />;
442443
}
444+
if (maybeProject === "usage") {
445+
return <TeamUsage />;
446+
}
443447
if (resourceOrPrebuild === "prebuilds") {
444448
return <Prebuilds />;
445449
}

components/dashboard/src/teams/TeamSettings.tsx

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,17 @@ 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",
2323
link: [`/t/${team?.slug}/settings`],
2424
},
25+
{
26+
title: "Usage",
27+
link: [`/t/${team?.slug}/usage`],
28+
},
2529
...(showPaymentUI
2630
? [
2731
{
@@ -30,14 +34,6 @@ export function getTeamSettingsMenu(params: { team?: Team; showPaymentUI?: boole
3034
},
3135
]
3236
: []),
33-
...(showUsageBasedUI
34-
? [
35-
{
36-
title: "Usage",
37-
link: [`/t/${team?.slug}/usage`],
38-
},
39-
]
40-
: []),
4137
];
4238
}
4339

components/dashboard/src/teams/TeamUsage.tsx

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

7-
import { useContext } from "react";
7+
import { useContext, useEffect, useState } from "react";
88
import { useLocation } from "react-router";
99
import { PageWithSubMenu } from "../components/PageWithSubMenu";
1010
import { getCurrentTeam, TeamsContext } from "./teams-context";
1111
import { getTeamSettingsMenu } from "./TeamSettings";
1212
import { PaymentContext } from "../payment-context";
13+
import { getGitpodService } from "../service/service";
14+
import { BillableSession } from "@gitpod/gitpod-protocol/lib/usage";
15+
import { Item, ItemField, ItemsList } from "../components/ItemsList";
16+
import moment from "moment";
17+
import Property from "../admin/Property";
1318

1419
function TeamUsage() {
1520
const { teams } = useContext(TeamsContext);
16-
const { showUsageBasedUI } = useContext(PaymentContext);
21+
const { showPaymentUI } = useContext(PaymentContext);
1722
const location = useLocation();
1823
const team = getCurrentTeam(location, teams);
24+
const [billedUsage, setBilledUsage] = useState<BillableSession[]>([]);
25+
26+
useEffect(() => {
27+
if (!team) {
28+
return;
29+
}
30+
(async () => {
31+
const billedUsageResult = await getGitpodService().server.getBilledUsage("some-attribution-id");
32+
setBilledUsage(billedUsageResult);
33+
})();
34+
}, [team]);
35+
36+
const getType = (type: string) => {
37+
if (type === "regular") {
38+
return "Workspace";
39+
}
40+
return "Prebuild";
41+
};
42+
43+
const getHours = (endTime: number, startTime: number) => {
44+
return (endTime - startTime) / (1000 * 60 * 60) + "hrs";
45+
};
1946

2047
return (
2148
<PageWithSubMenu
22-
subMenu={getTeamSettingsMenu({ team, showUsageBasedUI })}
49+
subMenu={getTeamSettingsMenu({ team, showPaymentUI })}
2350
title="Usage"
2451
subtitle="Manage team usage."
2552
>
26-
<div className="flex flex-col space-y-2">
27-
<div className="px-6 py-3 flex justify-between text-sm text-gray-400 border-t border-b border-gray-200 dark:border-gray-800 mb-2">
28-
<div className="w-7/12">Workspace</div>
29-
<div className="w-5/12">Usage duration</div>
30-
<div className="w-5/12">Credits used</div>
31-
<div className="w-5/12">Last used</div>
53+
<div className="flex flex-col w-full">
54+
<div className="flex w-full mt-6 mb-6">
55+
<Property name="Workspaces">4,200 Min</Property>
56+
<Property name="Prebuilds">12,334 Min</Property>
57+
<Property name="Last 30 days">Jun 1 - June 30</Property>
3258
</div>
3359
</div>
60+
<ItemsList className="mt-2 text-gray-500">
61+
<Item header={false} className="grid grid-cols-5 bg-gray-100">
62+
<ItemField className="my-auto">
63+
<span>Type</span>
64+
</ItemField>
65+
<ItemField className="my-auto">
66+
<span>Class</span>
67+
</ItemField>
68+
<ItemField className="my-auto">
69+
<span>Amount</span>
70+
</ItemField>
71+
<ItemField className="my-auto">
72+
<span>Credits</span>
73+
</ItemField>
74+
<ItemField className="my-auto">
75+
<span>Triggered</span>
76+
</ItemField>
77+
</Item>
78+
{billedUsage.map((usage) => (
79+
<div
80+
key={usage.instanceId}
81+
className="flex p-3 grid grid-cols-5 justify-between transition ease-in-out rounded-xl focus:bg-gitpod-kumquat-light"
82+
>
83+
<div className="my-auto">
84+
<span className={usage.workspaceType === "prebuild" ? "text-orange-400" : "text-green-500"}>
85+
{getType(usage.workspaceType)}
86+
</span>
87+
</div>
88+
<div className="my-auto">
89+
<span className="text-gray-400">{usage.workspaceClass}</span>
90+
</div>
91+
<div className="my-auto">
92+
<span className="text-gray-700">{getHours(usage.endTime, usage.startTime)}</span>
93+
</div>
94+
<div className="my-auto">
95+
<span className="text-gray-700">{usage.credits}</span>
96+
</div>
97+
<div className="my-auto">
98+
<span className="text-gray-400">
99+
{moment(new Date(usage.startTime).toDateString()).fromNow()}
100+
</span>
101+
</div>
102+
</div>
103+
))}
104+
</ItemsList>
34105
</PageWithSubMenu>
35106
);
36107
}

components/gitpod-protocol/src/gitpod-service.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ import { RemotePageMessage, RemoteTrackMessage, RemoteIdentifyMessage } from "./
6060
import { IDEServer } from "./ide-protocol";
6161
import { InstallationAdminSettings, TelemetryData } from "./installation-admin-protocol";
6262
import { Currency } from "./plans";
63+
import { BillableSession } from "./usage";
6364

6465
export interface GitpodClient {
6566
onInstanceUpdate(instance: WorkspaceInstance): void;
@@ -288,6 +289,8 @@ export interface GitpodServer extends JsonRpcServer<GitpodClient>, AdminServer,
288289
subscribeTeamToStripe(teamId: string, setupIntentId: string, currency: Currency): Promise<void>;
289290
getStripePortalUrlForTeam(teamId: string): Promise<string>;
290291

292+
getBilledUsage(attributionId: string): Promise<BillableSession[]>;
293+
291294
/**
292295
* Analytics
293296
*/
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
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 { WorkspaceType } from "./protocol";
8+
9+
export interface BillableSession {
10+
// The id of the one paying the bill
11+
attributionId: string;
12+
13+
// Relevant for workspace type. When prebuild, shows "prebuild"
14+
userId?: string;
15+
teamId?: string;
16+
17+
instanceId: string;
18+
19+
workspaceId: string;
20+
21+
workspaceType: WorkspaceType;
22+
23+
// "standard" or "XL"
24+
workspaceClass: string;
25+
26+
// When the workspace started
27+
startTime: number;
28+
29+
// When the workspace ended
30+
endTime: number;
31+
32+
// The credits used for this session
33+
credits: number;
34+
35+
// TODO - maybe
36+
projectId?: string;
37+
}
38+
39+
export const billableSessionDummyData: BillableSession[] = [
40+
{
41+
attributionId: "some-attribution-id",
42+
userId: "prebuild",
43+
teamId: "prebuild",
44+
instanceId: "some-instance-id",
45+
workspaceId: "some-workspace-id",
46+
workspaceType: "prebuild",
47+
workspaceClass: "XL",
48+
startTime: Date.now() + -3 * 24 * 3600 * 1000, // 3 days ago
49+
endTime: Date.now(),
50+
credits: 320,
51+
projectId: "project-123",
52+
},
53+
{
54+
attributionId: "some-attribution-id2",
55+
userId: "some-user",
56+
teamId: "some-team",
57+
instanceId: "some-instance-id2",
58+
workspaceId: "some-workspace-id2",
59+
workspaceType: "regular",
60+
workspaceClass: "standard",
61+
startTime: Date.now() + -5 * 24 * 3600 * 1000,
62+
endTime: Date.now(),
63+
credits: 130,
64+
projectId: "project-123",
65+
},
66+
{
67+
attributionId: "some-attribution-id3",
68+
userId: "some-other-user",
69+
teamId: "some-other-team",
70+
instanceId: "some-instance-id3",
71+
workspaceId: "some-workspace-id3",
72+
workspaceType: "regular",
73+
workspaceClass: "XL",
74+
startTime: Date.now() + -5 * 24 * 3600 * 1000,
75+
endTime: Date.now() + -4 * 24 * 3600 * 1000,
76+
credits: 150,
77+
projectId: "project-134",
78+
},
79+
{
80+
attributionId: "some-attribution-id4",
81+
userId: "some-other-user2",
82+
teamId: "some-other-team2",
83+
instanceId: "some-instance-id4",
84+
workspaceId: "some-workspace-id4",
85+
workspaceType: "regular",
86+
workspaceClass: "standard",
87+
startTime: Date.now() + -10 * 24 * 3600 * 1000,
88+
endTime: Date.now() + -9 * 24 * 3600 * 1000,
89+
credits: 330,
90+
projectId: "project-137",
91+
},
92+
{
93+
attributionId: "some-attribution-id5",
94+
userId: "some-other-user3",
95+
teamId: "some-other-team3",
96+
instanceId: "some-instance-id5",
97+
workspaceId: "some-workspace-id5",
98+
workspaceType: "regular",
99+
workspaceClass: "XL",
100+
startTime: Date.now() + -2 * 24 * 3600 * 1000,
101+
endTime: Date.now(),
102+
credits: 222,
103+
projectId: "project-138",
104+
},
105+
{
106+
attributionId: "some-attribution-id6",
107+
userId: "some-other-user4",
108+
teamId: "some-other-team4",
109+
instanceId: "some-instance-id3",
110+
workspaceId: "some-workspace-id3",
111+
workspaceType: "regular",
112+
workspaceClass: "XL",
113+
startTime: Date.now() + -7 * 24 * 3600 * 1000,
114+
endTime: Date.now() + -6 * 24 * 3600 * 1000,
115+
credits: 300,
116+
projectId: "project-134",
117+
},
118+
{
119+
attributionId: "some-attribution-id8",
120+
userId: "some-other-user5",
121+
teamId: "some-other-team5",
122+
instanceId: "some-instance-id3",
123+
workspaceId: "some-workspace-id3",
124+
workspaceType: "regular",
125+
workspaceClass: "standard",
126+
startTime: Date.now() + -1 * 24 * 3600 * 1000,
127+
endTime: Date.now(),
128+
credits: 100,
129+
projectId: "project-567",
130+
},
131+
{
132+
attributionId: "some-attribution-id7",
133+
userId: "prebuild",
134+
teamId: "some-other-team7",
135+
instanceId: "some-instance-id7",
136+
workspaceId: "some-workspace-id7",
137+
workspaceType: "prebuild",
138+
workspaceClass: "XL",
139+
startTime: Date.now() + -1 * 24 * 3600 * 1000,
140+
endTime: Date.now(),
141+
credits: 200,
142+
projectId: "project-345",
143+
},
144+
];

components/server/ee/src/workspace/gitpod-server-impl.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ import { BlockedRepository } from "@gitpod/gitpod-protocol/lib/blocked-repositor
7070
import { EligibilityService } from "../user/eligibility-service";
7171
import { AccountStatementProvider } from "../user/account-statement-provider";
7272
import { GithubUpgradeURL, PlanCoupon } from "@gitpod/gitpod-protocol/lib/payment-protocol";
73+
import { BillableSession, billableSessionDummyData } from "@gitpod/gitpod-protocol/lib/usage";
7374
import {
7475
AssigneeIdentityIdentifier,
7576
TeamSubscription,
@@ -2056,6 +2057,10 @@ export class GitpodServerEEImpl extends GitpodServerImpl {
20562057
}
20572058
}
20582059

2060+
async getBilledUsage(ctx: TraceContext, attributionId: string): Promise<BillableSession[]> {
2061+
return billableSessionDummyData;
2062+
}
2063+
20592064
// (SaaS) – admin
20602065
async adminGetAccountStatement(ctx: TraceContext, userId: string): Promise<AccountStatement> {
20612066
traceAPIParams(ctx, { userId });

components/server/src/auth/rate-limiter.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,7 @@ function getConfig(config: RateLimiterConfig): RateLimiterConfig {
210210
findStripeSubscriptionIdForTeam: { group: "default", points: 1 },
211211
subscribeTeamToStripe: { group: "default", points: 1 },
212212
getStripePortalUrlForTeam: { group: "default", points: 1 },
213+
getBilledUsage: { group: "default", points: 1 },
213214
trackEvent: { group: "default", points: 1 },
214215
trackLocation: { group: "default", points: 1 },
215216
identifyUser: { group: "default", points: 1 },

components/server/src/workspace/gitpod-server-impl.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ import { LicenseEvaluator } from "@gitpod/licensor/lib";
167167
import { Feature } from "@gitpod/licensor/lib/api";
168168
import { Currency } from "@gitpod/gitpod-protocol/lib/plans";
169169
import { getExperimentsClientForBackend } from "@gitpod/gitpod-protocol/lib/experiments/configcat-server";
170+
import { BillableSession } from "@gitpod/gitpod-protocol/lib/usage";
170171

171172
// shortcut
172173
export const traceWI = (ctx: TraceContext, wi: Omit<LogContext, "userId">) => TraceContext.setOWI(ctx, wi); // userId is already taken care of in WebsocketConnectionManager
@@ -3196,6 +3197,11 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {
31963197
async getStripePortalUrlForTeam(ctx: TraceContext, teamId: string): Promise<string> {
31973198
throw new ResponseError(ErrorCodes.SAAS_FEATURE, `Not implemented in this version`);
31983199
}
3200+
3201+
async getBilledUsage(ctx: TraceContext, attributionId: string): Promise<BillableSession[]> {
3202+
throw new ResponseError(ErrorCodes.SAAS_FEATURE, `Not implemented in this version`);
3203+
}
3204+
31993205
//
32003206
//#endregion
32013207
}

0 commit comments

Comments
 (0)