Skip to content

[usage] Wiring ListUsage through server #12656

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Sep 6, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion components/gitpod-protocol/src/gitpod-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ import {
import { RemotePageMessage, RemoteTrackMessage, RemoteIdentifyMessage } from "./analytics";
import { IDEServer } from "./ide-protocol";
import { InstallationAdminSettings, TelemetryData } from "./installation-admin-protocol";
import { ListBilledUsageResponse, ListBilledUsageRequest } from "./usage";
import { ListBilledUsageResponse, ListBilledUsageRequest, ListUsageRequest, ListUsageResponse } from "./usage";
import { SupportedWorkspaceClass } from "./workspace-class";
import { BillingMode } from "./billing-mode";

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

listBilledUsage(req: ListBilledUsageRequest): Promise<ListBilledUsageResponse>;
listUsage(req: ListUsageRequest): Promise<ListUsageResponse>;

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

Expand Down
65 changes: 65 additions & 0 deletions components/gitpod-protocol/src/usage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,68 @@ export interface ListBilledUsageResponse {
}

export type BillableWorkspaceType = WorkspaceType;

// types below are copied over from components/usage-api/typescript/src/usage/v1/usage_pb.d.ts

export interface ListUsageRequest {
attributionId: string;
from?: number;
to?: number;
order: Ordering;
pagination?: PaginationRequest;
}

export enum Ordering {
ORDERING_DESCENDING = 0,
ORDERING_ASCENDING = 1,
}

export interface PaginationRequest {
perPage: number;
page: number;
}

export interface ListUsageResponse {
usageEntriesList: Usage[];
pagination?: PaginationResponse;
creditBalanceAtStart: number;
creditBalanceAtEnd: number;
}

export interface PaginationResponse {
perPage: number;
totalPages: number;
total: number;
page: number;
}

export type UsageKind = "workspaceinstance" | "invoice";
export interface Usage {
id: string;
attributionId: string;
description: string;
credits: number;
effectiveTime?: number;
kind: UsageKind;
workspaceInstanceId: string;
draft: boolean;
metadata: WorkspaceInstanceUsageData | InvoiceUsageData;
}

// the equivalent golang shape is maintained in `/workspace/gitpod/`components/usage/pkg/db/usage.go`
export interface WorkspaceInstanceUsageData {
workspaceId: string;
workspaceType: WorkspaceType;
workspaceClass: string;
contextURL: string;
startTime: string;
endTime?: string;
userName: string;
userAvatarURL: string;
}

export interface InvoiceUsageData {
invoiceId: string;
startDate: string;
endDate: string;
}
65 changes: 62 additions & 3 deletions components/server/ee/src/workspace/gitpod-server-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,13 @@ import { BlockedRepository } from "@gitpod/gitpod-protocol/lib/blocked-repositor
import { EligibilityService } from "../user/eligibility-service";
import { AccountStatementProvider } from "../user/account-statement-provider";
import { GithubUpgradeURL, PlanCoupon } from "@gitpod/gitpod-protocol/lib/payment-protocol";
import { ListBilledUsageRequest, ListBilledUsageResponse } from "@gitpod/gitpod-protocol/lib/usage";
import { ListBilledUsageRequest as ListBilledUsage } from "@gitpod/usage-api/lib/usage/v1/usage_pb";
import {
ListBilledUsageRequest,
ListBilledUsageResponse,
ListUsageRequest,
ListUsageResponse,
} from "@gitpod/gitpod-protocol/lib/usage";
import * as usage_grpc from "@gitpod/usage-api/lib/usage/v1/usage_pb";
import {
AssigneeIdentityIdentifier,
TeamSubscription,
Expand Down Expand Up @@ -2182,6 +2187,60 @@ export class GitpodServerEEImpl extends GitpodServerImpl {
return result;
}

async listUsage(ctx: TraceContext, req: ListUsageRequest): Promise<ListUsageResponse> {
const { attributionId, from, to } = req;
traceAPIParams(ctx, { attributionId });
const user = this.checkAndBlockUser("listBilledUsage");

await this.guardCostCenterAccess(ctx, user.id, attributionId, "get");

const timestampFrom = from ? Timestamp.fromDate(new Date(from)) : undefined;
const timestampTo = to ? Timestamp.fromDate(new Date(to)) : undefined;

const usageClient = this.usageServiceClientProvider.getDefault();
const request = new usage_grpc.ListBilledUsageRequest();
request.setAttributionId(attributionId);
request.setFrom(timestampFrom);
if (to) {
request.setTo(timestampTo);
}
request.setOrder(req.order);
if (req.pagination) {
const paginatedRequest = new usage_grpc.PaginatedRequest();
paginatedRequest.setPage(req.pagination.page);
paginatedRequest.setPerPage(req.pagination.perPage);
request.setPagination(paginatedRequest);
}
const response = await usageClient.listUsage(ctx, request);
const pagination = response.getPagination();
return {
usageEntriesList: response.getUsageEntriesList().map((u) => {
return {
id: u.getId(),
attributionId: u.getAttributionId(),
effectiveTime: u.getEffectiveTime()!.toDate().getTime(),
credits: u.getCredits(),
description: u.getDescription(),
draft: u.getDraft(),
workspaceInstanceId: u.getWorkspaceInstanceId(),
kind:
u.getKind() === usage_grpc.Usage.Kind.KIND_WORKSPACE_INSTANCE ? "workspaceinstance" : "invoice",
metadata: JSON.parse(u.getMetadata()),
};
}),
pagination: pagination
? {
page: pagination.getPage(),
perPage: pagination.getPerPage(),
total: pagination.getTotal(),
totalPages: pagination.getTotalPages(),
}
: undefined,
creditBalanceAtEnd: response.getCreditBalanceAtEnd(),
creditBalanceAtStart: response.getCreditBalanceAtStart(),
};
}

async listBilledUsage(ctx: TraceContext, req: ListBilledUsageRequest): Promise<ListBilledUsageResponse> {
const { attributionId, fromDate, toDate, perPage, page } = req;
traceAPIParams(ctx, { attributionId });
Expand All @@ -2201,7 +2260,7 @@ export class GitpodServerEEImpl extends GitpodServerImpl {
const response = await usageClient.listBilledUsage(
ctx,
attributionId,
ListBilledUsage.Ordering.ORDERING_DESCENDING,
usage_grpc.ListBilledUsageRequest.Ordering.ORDERING_DESCENDING,
perPage,
page,
timestampFrom,
Expand Down
1 change: 1 addition & 0 deletions components/server/src/auth/rate-limiter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ const defaultFunctions: FunctionsConfig = {
subscribeTeamToStripe: { group: "default", points: 1 },
getStripePortalUrlForTeam: { group: "default", points: 1 },
listBilledUsage: { group: "default", points: 1 },
listUsage: { group: "default", points: 1 },
getBillingModeForTeam: { group: "default", points: 1 },
getBillingModeForUser: { group: "default", points: 1 },

Expand Down
11 changes: 10 additions & 1 deletion components/server/src/workspace/gitpod-server-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,12 @@ import { InstallationAdminTelemetryDataProvider } from "../installation-admin/te
import { LicenseEvaluator } from "@gitpod/licensor/lib";
import { Feature } from "@gitpod/licensor/lib/api";
import { getExperimentsClientForBackend } from "@gitpod/gitpod-protocol/lib/experiments/configcat-server";
import { ListBilledUsageRequest, ListBilledUsageResponse } from "@gitpod/gitpod-protocol/lib/usage";
import {
ListBilledUsageRequest,
ListBilledUsageResponse,
ListUsageRequest,
ListUsageResponse,
} from "@gitpod/gitpod-protocol/lib/usage";
import { WorkspaceClusterImagebuilderClientProvider } from "./workspace-cluster-imagebuilder-client-provider";
import { VerificationService } from "../auth/verification-service";
import { BillingMode } from "@gitpod/gitpod-protocol/lib/billing-mode";
Expand Down Expand Up @@ -3232,6 +3237,10 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {
throw new ResponseError(ErrorCodes.SAAS_FEATURE, `Not implemented in this version`);
}

async listUsage(ctx: TraceContext, req: ListUsageRequest): Promise<ListUsageResponse> {
throw new ResponseError(ErrorCodes.SAAS_FEATURE, `Not implemented in this version`);
}

async getSpendingLimitForTeam(ctx: TraceContext, teamId: string): Promise<number | undefined> {
throw new ResponseError(ErrorCodes.SAAS_FEATURE, `Not implemented in this version`);
}
Expand Down
30 changes: 29 additions & 1 deletion components/usage-api/typescript/src/usage/v1/sugar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { BillingServiceClient } from "./billing_grpc_pb";
import { TraceContext } from "@gitpod/gitpod-protocol/lib/util/tracing";
import * as opentracing from "opentracing";
import { Metadata } from "@grpc/grpc-js";
import { BilledSession, ListBilledUsageRequest, ListBilledUsageResponse, PaginatedRequest } from "./usage_pb";
import { BilledSession, ListBilledUsageRequest, ListBilledUsageResponse, ListUsageRequest, ListUsageResponse, PaginatedRequest } from "./usage_pb";
import {
GetUpcomingInvoiceRequest,
GetUpcomingInvoiceResponse,
Expand Down Expand Up @@ -194,6 +194,34 @@ export class PromisifiedUsageServiceClient {
}
}

public async listUsage(
_ctx: TraceContext,
request: ListUsageRequest,
): Promise<ListUsageResponse> {
const ctx = TraceContext.childContext(`/usage-service/listUsage`, _ctx);
try {
const response = await new Promise<ListUsageResponse>((resolve, reject) => {
this.client.listUsage(
request,
withTracing(ctx),
(err: grpc.ServiceError | null, response: ListUsageResponse) => {
if (err) {
reject(err);
return;
}
resolve(response);
},
);
});
return response;
} catch (err) {
TraceContext.setError(ctx, err);
throw err;
} finally {
ctx.span.finish();
}
}

public dispose() {
this.client.close();
}
Expand Down
13 changes: 13 additions & 0 deletions components/usage/pkg/db/usage.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,19 @@ type Usage struct {
Metadata datatypes.JSON `gorm:"column:metadata;type:text;size:65535" json:"metadata"`
}

// WorkspaceInstanceUsageData represents the shape of metadata for usage entries of kind "workspaceinstance"
// the equivalent TypeScript definition is maintained in `components/gitpod-protocol/src/usage.ts“
type WorkspaceInstanceUsageData struct {
WorkspaceId string `json:"workspaceId"`
WorkspaceType WorkspaceType `json:"workspaceType"`
WorkspaceClass string `json:"workspaceClass"`
ContextURL string `json:"contextURL"`
StartTime string `json:"startTime"`
EndTime string `json:"endTime"`
UserName string `json:"userName"`
UserAvatarURL string `json:"userAvatarURL"`
}

type FindUsageResult struct {
UsageEntries []Usage
}
Expand Down