Skip to content

Commit d6d8a76

Browse files
committed
[server] Hook up usage API
1 parent 969cb86 commit d6d8a76

File tree

11 files changed

+73
-116
lines changed

11 files changed

+73
-116
lines changed

components/dashboard/src/teams/TeamUsage.tsx

+6-2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { getTeamSettingsMenu } from "./TeamSettings";
1212
import { PaymentContext } from "../payment-context";
1313
import { getGitpodService } from "../service/service";
1414
import { BillableSession, BillableWorkspaceType } from "@gitpod/gitpod-protocol/lib/usage";
15+
import { AttributionId } from "@gitpod/gitpod-protocol/lib/attribution";
1516
import { Item, ItemField, ItemsList } from "../components/ItemsList";
1617
import moment from "moment";
1718
import Property from "../admin/Property";
@@ -29,7 +30,8 @@ function TeamUsage() {
2930
return;
3031
}
3132
(async () => {
32-
const billedUsageResult = await getGitpodService().server.getBilledUsage("some-attribution-id");
33+
const attributionId = AttributionId.render({ kind: "team", teamId: team.id });
34+
const billedUsageResult = await getGitpodService().server.getBilledUsage(attributionId);
3335
setBilledUsage(billedUsageResult);
3436
})();
3537
}, [team]);
@@ -92,7 +94,9 @@ function TeamUsage() {
9294
<span className="text-gray-400">{usage.workspaceClass}</span>
9395
</div>
9496
<div className="my-auto">
95-
<span className="text-gray-700">{getHours(usage.endTime, usage.startTime)}</span>
97+
<span className="text-gray-700">
98+
{getHours(new Date(usage.endTime).getTime(), new Date(usage.startTime).getTime())}
99+
</span>
96100
</div>
97101
<div className="my-auto">
98102
<span className="text-gray-700">{usage.credits}</span>

components/gitpod-protocol/src/usage.ts

+3-110
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,10 @@ export interface BillableSession {
2424
workspaceClass: string;
2525

2626
// When the workspace started
27-
startTime: number;
27+
startTime: string;
2828

2929
// When the workspace ended
30-
endTime: number;
30+
endTime: string;
3131

3232
// The credits used for this session
3333
credits: number;
@@ -36,111 +36,4 @@ export interface BillableSession {
3636
projectId?: string;
3737
}
3838

39-
export type BillableWorkspaceType = Omit<WorkspaceType, "probe">;
40-
41-
export const billableSessionDummyData: BillableSession[] = [
42-
{
43-
attributionId: "some-attribution-id",
44-
userId: "prebuild",
45-
teamId: "prebuild",
46-
instanceId: "some-instance-id",
47-
workspaceId: "some-workspace-id",
48-
workspaceType: "prebuild",
49-
workspaceClass: "XL",
50-
startTime: Date.now() + -3 * 24 * 3600 * 1000, // 3 days ago
51-
endTime: Date.now(),
52-
credits: 320,
53-
projectId: "project-123",
54-
},
55-
{
56-
attributionId: "some-attribution-id2",
57-
userId: "some-user",
58-
teamId: "some-team",
59-
instanceId: "some-instance-id2",
60-
workspaceId: "some-workspace-id2",
61-
workspaceType: "regular",
62-
workspaceClass: "standard",
63-
startTime: Date.now() + -5 * 24 * 3600 * 1000,
64-
endTime: Date.now(),
65-
credits: 130,
66-
projectId: "project-123",
67-
},
68-
{
69-
attributionId: "some-attribution-id3",
70-
userId: "some-other-user",
71-
teamId: "some-other-team",
72-
instanceId: "some-instance-id3",
73-
workspaceId: "some-workspace-id3",
74-
workspaceType: "regular",
75-
workspaceClass: "XL",
76-
startTime: Date.now() + -5 * 24 * 3600 * 1000,
77-
endTime: Date.now() + -4 * 24 * 3600 * 1000,
78-
credits: 150,
79-
projectId: "project-134",
80-
},
81-
{
82-
attributionId: "some-attribution-id4",
83-
userId: "some-other-user2",
84-
teamId: "some-other-team2",
85-
instanceId: "some-instance-id4",
86-
workspaceId: "some-workspace-id4",
87-
workspaceType: "regular",
88-
workspaceClass: "standard",
89-
startTime: Date.now() + -10 * 24 * 3600 * 1000,
90-
endTime: Date.now() + -9 * 24 * 3600 * 1000,
91-
credits: 330,
92-
projectId: "project-137",
93-
},
94-
{
95-
attributionId: "some-attribution-id5",
96-
userId: "some-other-user3",
97-
teamId: "some-other-team3",
98-
instanceId: "some-instance-id5",
99-
workspaceId: "some-workspace-id5",
100-
workspaceType: "regular",
101-
workspaceClass: "XL",
102-
startTime: Date.now() + -2 * 24 * 3600 * 1000,
103-
endTime: Date.now(),
104-
credits: 222,
105-
projectId: "project-138",
106-
},
107-
{
108-
attributionId: "some-attribution-id6",
109-
userId: "some-other-user4",
110-
teamId: "some-other-team4",
111-
instanceId: "some-instance-id6",
112-
workspaceId: "some-workspace-id3",
113-
workspaceType: "regular",
114-
workspaceClass: "XL",
115-
startTime: Date.now() + -7 * 24 * 3600 * 1000,
116-
endTime: Date.now() + -6 * 24 * 3600 * 1000,
117-
credits: 300,
118-
projectId: "project-134",
119-
},
120-
{
121-
attributionId: "some-attribution-id8",
122-
userId: "some-other-user5",
123-
teamId: "some-other-team5",
124-
instanceId: "some-instance-id8",
125-
workspaceId: "some-workspace-id3",
126-
workspaceType: "regular",
127-
workspaceClass: "standard",
128-
startTime: Date.now() + -1 * 24 * 3600 * 1000,
129-
endTime: Date.now(),
130-
credits: 100,
131-
projectId: "project-567",
132-
},
133-
{
134-
attributionId: "some-attribution-id7",
135-
userId: "prebuild",
136-
teamId: "some-other-team7",
137-
instanceId: "some-instance-id7",
138-
workspaceId: "some-workspace-id7",
139-
workspaceType: "prebuild",
140-
workspaceClass: "XL",
141-
startTime: Date.now() + -1 * 24 * 3600 * 1000,
142-
endTime: Date.now(),
143-
credits: 200,
144-
projectId: "project-345",
145-
},
146-
];
39+
export type BillableWorkspaceType = WorkspaceType;

components/server/BUILD.yaml

+3
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ packages:
1717
- components/licensor/typescript:lib
1818
- components/ws-manager-api/typescript:lib
1919
- components/supervisor-api/typescript-grpcweb:lib
20+
- components/usage-api/typescript:lib
2021
config:
2122
packaging: offline-mirror
2223
yarnLock: ${coreYarnLockBase}/yarn.lock
@@ -55,6 +56,7 @@ packages:
5556
- components/licensor/typescript:lib
5657
- components/ws-manager-api/typescript:lib
5758
- components/supervisor-api/typescript-grpcweb:lib
59+
- components/usage-api/typescript:lib
5860
- :dbtest
5961
config:
6062
packaging: library
@@ -80,6 +82,7 @@ packages:
8082
- components/licensor/typescript:lib
8183
- components/ws-manager-api/typescript:lib
8284
- components/supervisor-api/typescript-grpcweb:lib
85+
- components/usage-api/typescript:lib
8386
config:
8487
packaging: library
8588
yarnLock: ${coreYarnLockBase}/yarn.lock

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

+35-2
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ import {
4646
FindPrebuildsParams,
4747
TeamMemberRole,
4848
WORKSPACE_TIMEOUT_DEFAULT_SHORT,
49+
WorkspaceType,
4950
} from "@gitpod/gitpod-protocol";
5051
import { ResponseError } from "vscode-jsonrpc";
5152
import {
@@ -70,7 +71,7 @@ import { BlockedRepository } from "@gitpod/gitpod-protocol/lib/blocked-repositor
7071
import { EligibilityService } from "../user/eligibility-service";
7172
import { AccountStatementProvider } from "../user/account-statement-provider";
7273
import { GithubUpgradeURL, PlanCoupon } from "@gitpod/gitpod-protocol/lib/payment-protocol";
73-
import { BillableSession, billableSessionDummyData } from "@gitpod/gitpod-protocol/lib/usage";
74+
import { BillableSession } from "@gitpod/gitpod-protocol/lib/usage";
7475
import {
7576
AssigneeIdentityIdentifier,
7677
TeamSubscription,
@@ -106,6 +107,8 @@ import { URL } from "url";
106107
import { UserCounter } from "../user/user-counter";
107108
import { getExperimentsClientForBackend } from "@gitpod/gitpod-protocol/lib/experiments/configcat-server";
108109
import { AttributionId } from "@gitpod/gitpod-protocol/lib/attribution";
110+
import { CachingUsageServiceClientProvider } from "@gitpod/usage-api/lib/usage/v1/sugar";
111+
import * as usage from "@gitpod/usage-api/lib/usage/v1/usage_pb";
109112

110113
@injectable()
111114
export class GitpodServerEEImpl extends GitpodServerImpl {
@@ -145,6 +148,9 @@ export class GitpodServerEEImpl extends GitpodServerImpl {
145148

146149
@inject(UserService) protected readonly userService: UserService;
147150

151+
@inject(CachingUsageServiceClientProvider)
152+
protected readonly usageServiceClientProvider: CachingUsageServiceClientProvider;
153+
148154
initialize(
149155
client: GitpodClient | undefined,
150156
user: User | undefined,
@@ -2064,7 +2070,11 @@ export class GitpodServerEEImpl extends GitpodServerImpl {
20642070

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

2067-
return billableSessionDummyData;
2073+
const usageClient = this.usageServiceClientProvider.getDefault();
2074+
const response = await usageClient.getBilledUsage(ctx, attributionId);
2075+
const sessions = response.getSessionsList().map((s) => this.mapBilledSession(s));
2076+
2077+
return sessions;
20682078
}
20692079

20702080
protected async guardCostCenterAccess(
@@ -2102,6 +2112,29 @@ export class GitpodServerEEImpl extends GitpodServerImpl {
21022112

21032113
await this.guardAccess({ kind: "costCenter", /*subject: costCenter,*/ owner }, operation);
21042114
}
2115+
2116+
protected mapBilledSession(s: usage.BilledSession): BillableSession {
2117+
function mandatory<T>(s: T, m: (s: T) => string = (s) => "" + s): string {
2118+
if (!s) {
2119+
throw new Error("empty value in usage.BilledSession");
2120+
}
2121+
return m(s);
2122+
}
2123+
return {
2124+
attributionId: mandatory(s.getAttributionId()),
2125+
userId: s.getUserId() || undefined,
2126+
teamId: s.getTeamId() || undefined,
2127+
projectId: s.getProjectId() || undefined,
2128+
workspaceId: mandatory(s.getWorkspaceId()),
2129+
instanceId: mandatory(s.getInstanceId()),
2130+
workspaceType: mandatory(s.getWorkspaceType()) as WorkspaceType,
2131+
workspaceClass: mandatory(s.getWorkspaceClass()),
2132+
startTime: mandatory(s.getStartTime(), (t) => t!.toDate().toISOString()),
2133+
endTime: mandatory(s.getEndTime(), (t) => t!.toDate().toISOString()),
2134+
credits: s.getCredits(), // optional
2135+
};
2136+
}
2137+
21052138
// (SaaS) – admin
21062139
async adminGetAccountStatement(ctx: TraceContext, userId: string): Promise<AccountStatement> {
21072140
traceAPIParams(ctx, { userId });

components/server/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
"@gitpod/image-builder": "0.1.5",
3737
"@gitpod/licensor": "0.1.5",
3838
"@gitpod/supervisor-api-grpcweb": "0.1.5",
39+
"@gitpod/usage-api": "0.1.5",
3940
"@gitpod/ws-manager": "0.1.5",
4041
"@google-cloud/storage": "^5.6.0",
4142
"@improbable-eng/grpc-web-node-http-transport": "^0.14.0",

components/server/src/config.ts

+6
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,12 @@ export interface ConfigSerialized {
149149
*/
150150
imageBuilderAddr: string;
151151

152+
/**
153+
* The address usage service clients connect to
154+
* Example: usage:8080
155+
*/
156+
usageServiceAddr: string;
157+
152158
codeSync: CodeSyncConfig;
153159

154160
vsxRegistryUrl: string;

components/server/src/container-module.ts

+14
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,12 @@ import { InstallationAdminTelemetryDataProvider } from "./installation-admin/tel
100100
import { IDEService } from "./ide-service";
101101
import { LicenseEvaluator } from "@gitpod/licensor/lib";
102102
import { WorkspaceClusterImagebuilderClientProvider } from "./workspace/workspace-cluster-imagebuilder-client-provider";
103+
import {
104+
CachingUsageServiceClientProvider,
105+
UsageServiceClientCallMetrics,
106+
UsageServiceClientConfig,
107+
UsageServiceClientProvider,
108+
} from "@gitpod/usage-api/lib/usage/v1/sugar";
103109

104110
export const productionContainerModule = new ContainerModule((bind, unbind, isBound, rebind) => {
105111
bind(Config).toConstantValue(ConfigFile.fromFile());
@@ -247,4 +253,12 @@ export const productionContainerModule = new ContainerModule((bind, unbind, isBo
247253
bind(ProjectsService).toSelf().inSingletonScope();
248254

249255
bind(NewsletterSubscriptionController).toSelf().inSingletonScope();
256+
257+
bind(UsageServiceClientConfig).toDynamicValue((ctx) => {
258+
const config = ctx.container.get<Config>(Config);
259+
return { address: config.usageServiceAddr };
260+
});
261+
bind(CachingUsageServiceClientProvider).toSelf().inSingletonScope();
262+
bind(UsageServiceClientProvider).toService(CachingImageBuilderClientProvider);
263+
bind(UsageServiceClientCallMetrics).toService(IClientCallMetrics);
250264
});

install/installer/pkg/components/server/configmap.go

+2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"regexp"
1010

1111
"github.com/gitpod-io/gitpod/installer/pkg/common"
12+
"github.com/gitpod-io/gitpod/installer/pkg/components/usage"
1213
"github.com/gitpod-io/gitpod/installer/pkg/components/workspace"
1314
"github.com/gitpod-io/gitpod/installer/pkg/config/v1/experimental"
1415
corev1 "k8s.io/api/core/v1"
@@ -222,6 +223,7 @@ func configmap(ctx *common.RenderContext) ([]runtime.Object, error) {
222223
},
223224
ContentServiceAddr: "content-service:8080",
224225
ImageBuilderAddr: "image-builder-mk3:8080",
226+
UsageServiceAddr: fmt.Sprintf("%s:%d", usage.Component, usage.GRPCServicePort),
225227
CodeSync: CodeSync{},
226228
VSXRegistryUrl: fmt.Sprintf("https://open-vsx.%s", ctx.Config.Domain), // todo(sje): or "https://{{ .Values.vsxRegistry.host | default "open-vsx.org" }}" if not using OpenVSX proxy
227229
EnablePayment: chargebeeSecret != "" || stripeSecret != "" || stripeConfig != "",

install/installer/pkg/components/server/types.go

+1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ type ConfigSerialized struct {
3030
RunDbDeleter bool `json:"runDbDeleter"`
3131
ContentServiceAddr string `json:"contentServiceAddr"`
3232
ImageBuilderAddr string `json:"imageBuilderAddr"`
33+
UsageServiceAddr string `json:"usageServiceAddr"`
3334
VSXRegistryUrl string `json:"vsxRegistryUrl"`
3435
ChargebeeProviderOptionsFile string `json:"chargebeeProviderOptionsFile"`
3536
StripeSecretsFile string `json:"stripeSecretsFile"`

install/installer/pkg/components/usage/constants.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ const (
88
Component = "usage"
99
gRPCContainerPort = 9001
1010
gRPCPortName = "grpc"
11-
gRPCServicePort = 9001
11+
GRPCServicePort = 9001
1212
stripeSecretMountPath = "stripe-secret"
1313
stripeKeyFilename = "apikeys"
1414
configJSONFilename = "config.json"

install/installer/pkg/components/usage/service.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ func service(ctx *common.RenderContext) ([]runtime.Object, error) {
1313
{
1414
Name: gRPCPortName,
1515
ContainerPort: gRPCContainerPort,
16-
ServicePort: gRPCServicePort,
16+
ServicePort: GRPCServicePort,
1717
},
1818
})(ctx)
1919
}

0 commit comments

Comments
 (0)