Skip to content

Commit 0970791

Browse files
committed
[server] Added API call to fetch usage data
1 parent aff20f9 commit 0970791

File tree

7 files changed

+182
-6
lines changed

7 files changed

+182
-6
lines changed

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

+2-1
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ import {
6060
import { RemotePageMessage, RemoteTrackMessage, RemoteIdentifyMessage } from "./analytics";
6161
import { IDEServer } from "./ide-protocol";
6262
import { InstallationAdminSettings, TelemetryData } from "./installation-admin-protocol";
63-
import { ListBilledUsageResponse, ListBilledUsageRequest } from "./usage";
63+
import { ListBilledUsageResponse, ListBilledUsageRequest, ListUsageRequest, ListUsageResponse } from "./usage";
6464
import { SupportedWorkspaceClass } from "./workspace-class";
6565
import { BillingMode } from "./billing-mode";
6666

@@ -298,6 +298,7 @@ export interface GitpodServer extends JsonRpcServer<GitpodClient>, AdminServer,
298298
setSpendingLimitForTeam(teamId: string, spendingLimit: number): Promise<void>;
299299

300300
listBilledUsage(req: ListBilledUsageRequest): Promise<ListBilledUsageResponse>;
301+
listUsage(req: ListUsageRequest): Promise<ListUsageResponse>;
301302

302303
setUsageAttribution(usageAttribution: string): Promise<void>;
303304

components/gitpod-protocol/src/usage.ts

+65
Original file line numberDiff line numberDiff line change
@@ -61,3 +61,68 @@ export interface ListBilledUsageResponse {
6161
}
6262

6363
export type BillableWorkspaceType = WorkspaceType;
64+
65+
// types below are copied over from components/usage-api/typescript/src/usage/v1/usage_pb.d.ts
66+
67+
export interface ListUsageRequest {
68+
attributionId: string;
69+
from?: number;
70+
to?: number;
71+
order: Ordering;
72+
pagination?: PaginationRequest;
73+
}
74+
75+
export enum Ordering {
76+
ORDERING_DESCENDING = 0,
77+
ORDERING_ASCENDING = 1,
78+
}
79+
80+
export interface PaginationRequest {
81+
perPage: number;
82+
page: number;
83+
}
84+
85+
export interface ListUsageResponse {
86+
usageEntriesList: Usage[];
87+
pagination?: PaginationResponse;
88+
creditBalanceAtStart: number;
89+
creditBalanceAtEnd: number;
90+
}
91+
92+
export interface PaginationResponse {
93+
perPage: number;
94+
totalPages: number;
95+
total: number;
96+
page: number;
97+
}
98+
99+
export type UsageKind = "workspaceinstance" | "invoice";
100+
export interface Usage {
101+
id: string;
102+
attributionId: string;
103+
description: string;
104+
credits: number;
105+
effectiveTime?: number;
106+
kind: UsageKind;
107+
workspaceInstanceId: string;
108+
draft: boolean;
109+
metadata: WorkspaceInstanceUsageData | InvoiceUsageData;
110+
}
111+
112+
// the equivalent golang shape is maintained in `/workspace/gitpod/`components/usage/pkg/db/usage.go`
113+
export interface WorkspaceInstanceUsageData {
114+
workspaceId: string;
115+
workspaceType: WorkspaceType;
116+
workspaceClass: string;
117+
contextURL: string;
118+
startTime: string;
119+
endTime?: string;
120+
userName: string;
121+
userAvatarURL: string;
122+
}
123+
124+
export interface InvoiceUsageData {
125+
invoiceId: string;
126+
startDate: string;
127+
endDate: string;
128+
}

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

+62-3
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,13 @@ 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 { ListBilledUsageRequest, ListBilledUsageResponse } from "@gitpod/gitpod-protocol/lib/usage";
75-
import { ListBilledUsageRequest as ListBilledUsage } from "@gitpod/usage-api/lib/usage/v1/usage_pb";
74+
import {
75+
ListBilledUsageRequest,
76+
ListBilledUsageResponse,
77+
ListUsageRequest,
78+
ListUsageResponse,
79+
} from "@gitpod/gitpod-protocol/lib/usage";
80+
import * as usage_grpc from "@gitpod/usage-api/lib/usage/v1/usage_pb";
7681
import {
7782
AssigneeIdentityIdentifier,
7883
TeamSubscription,
@@ -2182,6 +2187,60 @@ export class GitpodServerEEImpl extends GitpodServerImpl {
21822187
return result;
21832188
}
21842189

2190+
async listUsage(ctx: TraceContext, req: ListUsageRequest): Promise<ListUsageResponse> {
2191+
const { attributionId, from, to } = req;
2192+
traceAPIParams(ctx, { attributionId });
2193+
const user = this.checkAndBlockUser("listBilledUsage");
2194+
2195+
await this.guardCostCenterAccess(ctx, user.id, attributionId, "get");
2196+
2197+
const timestampFrom = from ? Timestamp.fromDate(new Date(from)) : undefined;
2198+
const timestampTo = to ? Timestamp.fromDate(new Date(to)) : undefined;
2199+
2200+
const usageClient = this.usageServiceClientProvider.getDefault();
2201+
const request = new usage_grpc.ListBilledUsageRequest();
2202+
request.setAttributionId(attributionId);
2203+
request.setFrom(timestampFrom);
2204+
if (to) {
2205+
request.setTo(timestampTo);
2206+
}
2207+
request.setOrder(req.order);
2208+
if (req.pagination) {
2209+
const paginatedRequest = new usage_grpc.PaginatedRequest();
2210+
paginatedRequest.setPage(req.pagination.page);
2211+
paginatedRequest.setPerPage(req.pagination.perPage);
2212+
request.setPagination(paginatedRequest);
2213+
}
2214+
const response = await usageClient.listUsage(ctx, request);
2215+
const pagination = response.getPagination();
2216+
return {
2217+
usageEntriesList: response.getUsageEntriesList().map((u) => {
2218+
return {
2219+
id: u.getId(),
2220+
attributionId: u.getAttributionId(),
2221+
effectiveTime: u.getEffectiveTime()!.toDate().getTime(),
2222+
credits: u.getCredits(),
2223+
description: u.getDescription(),
2224+
draft: u.getDraft(),
2225+
workspaceInstanceId: u.getWorkspaceInstanceId(),
2226+
kind:
2227+
u.getKind() === usage_grpc.Usage.Kind.KIND_WORKSPACE_INSTANCE ? "workspaceinstance" : "invoice",
2228+
metadata: JSON.parse(u.getMetadata()),
2229+
};
2230+
}),
2231+
pagination: pagination
2232+
? {
2233+
page: pagination.getPage(),
2234+
perPage: pagination.getPerPage(),
2235+
total: pagination.getTotal(),
2236+
totalPages: pagination.getTotalPages(),
2237+
}
2238+
: undefined,
2239+
creditBalanceAtEnd: response.getCreditBalanceAtEnd(),
2240+
creditBalanceAtStart: response.getCreditBalanceAtStart(),
2241+
};
2242+
}
2243+
21852244
async listBilledUsage(ctx: TraceContext, req: ListBilledUsageRequest): Promise<ListBilledUsageResponse> {
21862245
const { attributionId, fromDate, toDate, perPage, page } = req;
21872246
traceAPIParams(ctx, { attributionId });
@@ -2201,7 +2260,7 @@ export class GitpodServerEEImpl extends GitpodServerImpl {
22012260
const response = await usageClient.listBilledUsage(
22022261
ctx,
22032262
attributionId,
2204-
ListBilledUsage.Ordering.ORDERING_DESCENDING,
2263+
usage_grpc.ListBilledUsageRequest.Ordering.ORDERING_DESCENDING,
22052264
perPage,
22062265
page,
22072266
timestampFrom,

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

+1
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,7 @@ function getConfig(config: RateLimiterConfig): RateLimiterConfig {
219219
subscribeTeamToStripe: { group: "default", points: 1 },
220220
getStripePortalUrlForTeam: { group: "default", points: 1 },
221221
listBilledUsage: { group: "default", points: 1 },
222+
listUsage: { group: "default", points: 1 },
222223
getBillingModeForTeam: { group: "default", points: 1 },
223224
getBillingModeForUser: { group: "default", points: 1 },
224225

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

+10-1
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,12 @@ import { InstallationAdminTelemetryDataProvider } from "../installation-admin/te
172172
import { LicenseEvaluator } from "@gitpod/licensor/lib";
173173
import { Feature } from "@gitpod/licensor/lib/api";
174174
import { getExperimentsClientForBackend } from "@gitpod/gitpod-protocol/lib/experiments/configcat-server";
175-
import { ListBilledUsageRequest, ListBilledUsageResponse } from "@gitpod/gitpod-protocol/lib/usage";
175+
import {
176+
ListBilledUsageRequest,
177+
ListBilledUsageResponse,
178+
ListUsageRequest,
179+
ListUsageResponse,
180+
} from "@gitpod/gitpod-protocol/lib/usage";
176181
import { WorkspaceClusterImagebuilderClientProvider } from "./workspace-cluster-imagebuilder-client-provider";
177182
import { VerificationService } from "../auth/verification-service";
178183
import { BillingMode } from "@gitpod/gitpod-protocol/lib/billing-mode";
@@ -3232,6 +3237,10 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {
32323237
throw new ResponseError(ErrorCodes.SAAS_FEATURE, `Not implemented in this version`);
32333238
}
32343239

3240+
async listUsage(ctx: TraceContext, req: ListUsageRequest): Promise<ListUsageResponse> {
3241+
throw new ResponseError(ErrorCodes.SAAS_FEATURE, `Not implemented in this version`);
3242+
}
3243+
32353244
async getSpendingLimitForTeam(ctx: TraceContext, teamId: string): Promise<number | undefined> {
32363245
throw new ResponseError(ErrorCodes.SAAS_FEATURE, `Not implemented in this version`);
32373246
}

components/usage-api/typescript/src/usage/v1/sugar.ts

+29-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { BillingServiceClient } from "./billing_grpc_pb";
99
import { TraceContext } from "@gitpod/gitpod-protocol/lib/util/tracing";
1010
import * as opentracing from "opentracing";
1111
import { Metadata } from "@grpc/grpc-js";
12-
import { BilledSession, ListBilledUsageRequest, ListBilledUsageResponse, PaginatedRequest } from "./usage_pb";
12+
import { BilledSession, ListBilledUsageRequest, ListBilledUsageResponse, ListUsageRequest, ListUsageResponse, PaginatedRequest } from "./usage_pb";
1313
import {
1414
GetUpcomingInvoiceRequest,
1515
GetUpcomingInvoiceResponse,
@@ -194,6 +194,34 @@ export class PromisifiedUsageServiceClient {
194194
}
195195
}
196196

197+
public async listUsage(
198+
_ctx: TraceContext,
199+
request: ListUsageRequest,
200+
): Promise<ListUsageResponse> {
201+
const ctx = TraceContext.childContext(`/usage-service/listUsage`, _ctx);
202+
try {
203+
const response = await new Promise<ListUsageResponse>((resolve, reject) => {
204+
this.client.listUsage(
205+
request,
206+
withTracing(ctx),
207+
(err: grpc.ServiceError | null, response: ListUsageResponse) => {
208+
if (err) {
209+
reject(err);
210+
return;
211+
}
212+
resolve(response);
213+
},
214+
);
215+
});
216+
return response;
217+
} catch (err) {
218+
TraceContext.setError(ctx, err);
219+
throw err;
220+
} finally {
221+
ctx.span.finish();
222+
}
223+
}
224+
197225
public dispose() {
198226
this.client.close();
199227
}

components/usage/pkg/db/usage.go

+13
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,19 @@ type Usage struct {
4747
Metadata datatypes.JSON `gorm:"column:metadata;type:text;size:65535" json:"metadata"`
4848
}
4949

50+
// WorkspaceInstanceUsageData represents the shape of metadata for usage entries of kind "workspaceinstance"
51+
// the equivalent TypeScript definition is maintained in `components/gitpod-protocol/src/usage.ts“
52+
type WorkspaceInstanceUsageData struct {
53+
WorkspaceId string `json:"workspaceId"`
54+
WorkspaceType WorkspaceType `json:"workspaceType"`
55+
WorkspaceClass string `json:"workspaceClass"`
56+
ContextURL string `json:"contextURL"`
57+
StartTime string `json:"startTime"`
58+
EndTime string `json:"endTime"`
59+
UserName string `json:"userName"`
60+
UserAvatarURL string `json:"userAvatarURL"`
61+
}
62+
5063
type FindUsageResult struct {
5164
UsageEntries []Usage
5265
}

0 commit comments

Comments
 (0)