Skip to content

Commit 1cb0f58

Browse files
author
Laurie T. Malau
committed
Show workspace and user details
1 parent b89a417 commit 1cb0f58

File tree

6 files changed

+101
-45
lines changed

6 files changed

+101
-45
lines changed
Lines changed: 1 addition & 0 deletions
Loading

components/dashboard/src/teams/TeamUsage.tsx

Lines changed: 78 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ import { useLocation } from "react-router";
99
import { getCurrentTeam, TeamsContext } from "./teams-context";
1010
import { getGitpodService, gitpodHostUrl } from "../service/service";
1111
import {
12-
BillableSession,
1312
BillableSessionRequest,
1413
BillableWorkspaceType,
14+
ExtendedBillableSession,
1515
SortOrder,
1616
} from "@gitpod/gitpod-protocol/lib/usage";
1717
import { AttributionId } from "@gitpod/gitpod-protocol/lib/attribution";
@@ -22,14 +22,16 @@ import { ErrorCodes } from "@gitpod/gitpod-protocol/lib/messaging/error";
2222
import { ReactComponent as CreditsSvg } from "../images/credits.svg";
2323
import { ReactComponent as Spinner } from "../icons/Spinner.svg";
2424
import { ReactComponent as SortArrow } from "../images/sort-arrow.svg";
25+
import { ReactComponent as UsageIcon } from "../images/usage-default.svg";
2526
import { BillingMode } from "@gitpod/gitpod-protocol/lib/billing-mode";
27+
import { toRemoteURL } from "../projects/render-utils";
2628

2729
function TeamUsage() {
2830
const { teams } = useContext(TeamsContext);
2931
const location = useLocation();
3032
const team = getCurrentTeam(location, teams);
3133
const [teamBillingMode, setTeamBillingMode] = useState<BillingMode | undefined>(undefined);
32-
const [billedUsage, setBilledUsage] = useState<BillableSession[]>([]);
34+
const [billedUsage, setBilledUsage] = useState<ExtendedBillableSession[]>([]);
3335
const [currentPage, setCurrentPage] = useState(1);
3436
const [resultsPerPage] = useState(50);
3537
const [errorMessage, setErrorMessage] = useState("");
@@ -55,6 +57,9 @@ function TeamUsage() {
5557
if (!team) {
5658
return;
5759
}
60+
if (billedUsage.length === 0) {
61+
setIsLoading(true);
62+
}
5863
(async () => {
5964
const attributionId = AttributionId.render({ kind: "team", teamId: team.id });
6065
const request: BillableSessionRequest = {
@@ -64,7 +69,8 @@ function TeamUsage() {
6469
to: endDateOfBillMonth,
6570
};
6671
try {
67-
const billedUsageResult = await getGitpodService().server.listBilledUsage(request);
72+
const { server } = getGitpodService();
73+
const billedUsageResult = await server.listBilledUsage(request);
6874
setBilledUsage(billedUsageResult);
6975
} catch (error) {
7076
if (error.code === ErrorCodes.PERMISSION_DENIED) {
@@ -92,7 +98,7 @@ function TeamUsage() {
9298
return "Prebuild";
9399
};
94100

95-
const getMinutes = (usage: BillableSession) => {
101+
const getMinutes = (usage: ExtendedBillableSession) => {
96102
let end;
97103
if (!usage.endTime) {
98104
end = new Date(Date.now()).getTime();
@@ -141,7 +147,7 @@ function TeamUsage() {
141147
const displayTime = (time: string) => {
142148
const options: Intl.DateTimeFormatOptions = {
143149
day: "numeric",
144-
month: "long",
150+
month: "short",
145151
year: "numeric",
146152
hour: "numeric",
147153
minute: "numeric",
@@ -184,7 +190,7 @@ function TeamUsage() {
184190
</div>
185191
</div>
186192
</div>
187-
{billedUsage.length === 0 && !errorMessage && !isLoading && (
193+
{!isLoading && billedUsage.length === 0 && !errorMessage && (
188194
<div className="flex flex-col w-full mb-8">
189195
<h3 className="text-center text-gray-500 mt-8">No sessions found.</h3>
190196
<p className="text-center text-gray-500 mt-1">
@@ -212,26 +218,23 @@ function TeamUsage() {
212218
{billedUsage.length > 0 && !isLoading && (
213219
<div className="flex flex-col w-full mb-8">
214220
<ItemsList className="mt-2 text-gray-500">
215-
<Item header={false} className="grid grid-cols-5 bg-gray-100 mb-5">
216-
<ItemField className="my-auto">
221+
<Item header={false} className="grid grid-cols-12 gap-x-3 bg-gray-100 mb-5">
222+
<ItemField className="col-span-2 my-auto">
217223
<span>Type</span>
218224
</ItemField>
219-
<ItemField className="my-auto">
220-
<span>Class</span>
225+
<ItemField className="col-span-5 my-auto">
226+
<span>ID</span>
221227
</ItemField>
222228
<ItemField className="my-auto">
223-
<span>Usage</span>
224-
</ItemField>
225-
<ItemField className="flex my-auto">
226-
<CreditsSvg className="my-auto mr-1" />
227229
<span>Credits</span>
228230
</ItemField>
229-
<ItemField className="my-auto cursor-pointer">
231+
<ItemField className="my-auto" />
232+
<ItemField className="col-span-3 my-auto cursor-pointer">
230233
<span
231234
className="flex my-auto"
232235
onClick={() => setIsStartedTimeDescending(!isStartedTimeDescending)}
233236
>
234-
Started
237+
Timestamp
235238
<SortArrow
236239
className={`h-4 w-4 my-auto ${
237240
isStartedTimeDescending ? "" : " transform rotate-180"
@@ -241,30 +244,66 @@ function TeamUsage() {
241244
</ItemField>
242245
</Item>
243246
{currentPaginatedResults &&
244-
currentPaginatedResults.map((usage) => (
245-
<div
246-
key={usage.instanceId}
247-
className="flex p-3 grid grid-cols-5 justify-between transition ease-in-out rounded-xl focus:bg-gitpod-kumquat-light"
248-
>
249-
<div className="my-auto">
250-
<span>{getType(usage.workspaceType)}</span>
251-
</div>
252-
<div className="my-auto">
253-
<span className="text-gray-400">{usage.workspaceClass}</span>
254-
</div>
255-
<div className="my-auto">
256-
<span className="text-gray-700">{getMinutes(usage)}</span>
257-
</div>
258-
<div className="my-auto">
259-
<span className="text-gray-700">{usage.credits.toFixed(1)}</span>
260-
</div>
261-
<div className="my-auto">
262-
<span className="text-gray-400">
263-
{displayTime(usage.startTime)}
264-
</span>
247+
currentPaginatedResults.map((usage) => {
248+
return (
249+
<div
250+
key={usage.instanceId}
251+
className="flex p-3 grid grid-cols-12 gap-x-3 justify-between transition ease-in-out rounded-xl focus:bg-gitpod-kumquat-light"
252+
>
253+
<div className="flex flex-col col-span-2 my-auto">
254+
<span className="text-gray-700 dark:text-gray-400">
255+
{getType(usage.workspaceType)}
256+
</span>
257+
<span className="text-sm text-gray-400 dark:text-gray-600">
258+
{usage.workspaceClass}
259+
</span>
260+
</div>
261+
<div className="flex flex-col col-span-5 my-auto">
262+
<span className="truncate text-gray-700 dark:text-gray-400">
263+
{usage.workspaceId}
264+
</span>
265+
<span className="text-sm truncate text-gray-400 dark:text-gray-600">
266+
{usage.contextURL && toRemoteURL(usage.contextURL)}
267+
</span>
268+
</div>
269+
<div className="flex flex-col my-auto">
270+
<span className="text-right text-gray-700 dark:text-gray-400">
271+
{usage.credits.toFixed(1)}
272+
</span>
273+
<span className="text-right truncate text-sm text-gray-400 dark:text-gray-600">
274+
{getMinutes(usage)}
275+
</span>
276+
</div>
277+
<div className="my-auto" />
278+
<div className="flex flex-col col-span-3 my-auto">
279+
<span className="text-gray-400 truncate">
280+
{displayTime(usage.startTime)}
281+
</span>
282+
<div className="flex">
283+
{usage.workspaceType === "prebuild" ? (
284+
<UsageIcon className="my-auto" />
285+
) : (
286+
""
287+
)}
288+
{usage.workspaceType === "prebuild" ? (
289+
<span className="text-sm text-gray-400">Gitpod</span>
290+
) : (
291+
<div className="flex">
292+
<img
293+
className="my-auto rounded-full w-4 h-4 inline-block align-text-bottom mr-2 overflow-hidden"
294+
src={usage.user?.avatarUrl || ""}
295+
alt="user avatar"
296+
/>
297+
<span className="text-sm text-gray-400">
298+
{usage.user?.name}
299+
</span>
300+
</div>
301+
)}
302+
</div>
303+
</div>
265304
</div>
266-
</div>
267-
))}
305+
);
306+
})}
268307
</ItemsList>
269308
{billedUsage.length > resultsPerPage && (
270309
<Pagination

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ import { JsonRpcProxy, JsonRpcServer } from "./messaging/proxy-factory";
4343
import { Disposable, CancellationTokenSource } from "vscode-jsonrpc";
4444
import { HeadlessLogUrls } from "./headless-workspace-log";
4545
import { WorkspaceInstance, WorkspaceInstancePort, WorkspaceInstancePhase } from "./workspace-instance";
46-
import { AdminServer } from "./admin-protocol";
46+
import { AdminServer, WorkspaceAndInstance } from "./admin-protocol";
4747
import { GitpodHostUrl } from "./util/gitpod-host-url";
4848
import { WebSocketConnectionProvider } from "./messaging/browser/connection";
4949
import { PermissionName } from "./permission";

components/gitpod-protocol/src/usage.ts

Lines changed: 6 additions & 1 deletion
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 { WorkspaceType } from "./protocol";
7+
import { User, WorkspaceType } from "./protocol";
88

99
export interface BillableSession {
1010
// The id of the one paying the bill
@@ -35,6 +35,11 @@ export interface BillableSession {
3535
projectId?: string;
3636
}
3737

38+
export interface ExtendedBillableSession extends BillableSession {
39+
contextURL?: string;
40+
user?: User;
41+
}
42+
3843
export interface BillableSessionRequest {
3944
attributionId: string;
4045
startedTimeOrder: SortOrder;

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

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ import { BlockedRepository } from "@gitpod/gitpod-protocol/lib/blocked-repositor
7171
import { EligibilityService } from "../user/eligibility-service";
7272
import { AccountStatementProvider } from "../user/account-statement-provider";
7373
import { GithubUpgradeURL, PlanCoupon } from "@gitpod/gitpod-protocol/lib/payment-protocol";
74-
import { BillableSession, BillableSessionRequest } from "@gitpod/gitpod-protocol/lib/usage";
74+
import { ExtendedBillableSession, BillableSessionRequest } from "@gitpod/gitpod-protocol/lib/usage";
7575
import {
7676
AssigneeIdentityIdentifier,
7777
TeamSubscription,
@@ -2149,7 +2149,7 @@ export class GitpodServerEEImpl extends GitpodServerImpl {
21492149
return result;
21502150
}
21512151

2152-
async listBilledUsage(ctx: TraceContext, req: BillableSessionRequest): Promise<BillableSession[]> {
2152+
async listBilledUsage(ctx: TraceContext, req: BillableSessionRequest): Promise<ExtendedBillableSession[]> {
21532153
const { attributionId, startedTimeOrder, from, to } = req;
21542154
traceAPIParams(ctx, { attributionId });
21552155
let timestampFrom;
@@ -2173,8 +2173,18 @@ export class GitpodServerEEImpl extends GitpodServerImpl {
21732173
timestampTo,
21742174
);
21752175
const sessions = response.getSessionsList().map((s) => UsageService.mapBilledSession(s));
2176-
2177-
return sessions;
2176+
const extendedSessions = await Promise.all(
2177+
sessions.map(async (session) => {
2178+
let user;
2179+
const ws = await this.workspaceDb.trace(ctx).findWorkspaceAndInstance(session.workspaceId);
2180+
if (session.workspaceType === "regular" && session.userId) {
2181+
user = await this.userDB.findUserById(session.userId);
2182+
return Object.assign(session, { contextURL: ws?.contextURL, user: user });
2183+
}
2184+
return Object.assign(session, { contextURL: ws?.contextURL });
2185+
}),
2186+
);
2187+
return extendedSessions;
21782188
}
21792189

21802190
protected async guardCostCenterAccess(

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3216,6 +3216,7 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {
32163216
async listBilledUsage(ctx: TraceContext, req: BillableSessionRequest): Promise<BillableSession[]> {
32173217
throw new ResponseError(ErrorCodes.SAAS_FEATURE, `Not implemented in this version`);
32183218
}
3219+
32193220
async getSpendingLimitForTeam(ctx: TraceContext, teamId: string): Promise<number | undefined> {
32203221
throw new ResponseError(ErrorCodes.SAAS_FEATURE, `Not implemented in this version`);
32213222
}

0 commit comments

Comments
 (0)