Skip to content

Commit 28a9896

Browse files
committed
[server][dashboard] Fetch and use the actual billing cycle dates in usage-based Billing pages
1 parent aa320ac commit 28a9896

File tree

5 files changed

+44
-7
lines changed

5 files changed

+44
-7
lines changed

components/dashboard/src/components/UsageBasedBillingConfig.tsx

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,8 @@ export default function UsageBasedBillingConfig({ attributionId }: Props) {
4545

4646
const localStorageKey = `pendingStripeSubscriptionFor${attributionId}`;
4747
const now = dayjs().utc(true);
48-
const billingPeriodFrom = now.startOf("month");
49-
const billingPeriodTo = now.endOf("month");
48+
const [billingPeriodFrom, setBillingPeriodFrom] = useState<dayjs.Dayjs>(now.startOf("month"));
49+
const [billingPeriodTo, setBillingPeriodTo] = useState<dayjs.Dayjs>(now.endOf("month"));
5050

5151
useEffect(() => {
5252
if (!attributionId) {
@@ -56,12 +56,17 @@ export default function UsageBasedBillingConfig({ attributionId }: Props) {
5656
setStripeSubscriptionId(undefined);
5757
setIsLoadingStripeSubscription(true);
5858
try {
59-
const [subscriptionId, limit] = await Promise.all([
59+
const [subscriptionId, limit, billingCycleDates] = await Promise.all([
6060
getGitpodService().server.findStripeSubscriptionId(attributionId),
6161
getGitpodService().server.getUsageLimit(attributionId),
62+
getGitpodService().server.getCurrentBillingCycleDates(attributionId),
6263
]);
6364
setStripeSubscriptionId(subscriptionId);
6465
setUsageLimit(limit);
66+
if (billingCycleDates) {
67+
setBillingPeriodFrom(dayjs(billingCycleDates.from).utc(true));
68+
setBillingPeriodTo(dayjs(billingCycleDates.to).utc(true));
69+
}
6570
} catch (error) {
6671
console.error("Could not get Stripe subscription details.", error);
6772
setErrorMessage(`Could not get Stripe subscription details. ${error?.message || String(error)}`);
@@ -183,7 +188,7 @@ export default function UsageBasedBillingConfig({ attributionId }: Props) {
183188
});
184189
setCurrentUsage(response.creditsUsed);
185190
})();
186-
}, [attributionId]);
191+
}, [attributionId, billingPeriodFrom]);
187192

188193
const showSpinner = !attributionId || isLoadingStripeSubscription || !!pendingStripeSubscription;
189194
const showBalance = !showSpinner && !(AttributionId.parse(attributionId)?.kind === "team" && !stripeSubscriptionId);

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,7 @@ export interface GitpodServer extends JsonRpcServer<GitpodClient>, AdminServer,
281281
createStripeCustomerIfNeeded(attributionId: string, currency: string): Promise<void>;
282282
subscribeToStripe(attributionId: string, setupIntentId: string, usageLimit: number): Promise<number | undefined>;
283283
getStripePortalUrl(attributionId: string): Promise<string>;
284+
getCurrentBillingCycleDates(attributionId: string): Promise<{ from: string; to: string } | undefined>;
284285
getUsageLimit(attributionId: string): Promise<number | undefined>;
285286
setUsageLimit(attributionId: string, usageLimit: number): Promise<void>;
286287

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

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2258,6 +2258,29 @@ export class GitpodServerEEImpl extends GitpodServerImpl {
22582258
return url;
22592259
}
22602260

2261+
async getCurrentBillingCycleDates(
2262+
ctx: TraceContext,
2263+
attributionId: string,
2264+
): Promise<{ from: string; to: string } | undefined> {
2265+
const attrId = AttributionId.parse(attributionId);
2266+
if (attrId === undefined) {
2267+
log.error(`Invalid attribution id: ${attributionId}`);
2268+
throw new ResponseError(ErrorCodes.BAD_REQUEST, `Invalid attibution id: ${attributionId}`);
2269+
}
2270+
2271+
const user = this.checkAndBlockUser("getUsageLimit");
2272+
await this.guardCostCenterAccess(ctx, user.id, attrId, "get");
2273+
2274+
const { costCenter } = await this.usageService.getCostCenter({ attributionId });
2275+
if (costCenter?.creationTime && costCenter?.nextBillingTime) {
2276+
return {
2277+
from: costCenter.creationTime.toISOString(),
2278+
to: costCenter.nextBillingTime.toISOString(),
2279+
};
2280+
}
2281+
return undefined;
2282+
}
2283+
22612284
async getUsageLimit(ctx: TraceContext, attributionId: string): Promise<number | undefined> {
22622285
const attrId = AttributionId.parse(attributionId);
22632286
if (attrId === undefined) {
@@ -2268,9 +2291,9 @@ export class GitpodServerEEImpl extends GitpodServerImpl {
22682291
const user = this.checkAndBlockUser("getUsageLimit");
22692292
await this.guardCostCenterAccess(ctx, user.id, attrId, "get");
22702293

2271-
const costCenter = await this.usageService.getCostCenter({ attributionId });
2272-
if (costCenter?.costCenter) {
2273-
return costCenter.costCenter.spendingLimit;
2294+
const { costCenter } = await this.usageService.getCostCenter({ attributionId });
2295+
if (costCenter) {
2296+
return costCenter.spendingLimit;
22742297
}
22752298
return undefined;
22762299
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,7 @@ const defaultFunctions: FunctionsConfig = {
214214
getPrebuildEvents: { group: "default", points: 1 },
215215
setUsageAttribution: { group: "default", points: 1 },
216216
listAvailableUsageAttributionIds: { group: "default", points: 1 },
217+
getCurrentBillingCycleDates: { group: "default", points: 1 },
217218
getUsageLimit: { group: "default", points: 1 },
218219
setUsageLimit: { group: "default", points: 1 },
219220
getNotifications: { group: "default", points: 1 },

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3144,6 +3144,13 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {
31443144
throw new ResponseError(ErrorCodes.SAAS_FEATURE, `Not implemented in this version`);
31453145
}
31463146

3147+
async getCurrentBillingCycleDates(
3148+
ctx: TraceContext,
3149+
attributionId: string,
3150+
): Promise<{ from: string; to: string } | undefined> {
3151+
throw new ResponseError(ErrorCodes.SAAS_FEATURE, `Not implemented in this version`);
3152+
}
3153+
31473154
async getUsageLimit(ctx: TraceContext, attributionId: string): Promise<number | undefined> {
31483155
throw new ResponseError(ErrorCodes.SAAS_FEATURE, `Not implemented in this version`);
31493156
}

0 commit comments

Comments
 (0)