@gitpod/gitpod-protocol 0.1.5-sje-kots-node-reqs.1 → 0.1.5-sje-installer-proxy-config-backup.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/data/gitpod-schema.json +44 -129
  2. package/lib/accounting-protocol.d.ts +1 -0
  3. package/lib/accounting-protocol.d.ts.map +1 -1
  4. package/lib/accounting-protocol.js +4 -0
  5. package/lib/accounting-protocol.js.map +1 -1
  6. package/lib/admin-protocol.d.ts +4 -1
  7. package/lib/admin-protocol.d.ts.map +1 -1
  8. package/lib/admin-protocol.js.map +1 -1
  9. package/lib/billing-mode.d.ts +40 -0
  10. package/lib/billing-mode.d.ts.map +1 -0
  11. package/lib/billing-mode.js +36 -0
  12. package/lib/billing-mode.js.map +1 -0
  13. package/lib/experiments/configcat-server.d.ts +2 -0
  14. package/lib/experiments/configcat-server.d.ts.map +1 -1
  15. package/lib/experiments/configcat-server.js +2 -1
  16. package/lib/experiments/configcat-server.js.map +1 -1
  17. package/lib/gitpod-service.d.ts +19 -4
  18. package/lib/gitpod-service.d.ts.map +1 -1
  19. package/lib/gitpod-service.js.map +1 -1
  20. package/lib/ide-protocol.d.ts +8 -0
  21. package/lib/ide-protocol.d.ts.map +1 -1
  22. package/lib/messaging/client-call-metrics.d.ts +1 -17
  23. package/lib/messaging/client-call-metrics.d.ts.map +1 -1
  24. package/lib/messaging/client-call-metrics.js +1 -2
  25. package/lib/messaging/client-call-metrics.js.map +1 -1
  26. package/lib/messaging/error.d.ts +4 -1
  27. package/lib/messaging/error.d.ts.map +1 -1
  28. package/lib/messaging/error.js +9 -3
  29. package/lib/messaging/error.js.map +1 -1
  30. package/lib/plans.d.ts +1 -0
  31. package/lib/plans.d.ts.map +1 -1
  32. package/lib/plans.js +4 -0
  33. package/lib/plans.js.map +1 -1
  34. package/lib/protocol.d.ts +17 -2
  35. package/lib/protocol.d.ts.map +1 -1
  36. package/lib/protocol.js +15 -2
  37. package/lib/protocol.js.map +1 -1
  38. package/lib/team-subscription-protocol.d.ts +1 -0
  39. package/lib/team-subscription-protocol.d.ts.map +1 -1
  40. package/lib/team-subscription-protocol.js +4 -0
  41. package/lib/team-subscription-protocol.js.map +1 -1
  42. package/lib/usage.d.ts +50 -10
  43. package/lib/usage.d.ts.map +1 -1
  44. package/lib/usage.js +6 -107
  45. package/lib/usage.js.map +1 -1
  46. package/lib/util/garbage-collected-cache.d.ts.map +1 -1
  47. package/lib/util/garbage-collected-cache.js +5 -0
  48. package/lib/util/garbage-collected-cache.js.map +1 -1
  49. package/lib/util/gitpod-host-url.d.ts +3 -0
  50. package/lib/util/gitpod-host-url.d.ts.map +1 -1
  51. package/lib/util/gitpod-host-url.js +18 -0
  52. package/lib/util/gitpod-host-url.js.map +1 -1
  53. package/lib/util/grpc.d.ts +19 -0
  54. package/lib/util/grpc.d.ts.map +1 -1
  55. package/lib/util/grpc.js +73 -1
  56. package/lib/util/grpc.js.map +1 -1
  57. package/lib/util/logging.spec.d.ts +7 -0
  58. package/lib/util/logging.spec.d.ts.map +1 -0
  59. package/lib/util/logging.spec.js +52 -0
  60. package/lib/util/logging.spec.js.map +1 -0
  61. package/lib/util/timeutil.d.ts +1 -0
  62. package/lib/util/timeutil.d.ts.map +1 -1
  63. package/lib/util/timeutil.js +7 -1
  64. package/lib/util/timeutil.js.map +1 -1
  65. package/lib/util/timeutil.spec.d.ts +1 -0
  66. package/lib/util/timeutil.spec.d.ts.map +1 -1
  67. package/lib/util/timeutil.spec.js +10 -0
  68. package/lib/util/timeutil.spec.js.map +1 -1
  69. package/lib/workspace-class.d.ts +14 -0
  70. package/lib/workspace-class.d.ts.map +1 -0
  71. package/lib/workspace-class.js +8 -0
  72. package/lib/workspace-class.js.map +1 -0
  73. package/lib/workspace-instance.d.ts +1 -0
  74. package/lib/workspace-instance.d.ts.map +1 -1
  75. package/lib/wsready.d.ts +3 -3
  76. package/lib/wsready.d.ts.map +1 -1
  77. package/lib/wsready.js +0 -2
  78. package/lib/wsready.js.map +1 -1
  79. package/package.json +5 -2
  80. package/pkg-yarn.lock +3 -2
  81. package/provenance-bundle.jsonl +1 -1
  82. package/src/accounting-protocol.ts +3 -0
  83. package/src/admin-protocol.ts +4 -1
  84. package/src/billing-mode.ts +67 -0
  85. package/src/experiments/configcat-server.ts +3 -0
  86. package/src/gitpod-service.ts +23 -4
  87. package/src/ide-protocol.ts +10 -0
  88. package/src/messaging/client-call-metrics.ts +1 -20
  89. package/src/messaging/error.ts +13 -4
  90. package/src/plans.ts +4 -0
  91. package/src/protocol.ts +36 -4
  92. package/src/team-subscription-protocol.ts +3 -0
  93. package/src/usage.ts +54 -128
  94. package/src/util/garbage-collected-cache.ts +5 -0
  95. package/src/util/gitpod-host-url.ts +21 -0
  96. package/src/util/grpc.ts +89 -0
  97. package/src/util/logging.spec.ts +23 -0
  98. package/src/util/timeutil.spec.ts +7 -1
  99. package/src/util/timeutil.ts +6 -0
  100. package/src/workspace-class.ts +14 -0
  101. package/src/workspace-instance.ts +3 -0
  102. package/src/wsready.ts +5 -4
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Copyright (c) 2022 Gitpod GmbH. All rights reserved.
3
+ * Licensed under the GNU Affero General Public License (AGPL).
4
+ * See License-AGPL.txt in the project root for license information.
5
+ */
6
+
7
+ /**
8
+ * BillingMode is used to answer the following questions:
9
+ * - Should UI piece x be displayed for this user/team? (getBillingModeForUser/Team)
10
+ * - What model should be used to limit this workspace's capabilities (mayStartWorkspace, setTimeout, workspace class, etc...) (getBillingMode(workspaceInstance.attributionId))
11
+ * - How is a workspace session charged for? (getBillingMode(workspaceInstance.attributionId))
12
+ */
13
+ export type BillingMode = None | Chargebee | UsageBased;
14
+ export namespace BillingMode {
15
+ export const NONE: None = {
16
+ mode: "none",
17
+ };
18
+
19
+ /** Incl. upgrade and status */
20
+ export function showUsageBasedBilling(billingMode?: BillingMode): boolean {
21
+ if (!billingMode) {
22
+ return false;
23
+ }
24
+ return (
25
+ billingMode.mode === "usage-based" || (billingMode.mode === "chargebee" && !!billingMode.canUpgradeToUBB)
26
+ );
27
+ }
28
+
29
+ export function canSetWorkspaceClass(billingMode?: BillingMode): boolean {
30
+ if (!billingMode) {
31
+ return false;
32
+ }
33
+
34
+ // if has any Stripe subscription, either directly or per team
35
+ return billingMode.mode === "usage-based";
36
+ }
37
+
38
+ export function canSetCostCenter(billingMode: BillingMode): boolean {
39
+ // if has any Stripe Subscription, either directly or per team
40
+ return billingMode.mode === "usage-based";
41
+ }
42
+ }
43
+
44
+ /** Payment is disabled */
45
+ interface None {
46
+ mode: "none";
47
+ }
48
+
49
+ /** Sessions is handled with old subscription logic based on Chargebee */
50
+ interface Chargebee {
51
+ mode: "chargebee";
52
+ canUpgradeToUBB?: boolean;
53
+ }
54
+
55
+ /** Session is handld with new usage-based logic */
56
+ interface UsageBased {
57
+ mode: "usage-based";
58
+
59
+ /** True iff this is a team, and is based on a paid plan. Currently only set for teams! */
60
+ paid?: boolean;
61
+
62
+ /** User is already converted, but is member with at least one Chargebee-based "Team Plan" */
63
+ hasChargebeeTeamPlan?: boolean;
64
+
65
+ /** User is already converted, but is member in at least one Chargebee-based "Team Subscription" */
66
+ hasChargebeeTeamSubscription?: boolean;
67
+ }
@@ -12,6 +12,9 @@ import { newAlwaysReturningDefaultValueClient } from "./always-default";
12
12
 
13
13
  let client: Client | undefined;
14
14
 
15
+ export type ConfigCatClientFactory = () => Client;
16
+ export const ConfigCatClientFactory = Symbol("ConfigCatClientFactory");
17
+
15
18
  export function getExperimentsClientForBackend(): Client {
16
19
  // We have already instantiated a client, we can just re-use it.
17
20
  if (client !== undefined) {
@@ -60,8 +60,9 @@ import {
60
60
  import { RemotePageMessage, RemoteTrackMessage, RemoteIdentifyMessage } from "./analytics";
61
61
  import { IDEServer } from "./ide-protocol";
62
62
  import { InstallationAdminSettings, TelemetryData } from "./installation-admin-protocol";
63
- import { Currency } from "./plans";
64
- import { BillableSession } from "./usage";
63
+ import { ListUsageRequest, ListUsageResponse } from "./usage";
64
+ import { SupportedWorkspaceClass } from "./workspace-class";
65
+ import { BillingMode } from "./billing-mode";
65
66
 
66
67
  export interface GitpodClient {
67
68
  onInstanceUpdate(instance: WorkspaceInstance): void;
@@ -83,6 +84,8 @@ export interface GitpodServer extends JsonRpcServer<GitpodClient>, AdminServer,
83
84
  getLoggedInUser(): Promise<User>;
84
85
  getTerms(): Promise<Terms>;
85
86
  updateLoggedInUser(user: Partial<User>): Promise<User>;
87
+ sendPhoneNumberVerificationToken(phoneNumber: string): Promise<void>;
88
+ verifyPhoneNumberVerificationToken(phoneNumber: string, token: string): Promise<boolean>;
86
89
  getAuthProviders(): Promise<AuthProviderInfo[]>;
87
90
  getOwnAuthProviders(): Promise<AuthProviderEntry[]>;
88
91
  updateOwnAuthProvider(params: GitpodServer.UpdateOwnAuthProviderParams): Promise<AuthProviderEntry>;
@@ -288,10 +291,19 @@ export interface GitpodServer extends JsonRpcServer<GitpodClient>, AdminServer,
288
291
  getStripePublishableKey(): Promise<string>;
289
292
  getStripeSetupIntentClientSecret(): Promise<string>;
290
293
  findStripeSubscriptionIdForTeam(teamId: string): Promise<string | undefined>;
291
- subscribeTeamToStripe(teamId: string, setupIntentId: string, currency: Currency): Promise<void>;
294
+ createOrUpdateStripeCustomerForTeam(teamId: string, currency: string): Promise<void>;
295
+ createOrUpdateStripeCustomerForUser(currency: string): Promise<void>;
296
+ subscribeTeamToStripe(teamId: string, setupIntentId: string): Promise<void>;
292
297
  getStripePortalUrlForTeam(teamId: string): Promise<string>;
298
+ getUsageLimitForTeam(teamId: string): Promise<number | undefined>;
299
+ setUsageLimitForTeam(teamId: string, spendingLimit: number): Promise<void>;
293
300
 
294
- getBilledUsage(attributionId: string): Promise<BillableSession[]>;
301
+ listUsage(req: ListUsageRequest): Promise<ListUsageResponse>;
302
+
303
+ setUsageAttribution(usageAttribution: string): Promise<void>;
304
+
305
+ getBillingModeForUser(): Promise<BillingMode>;
306
+ getBillingModeForTeam(teamId: string): Promise<BillingMode>;
295
307
 
296
308
  /**
297
309
  * Analytics
@@ -299,6 +311,13 @@ export interface GitpodServer extends JsonRpcServer<GitpodClient>, AdminServer,
299
311
  trackEvent(event: RemoteTrackMessage): Promise<void>;
300
312
  trackLocation(event: RemotePageMessage): Promise<void>;
301
313
  identifyUser(event: RemoteIdentifyMessage): Promise<void>;
314
+
315
+ /**
316
+ * Frontend notifications
317
+ */
318
+ getNotifications(): Promise<string[]>;
319
+
320
+ getSupportedWorkspaceClasses(): Promise<SupportedWorkspaceClass[]>;
302
321
  }
303
322
 
304
323
  export interface RateLimiterError {
@@ -116,4 +116,14 @@ export interface IDEOption {
116
116
  * we resolve the tag regularly to the most recent image version.
117
117
  */
118
118
  resolveImageDigest?: boolean;
119
+
120
+ /**
121
+ * The plugin image ref for the IDE image, this image ref always resolve to digest.
122
+ */
123
+ pluginImage?: string;
124
+
125
+ /**
126
+ * The latest plugin image ref for the latest IDE image, this image ref always resolve to digest.
127
+ */
128
+ pluginLatestImage?: string;
119
129
  }
@@ -6,26 +6,7 @@
6
6
 
7
7
  import { injectable } from "inversify";
8
8
  import * as prometheusClient from "prom-client";
9
-
10
- type GrpcMethodType = "unary" | "client_stream" | "server_stream" | "bidi_stream";
11
- export interface IGrpcCallMetricsLabels {
12
- service: string;
13
- method: string;
14
- type: GrpcMethodType;
15
- }
16
-
17
- export interface IGrpcCallMetricsLabelsWithCode extends IGrpcCallMetricsLabels {
18
- code: string;
19
- }
20
-
21
- export const IClientCallMetrics = Symbol("IClientCallMetrics");
22
-
23
- export interface IClientCallMetrics {
24
- started(labels: IGrpcCallMetricsLabels): void;
25
- sent(labels: IGrpcCallMetricsLabels): void;
26
- received(labels: IGrpcCallMetricsLabels): void;
27
- handled(labels: IGrpcCallMetricsLabelsWithCode): void;
28
- }
9
+ import { IClientCallMetrics, IGrpcCallMetricsLabels, IGrpcCallMetricsLabelsWithCode } from "../util/grpc";
29
10
 
30
11
  @injectable()
31
12
  export class PrometheusClientCallMetrics implements IClientCallMetrics {
@@ -26,21 +26,30 @@ export namespace ErrorCodes {
26
26
  // 410 No User
27
27
  export const SETUP_REQUIRED = 410;
28
28
 
29
+ // 411 No User
30
+ export const NEEDS_VERIFICATION = 411;
31
+
29
32
  // 429 Too Many Requests
30
33
  export const TOO_MANY_REQUESTS = 429;
31
34
 
32
35
  // 430 Repository not whitelisted (custom status code)
33
36
  export const REPOSITORY_NOT_WHITELISTED = 430;
34
37
 
38
+ // 450 Payment error
39
+ export const PAYMENT_ERROR = 450;
40
+
41
+ // 451 Out of credits
42
+ export const PAYMENT_SPENDING_LIMIT_REACHED = 451;
43
+
44
+ // 455 Invalid cost center (custom status code)
45
+ export const INVALID_COST_CENTER = 455;
46
+
35
47
  // 460 Context Parse Error (custom status code)
36
48
  export const CONTEXT_PARSE_ERROR = 460;
37
49
 
38
- // 461 Invalid gitpod yml
50
+ // 461 Invalid gitpod yml (custom status code)
39
51
  export const INVALID_GITPOD_YML = 461;
40
52
 
41
- // 450 Payment error
42
- export const PAYMENT_ERROR = 450;
43
-
44
53
  // 470 User Blocked (custom status code)
45
54
  export const USER_BLOCKED = 470;
46
55
 
package/src/plans.ts CHANGED
@@ -482,6 +482,10 @@ export namespace Plans {
482
482
  );
483
483
  }
484
484
 
485
+ export function isFreeTier(chargebeeId: string | undefined): boolean {
486
+ return chargebeeId === Plans.FREE.chargebeeId || chargebeeId === Plans.FREE_50.chargebeeId;
487
+ }
488
+
485
489
  export function isFreeNonTransientPlan(chargebeeId: string | undefined): boolean {
486
490
  return chargebeeId === Plans.FREE_OPEN_SOURCE.chargebeeId;
487
491
  }
package/src/protocol.ts CHANGED
@@ -8,6 +8,7 @@ import { WorkspaceInstance, PortVisibility } from "./workspace-instance";
8
8
  import { RoleOrPermission } from "./permission";
9
9
  import { Project } from "./teams-projects-protocol";
10
10
  import { createHash } from "crypto";
11
+ import { AttributionId } from "./attribution";
11
12
 
12
13
  export interface UserInfo {
13
14
  name?: string;
@@ -45,6 +46,15 @@ export interface User {
45
46
  markedDeleted?: boolean;
46
47
 
47
48
  additionalData?: AdditionalUserData;
49
+
50
+ // Identifies an explicit team or user ID to which all the user's workspace usage should be attributed to (e.g. for billing purposes)
51
+ usageAttributionId?: string;
52
+
53
+ // The last time this user got verified somehow. The user is not verified if this is empty.
54
+ lastVerificationTime?: string;
55
+
56
+ // The phone number used for the last phone verification.
57
+ verificationPhoneNumber?: string;
48
58
  }
49
59
 
50
60
  export namespace User {
@@ -199,14 +209,26 @@ export interface AdditionalUserData {
199
209
  knownGitHubOrgs?: string[];
200
210
  // Git clone URL pointing to the user's dotfile repo
201
211
  dotfileRepo?: string;
202
- // Identifies an explicit team or user ID to which all the user's workspace usage should be attributed to (e.g. for billing purposes)
203
- usageAttributionId?: string;
204
212
  // preferred workspace classes
205
213
  workspaceClasses?: WorkspaceClasses;
206
214
  // additional user profile data
207
215
  profile?: ProfileDetails;
208
216
  }
209
-
217
+ export namespace AdditionalUserData {
218
+ export function set(user: User, partialData: Partial<AdditionalUserData>): User {
219
+ if (!user.additionalData) {
220
+ user.additionalData = {
221
+ ...partialData,
222
+ };
223
+ } else {
224
+ user.additionalData = {
225
+ ...user.additionalData,
226
+ ...partialData,
227
+ };
228
+ }
229
+ return user;
230
+ }
231
+ }
210
232
  // The format in which we store User Profiles in
211
233
  export interface ProfileDetails {
212
234
  // when was the last time the user updated their profile information or has been nudged to do so.
@@ -270,9 +292,9 @@ export interface UserFeatureSettings {
270
292
  */
271
293
  export const WorkspaceFeatureFlags = {
272
294
  full_workspace_backup: undefined,
273
- fixed_resources: undefined,
274
295
  persistent_volume_claim: undefined,
275
296
  protected_secrets: undefined,
297
+ workspace_class_limiting: undefined,
276
298
  };
277
299
  export type NamedWorkspaceFeatureFlag = keyof typeof WorkspaceFeatureFlags;
278
300
 
@@ -1481,3 +1503,13 @@ export interface Terms {
1481
1503
  readonly content: string;
1482
1504
  readonly formElements?: object;
1483
1505
  }
1506
+
1507
+ export type BillingStrategy = "other" | "stripe";
1508
+ export interface CostCenter {
1509
+ readonly id: AttributionId;
1510
+ /**
1511
+ * Unit: credits
1512
+ */
1513
+ spendingLimit: number;
1514
+ billingStrategy: BillingStrategy;
1515
+ }
@@ -55,6 +55,9 @@ export namespace TeamSubscription2 {
55
55
  export const isActive = (ts2: TeamSubscription2, date: string): boolean => {
56
56
  return ts2.startDate <= date && (ts2.endDate === undefined || date < ts2.endDate);
57
57
  };
58
+ export function isCancelled(s: TeamSubscription2, date: string): boolean {
59
+ return (!!s.cancellationDate && s.cancellationDate < date) || (!!s.endDate && s.endDate < date); // This edge case is meant to handle bad data: If for whatever reason cancellationDate has not been set: treat endDate as such
60
+ }
58
61
  }
59
62
 
60
63
  /**
package/src/usage.ts CHANGED
@@ -6,141 +6,67 @@
6
6
 
7
7
  import { WorkspaceType } from "./protocol";
8
8
 
9
- export interface BillableSession {
10
- // The id of the one paying the bill
9
+ // types below are copied over from components/usage-api/typescript/src/usage/v1/usage_pb.d.ts
10
+ export interface ListUsageRequest {
11
11
  attributionId: string;
12
+ from?: number;
13
+ to?: number;
14
+ order: Ordering;
15
+ pagination?: PaginationRequest;
16
+ }
12
17
 
13
- // Relevant for workspace type. When prebuild, shows "prebuild"
14
- userId?: string;
15
- teamId?: string;
16
-
17
- instanceId: string;
18
-
19
- workspaceId: string;
20
-
21
- workspaceType: BillableWorkspaceType;
18
+ export enum Ordering {
19
+ ORDERING_DESCENDING = 0,
20
+ ORDERING_ASCENDING = 1,
21
+ }
22
22
 
23
- // "standard" or "XL"
24
- workspaceClass: string;
23
+ export interface PaginationRequest {
24
+ perPage: number;
25
+ page: number;
26
+ }
25
27
 
26
- // When the workspace started
27
- startTime: string;
28
+ export interface ListUsageResponse {
29
+ usageEntriesList: Usage[];
30
+ pagination?: PaginationResponse;
31
+ creditBalanceAtStart: number;
32
+ creditBalanceAtEnd: number;
33
+ }
28
34
 
29
- // When the workspace ended
30
- endTime: string;
35
+ export interface PaginationResponse {
36
+ perPage: number;
37
+ totalPages: number;
38
+ total: number;
39
+ page: number;
40
+ }
31
41
 
32
- // The credits used for this session
42
+ export type UsageKind = "workspaceinstance" | "invoice";
43
+ export interface Usage {
44
+ id: string;
45
+ attributionId: string;
46
+ description: string;
33
47
  credits: number;
34
-
35
- // TODO - maybe
36
- projectId?: string;
48
+ effectiveTime?: number;
49
+ kind: UsageKind;
50
+ workspaceInstanceId: string;
51
+ draft: boolean;
52
+ metadata: WorkspaceInstanceUsageData | InvoiceUsageData;
37
53
  }
38
54
 
39
- export type BillableWorkspaceType = WorkspaceType;
55
+ // the equivalent golang shape is maintained in `/workspace/gitpod/`components/usage/pkg/db/usage.go`
56
+ export interface WorkspaceInstanceUsageData {
57
+ workspaceId: string;
58
+ workspaceType: WorkspaceType;
59
+ workspaceClass: string;
60
+ contextURL: string;
61
+ startTime: string;
62
+ endTime?: string;
63
+ userId: string;
64
+ userName: string;
65
+ userAvatarURL: string;
66
+ }
40
67
 
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: new Date(Date.now() + -3 * 24 * 3600 * 1000).toISOString(), // 3 days ago
51
- endTime: new Date().toISOString(),
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: new Date(Date.now() + -5 * 24 * 3600 * 1000).toISOString(),
64
- endTime: new Date().toISOString(),
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: new Date(Date.now() + -5 * 24 * 3600 * 1000).toISOString(),
77
- endTime: new Date(Date.now() + -4 * 24 * 3600 * 1000).toISOString(),
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: new Date(Date.now() + -10 * 24 * 3600 * 1000).toISOString(),
90
- endTime: new Date(Date.now() + -9 * 24 * 3600 * 1000).toISOString(),
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: new Date(Date.now() + -2 * 24 * 3600 * 1000).toISOString(),
103
- endTime: new Date().toISOString(),
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: new Date(Date.now() + -7 * 24 * 3600 * 1000).toISOString(),
116
- endTime: new Date(Date.now() + -6 * 24 * 3600 * 1000).toISOString(),
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: new Date(Date.now() + -1 * 24 * 3600 * 1000).toISOString(),
129
- endTime: new Date().toISOString(),
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: new Date(Date.now() + -1 * 24 * 3600 * 1000).toISOString(),
142
- endTime: new Date().toISOString(),
143
- credits: 200,
144
- projectId: "project-345",
145
- },
146
- ];
68
+ export interface InvoiceUsageData {
69
+ invoiceId: string;
70
+ startDate: string;
71
+ endDate: string;
72
+ }
@@ -37,6 +37,11 @@ export class GarbageCollectedCache<T> {
37
37
  if (!entry) {
38
38
  return undefined;
39
39
  }
40
+ // Still valid?
41
+ if (entry.expiryDate < Date.now()) {
42
+ this.store.delete(entry.key);
43
+ return undefined;
44
+ }
40
45
  return entry.value;
41
46
  }
42
47
 
@@ -85,6 +85,10 @@ export class GitpodHostUrl {
85
85
  return this.with((url) => ({ protocol: url.protocol === "https:" ? "wss:" : "ws:" }));
86
86
  }
87
87
 
88
+ asWorkspacePage(): GitpodHostUrl {
89
+ return this.with((url) => ({ pathname: "/workspaces" }));
90
+ }
91
+
88
92
  asDashboard(): GitpodHostUrl {
89
93
  return this.with((url) => ({ pathname: "/" }));
90
94
  }
@@ -171,4 +175,21 @@ export class GitpodHostUrl {
171
175
  asApiLogout(): GitpodHostUrl {
172
176
  return this.withApi((url) => ({ pathname: "/logout/" }));
173
177
  }
178
+
179
+ asIDEProxy(): GitpodHostUrl {
180
+ const hostSegments = this.url.host.split(".");
181
+ if (hostSegments[0] === "ide") {
182
+ return this;
183
+ }
184
+ return this.with((url) => ({ host: "ide." + url.host }));
185
+ }
186
+
187
+ asIDEMetrics(): GitpodHostUrl {
188
+ let newUrl: GitpodHostUrl = this;
189
+ const hostSegments = this.url.host.split(".");
190
+ if (hostSegments[0] !== "ide") {
191
+ newUrl = newUrl.asIDEProxy();
192
+ }
193
+ return newUrl.with((url) => ({ pathname: "/metrics-api" }));
194
+ }
174
195
  }
package/src/util/grpc.ts CHANGED
@@ -4,6 +4,9 @@
4
4
  * See License-AGPL.txt in the project root for license information.
5
5
  */
6
6
 
7
+ import * as grpc from "@grpc/grpc-js";
8
+ import { Status } from "@grpc/grpc-js/build/src/constants";
9
+
7
10
  export const defaultGRPCOptions = {
8
11
  "grpc.keepalive_timeout_ms": 10000,
9
12
  "grpc.keepalive_time_ms": 60000,
@@ -13,3 +16,89 @@ export const defaultGRPCOptions = {
13
16
  "grpc.max_reconnect_backoff_ms": 5000,
14
17
  "grpc.max_receive_message_length": 1024 * 1024 * 16,
15
18
  };
19
+
20
+ export type GrpcMethodType = "unary" | "client_stream" | "server_stream" | "bidi_stream";
21
+
22
+ export interface IGrpcCallMetricsLabels {
23
+ service: string;
24
+ method: string;
25
+ type: GrpcMethodType;
26
+ }
27
+
28
+ export interface IGrpcCallMetricsLabelsWithCode extends IGrpcCallMetricsLabels {
29
+ code: string;
30
+ }
31
+
32
+ export const IClientCallMetrics = Symbol("IClientCallMetrics");
33
+
34
+ export interface IClientCallMetrics {
35
+ started(labels: IGrpcCallMetricsLabels): void;
36
+ sent(labels: IGrpcCallMetricsLabels): void;
37
+ received(labels: IGrpcCallMetricsLabels): void;
38
+ handled(labels: IGrpcCallMetricsLabelsWithCode): void;
39
+ }
40
+
41
+ export function getGrpcMethodType(requestStream: boolean, responseStream: boolean): GrpcMethodType {
42
+ if (requestStream) {
43
+ if (responseStream) {
44
+ return "bidi_stream";
45
+ } else {
46
+ return "client_stream";
47
+ }
48
+ } else {
49
+ if (responseStream) {
50
+ return "server_stream";
51
+ } else {
52
+ return "unary";
53
+ }
54
+ }
55
+ }
56
+
57
+ export function createClientCallMetricsInterceptor(metrics: IClientCallMetrics): grpc.Interceptor {
58
+ return (options, nextCall): grpc.InterceptingCall => {
59
+ const methodDef = options.method_definition;
60
+ const method = methodDef.path.substring(methodDef.path.lastIndexOf("/") + 1);
61
+ const service = methodDef.path.substring(1, methodDef.path.length - method.length - 1);
62
+ const labels = {
63
+ service,
64
+ method,
65
+ type: getGrpcMethodType(options.method_definition.requestStream, options.method_definition.responseStream),
66
+ };
67
+ const requester = new grpc.RequesterBuilder()
68
+ .withStart((metadata, listener, next) => {
69
+ const newListener = new grpc.ListenerBuilder()
70
+ .withOnReceiveStatus((status, next) => {
71
+ try {
72
+ metrics.handled({
73
+ ...labels,
74
+ code: Status[status.code],
75
+ });
76
+ } finally {
77
+ next(status);
78
+ }
79
+ })
80
+ .withOnReceiveMessage((message, next) => {
81
+ try {
82
+ metrics.received(labels);
83
+ } finally {
84
+ next(message);
85
+ }
86
+ })
87
+ .build();
88
+ try {
89
+ metrics.started(labels);
90
+ } finally {
91
+ next(metadata, newListener);
92
+ }
93
+ })
94
+ .withSendMessage((message, next) => {
95
+ try {
96
+ metrics.sent(labels);
97
+ } finally {
98
+ next(message);
99
+ }
100
+ })
101
+ .build();
102
+ return new grpc.InterceptingCall(nextCall(options), requester);
103
+ };
104
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Copyright (c) 2021 Gitpod GmbH. All rights reserved.
3
+ * Licensed under the GNU Affero General Public License (AGPL).
4
+ * See License-AGPL.txt in the project root for license information.
5
+ */
6
+
7
+ import { suite, test } from "mocha-typescript";
8
+ import { log } from "./logging";
9
+
10
+ @suite
11
+ class TestLogging {
12
+ @test public async testLogInfo_output() {
13
+ const testObj = {
14
+ null: null,
15
+ undefined: undefined,
16
+ empty: "",
17
+ foo: "bar",
18
+ number: 0,
19
+ };
20
+ log.info("info logging test", testObj);
21
+ }
22
+ }
23
+ module.exports = new TestLogging();