Skip to content

Commit 568f5f2

Browse files
author
Andrew Farries
committed
Add getUsageLimitFor and setUsageLimitFor
Add two new methods to the server API for getting and setting usage limits. Both new functions take an attributionId and work for both users and teams. For backwards compatibility, leave the `getUsageLimitForTeam` and `setUsageLimitForTeam` methods as they are still used by the dashboard, but change them to be implemented in terms of the more general `get/set` methods.
1 parent 6635528 commit 568f5f2

File tree

4 files changed

+69
-16
lines changed

4 files changed

+69
-16
lines changed

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

+2
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,8 @@ export interface GitpodServer extends JsonRpcServer<GitpodClient>, AdminServer,
295295
createOrUpdateStripeCustomerForUser(currency: string): Promise<void>;
296296
subscribeTeamToStripe(teamId: string, setupIntentId: string): Promise<void>;
297297
getStripePortalUrlForTeam(teamId: string): Promise<string>;
298+
getUsageLimit(attributionId: string): Promise<number | undefined>;
299+
setUsageLimit(attributionId: string, usageLimit: number): Promise<void>;
298300
getUsageLimitForTeam(teamId: string): Promise<number | undefined>;
299301
setUsageLimitForTeam(teamId: string, spendingLimit: number): Promise<void>;
300302

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

+57-16
Original file line numberDiff line numberDiff line change
@@ -2146,39 +2146,80 @@ export class GitpodServerEEImpl extends GitpodServerImpl {
21462146
}
21472147
}
21482148

2149-
async getUsageLimitForTeam(ctx: TraceContext, teamId: string): Promise<number | undefined> {
2150-
const user = this.checkAndBlockUser("getUsageLimitForTeam");
2151-
const team = await this.guardTeamOperation(teamId, "get");
2152-
await this.ensureStripeApiIsAllowed({ team });
2149+
async getUsageLimit(ctx: TraceContext, attributionId: string): Promise<number | undefined> {
2150+
const attrId = AttributionId.parse(attributionId);
2151+
if (attrId === undefined) {
2152+
log.error(`Invalid attribution id: ${attributionId}`);
2153+
throw new ResponseError(ErrorCodes.BAD_REQUEST, `Invalid attibution id: ${attributionId}`);
2154+
}
21532155

2154-
const attributionId: AttributionId = { kind: "team", teamId };
2155-
await this.guardCostCenterAccess(ctx, user.id, attributionId, "get");
2156+
const user = this.checkAndBlockUser("getUsageLimit");
2157+
switch (attrId.kind) {
2158+
case "team":
2159+
const team = await this.guardTeamOperation(attrId.teamId, "get");
2160+
await this.ensureStripeApiIsAllowed({ team });
2161+
break;
2162+
case "user":
2163+
await this.ensureStripeApiIsAllowed({ user });
2164+
break;
2165+
}
2166+
await this.guardCostCenterAccess(ctx, user.id, attrId, "get");
21562167

2157-
const costCenter = await this.usageServiceClientProvider.getDefault().getCostCenter(attributionId);
2168+
const costCenter = await this.usageServiceClientProvider.getDefault().getCostCenter(attrId);
21582169
if (costCenter) {
21592170
return costCenter.spendingLimit;
21602171
}
21612172
return undefined;
21622173
}
21632174

2164-
async setUsageLimitForTeam(ctx: TraceContext, teamId: string, usageLimit: number): Promise<void> {
2165-
const user = this.checkAndBlockUser("setUsageLimitForTeam");
2166-
const team = await this.guardTeamOperation(teamId, "update");
2167-
await this.ensureStripeApiIsAllowed({ team });
2175+
async setUsageLimit(ctx: TraceContext, attributionId: string, usageLimit: number): Promise<void> {
2176+
const attrId = AttributionId.parse(attributionId);
2177+
if (attrId === undefined) {
2178+
log.error(`Invalid attribution id: ${attributionId}`);
2179+
throw new ResponseError(ErrorCodes.BAD_REQUEST, `Invalid attibution id: ${attributionId}`);
2180+
}
21682181
if (typeof usageLimit !== "number" || usageLimit < 0) {
2169-
throw new ResponseError(ErrorCodes.BAD_REQUEST, "Unexpected `usageLimit` value.");
2182+
throw new ResponseError(ErrorCodes.BAD_REQUEST, `Unexpected usageLimit value: ${usageLimit}`);
2183+
}
2184+
2185+
const user = this.checkAndBlockUser("setUsageLimit");
2186+
switch (attrId.kind) {
2187+
case "team":
2188+
const team = await this.guardTeamOperation(attrId.teamId, "update");
2189+
await this.ensureStripeApiIsAllowed({ team });
2190+
break;
2191+
case "user":
2192+
await this.ensureStripeApiIsAllowed({ user });
2193+
break;
2194+
}
2195+
await this.guardCostCenterAccess(ctx, user.id, attrId, "update");
2196+
2197+
const costCenter = await this.usageServiceClientProvider.getDefault().getCostCenter(attrId);
2198+
if (costCenter?.billingStrategy !== "stripe") {
2199+
throw new ResponseError(
2200+
ErrorCodes.BAD_REQUEST,
2201+
`Setting a usage limit is not valid for non-Stripe billing strategies`,
2202+
);
21702203
}
2171-
const attributionId: AttributionId = { kind: "team", teamId };
2172-
await this.guardCostCenterAccess(ctx, user.id, attributionId, "update");
21732204

2174-
const costCenter = await this.usageServiceClientProvider.getDefault().getCostCenter(attributionId);
21752205
await this.usageServiceClientProvider.getDefault().setCostCenter({
2176-
id: attributionId,
2206+
id: attrId,
21772207
spendingLimit: usageLimit,
21782208
billingStrategy: costCenter?.billingStrategy || "other",
21792209
});
21802210
}
21812211

2212+
async getUsageLimitForTeam(ctx: TraceContext, teamId: string): Promise<number | undefined> {
2213+
const attributionId: AttributionId = { kind: "team", teamId: teamId };
2214+
2215+
return this.getUsageLimit(ctx, AttributionId.render(attributionId));
2216+
}
2217+
2218+
async setUsageLimitForTeam(ctx: TraceContext, teamId: string, usageLimit: number): Promise<void> {
2219+
const attributionId: AttributionId = { kind: "team", teamId: teamId };
2220+
return this.setUsageLimit(ctx, AttributionId.render(attributionId), usageLimit);
2221+
}
2222+
21822223
async getNotifications(ctx: TraceContext): Promise<string[]> {
21832224
const result = await super.getNotifications(ctx);
21842225
const user = this.checkAndBlockUser("getNotifications");

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

+2
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,8 @@ const defaultFunctions: FunctionsConfig = {
221221
getIDEOptions: { group: "default", points: 1 },
222222
getPrebuildEvents: { group: "default", points: 1 },
223223
setUsageAttribution: { group: "default", points: 1 },
224+
getUsageLimit: { group: "default", points: 1 },
225+
setUsageLimit: { group: "default", points: 1 },
224226
getUsageLimitForTeam: { group: "default", points: 1 },
225227
setUsageLimitForTeam: { group: "default", points: 1 },
226228
getNotifications: { group: "default", points: 1 },

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

+8
Original file line numberDiff line numberDiff line change
@@ -3238,6 +3238,14 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {
32383238
throw new ResponseError(ErrorCodes.SAAS_FEATURE, `Not implemented in this version`);
32393239
}
32403240

3241+
async getUsageLimit(ctx: TraceContext, attributionId: string): Promise<number | undefined> {
3242+
throw new ResponseError(ErrorCodes.SAAS_FEATURE, `Not implemented in this version`);
3243+
}
3244+
3245+
async setUsageLimit(ctx: TraceContext, attributionId: string, usageLimit: number): Promise<void> {
3246+
throw new ResponseError(ErrorCodes.SAAS_FEATURE, `Not implemented in this version`);
3247+
}
3248+
32413249
async getUsageLimitForTeam(ctx: TraceContext, teamId: string): Promise<number | undefined> {
32423250
throw new ResponseError(ErrorCodes.SAAS_FEATURE, `Not implemented in this version`);
32433251
}

0 commit comments

Comments
 (0)