@gitpod/gitpod-protocol 0.1.5-pd-ide-metrics.45 → 0.1.5-pd-ide-service.11

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 (90) hide show
  1. package/data/gitpod-schema.json +63 -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 +3 -0
  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 +41 -0
  10. package/lib/billing-mode.d.ts.map +1 -0
  11. package/lib/billing-mode.js +44 -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 +20 -15
  18. package/lib/gitpod-service.d.ts.map +1 -1
  19. package/lib/gitpod-service.js +12 -0
  20. package/lib/gitpod-service.js.map +1 -1
  21. package/lib/ide-protocol.d.ts +8 -0
  22. package/lib/ide-protocol.d.ts.map +1 -1
  23. package/lib/messaging/error.d.ts +4 -1
  24. package/lib/messaging/error.d.ts.map +1 -1
  25. package/lib/messaging/error.js +9 -3
  26. package/lib/messaging/error.js.map +1 -1
  27. package/lib/plans.d.ts +1 -0
  28. package/lib/plans.d.ts.map +1 -1
  29. package/lib/plans.js +4 -0
  30. package/lib/plans.js.map +1 -1
  31. package/lib/protocol.d.ts +18 -3
  32. package/lib/protocol.d.ts.map +1 -1
  33. package/lib/protocol.js +16 -1
  34. package/lib/protocol.js.map +1 -1
  35. package/lib/team-subscription-protocol.d.ts +1 -0
  36. package/lib/team-subscription-protocol.d.ts.map +1 -1
  37. package/lib/team-subscription-protocol.js +4 -0
  38. package/lib/team-subscription-protocol.js.map +1 -1
  39. package/lib/teams-projects-protocol.d.ts +0 -1
  40. package/lib/teams-projects-protocol.d.ts.map +1 -1
  41. package/lib/teams-projects-protocol.js.map +1 -1
  42. package/lib/usage.d.ts +48 -17
  43. package/lib/usage.d.ts.map +1 -1
  44. package/lib/usage.js +6 -6
  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/logging.spec.d.ts +7 -0
  50. package/lib/util/logging.spec.d.ts.map +1 -0
  51. package/lib/util/logging.spec.js +52 -0
  52. package/lib/util/logging.spec.js.map +1 -0
  53. package/lib/util/nice-grpc.d.ts +9 -0
  54. package/lib/util/nice-grpc.d.ts.map +1 -0
  55. package/lib/util/nice-grpc.js +119 -0
  56. package/lib/util/nice-grpc.js.map +1 -0
  57. package/lib/util/timeutil.d.ts +1 -0
  58. package/lib/util/timeutil.d.ts.map +1 -1
  59. package/lib/util/timeutil.js +7 -1
  60. package/lib/util/timeutil.js.map +1 -1
  61. package/lib/util/timeutil.spec.d.ts +2 -0
  62. package/lib/util/timeutil.spec.d.ts.map +1 -1
  63. package/lib/util/timeutil.spec.js +34 -0
  64. package/lib/util/timeutil.spec.js.map +1 -1
  65. package/lib/workspace-class.d.ts +1 -1
  66. package/lib/workspace-class.d.ts.map +1 -1
  67. package/lib/workspace-instance.d.ts +1 -0
  68. package/lib/workspace-instance.d.ts.map +1 -1
  69. package/package.json +4 -2
  70. package/pkg-yarn.lock +4 -2
  71. package/provenance-bundle.jsonl +1 -1
  72. package/src/accounting-protocol.ts +3 -0
  73. package/src/admin-protocol.ts +3 -0
  74. package/src/billing-mode.ts +77 -0
  75. package/src/experiments/configcat-server.ts +3 -0
  76. package/src/gitpod-service.ts +33 -15
  77. package/src/ide-protocol.ts +10 -0
  78. package/src/messaging/error.ts +13 -4
  79. package/src/plans.ts +4 -0
  80. package/src/protocol.ts +35 -4
  81. package/src/team-subscription-protocol.ts +3 -0
  82. package/src/teams-projects-protocol.ts +0 -1
  83. package/src/usage.ts +52 -30
  84. package/src/util/garbage-collected-cache.ts +5 -0
  85. package/src/util/logging.spec.ts +23 -0
  86. package/src/util/nice-grpc.ts +93 -0
  87. package/src/util/timeutil.spec.ts +28 -1
  88. package/src/util/timeutil.ts +6 -0
  89. package/src/workspace-class.ts +1 -1
  90. package/src/workspace-instance.ts +3 -0
@@ -0,0 +1,77 @@
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 showTeamSubscriptionUI(billingMode?: BillingMode): boolean {
30
+ if (!billingMode) {
31
+ return false;
32
+ }
33
+ return (
34
+ billingMode.mode === "chargebee" ||
35
+ (billingMode.mode === "usage-based" && !!billingMode.hasChargebeeTeamSubscription)
36
+ );
37
+ }
38
+
39
+ export function canSetWorkspaceClass(billingMode?: BillingMode): boolean {
40
+ if (!billingMode) {
41
+ return false;
42
+ }
43
+
44
+ // if has any Stripe subscription, either directly or per team
45
+ return billingMode.mode === "usage-based";
46
+ }
47
+
48
+ export function canSetCostCenter(billingMode: BillingMode): boolean {
49
+ // if has any Stripe Subscription, either directly or per team
50
+ return billingMode.mode === "usage-based";
51
+ }
52
+ }
53
+
54
+ /** Payment is disabled */
55
+ interface None {
56
+ mode: "none";
57
+ }
58
+
59
+ /** Sessions is handled with old subscription logic based on Chargebee */
60
+ interface Chargebee {
61
+ mode: "chargebee";
62
+ canUpgradeToUBB?: boolean;
63
+ }
64
+
65
+ /** Session is handld with new usage-based logic */
66
+ interface UsageBased {
67
+ mode: "usage-based";
68
+
69
+ /** True iff this is a team, and is based on a paid plan. Currently only set for teams! */
70
+ paid?: boolean;
71
+
72
+ /** User is already converted, but is member with at least one Chargebee-based "Team Plan" */
73
+ hasChargebeeTeamPlan?: boolean;
74
+
75
+ /** User is already converted, but is member or owner in at least one Chargebee-based "Team Subscription" */
76
+ hasChargebeeTeamSubscription?: boolean;
77
+ }
@@ -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,9 +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, BillableSessionRequest } from "./usage";
63
+ import { ListUsageRequest, ListUsageResponse } from "./usage";
65
64
  import { SupportedWorkspaceClass } from "./workspace-class";
65
+ import { BillingMode } from "./billing-mode";
66
66
 
67
67
  export interface GitpodClient {
68
68
  onInstanceUpdate(instance: WorkspaceInstance): void;
@@ -70,6 +70,8 @@ export interface GitpodClient {
70
70
 
71
71
  onPrebuildUpdate(update: PrebuildWithStatus): void;
72
72
 
73
+ onNotificationUpdated(): void;
74
+
73
75
  onCreditAlert(creditAlert: CreditAlert): void;
74
76
 
75
77
  //#region propagating reconnection to iframe
@@ -84,6 +86,8 @@ export interface GitpodServer extends JsonRpcServer<GitpodClient>, AdminServer,
84
86
  getLoggedInUser(): Promise<User>;
85
87
  getTerms(): Promise<Terms>;
86
88
  updateLoggedInUser(user: Partial<User>): Promise<User>;
89
+ sendPhoneNumberVerificationToken(phoneNumber: string): Promise<void>;
90
+ verifyPhoneNumberVerificationToken(phoneNumber: string, token: string): Promise<boolean>;
87
91
  getAuthProviders(): Promise<AuthProviderInfo[]>;
88
92
  getOwnAuthProviders(): Promise<AuthProviderEntry[]>;
89
93
  updateOwnAuthProvider(params: GitpodServer.UpdateOwnAuthProviderParams): Promise<AuthProviderEntry>;
@@ -189,11 +193,6 @@ export interface GitpodServer extends JsonRpcServer<GitpodClient>, AdminServer,
189
193
  getPrebuild(prebuildId: string): Promise<PrebuildWithStatus | undefined>;
190
194
  triggerPrebuild(projectId: string, branchName: string | null): Promise<StartPrebuildResult>;
191
195
  cancelPrebuild(projectId: string, prebuildId: string): Promise<void>;
192
- fetchProjectRepositoryConfiguration(projectId: string): Promise<string | undefined>;
193
- guessProjectConfiguration(projectId: string): Promise<string | undefined>;
194
- fetchRepositoryConfiguration(cloneUrl: string): Promise<string | undefined>;
195
- guessRepositoryConfiguration(cloneUrl: string): Promise<string | undefined>;
196
- setProjectConfiguration(projectId: string, configString: string): Promise<void>;
197
196
  updateProjectPartial(partialProject: PartialProject): Promise<void>;
198
197
  setProjectEnvironmentVariable(projectId: string, name: string, value: string, censored: boolean): Promise<void>;
199
198
  getProjectEnvironmentVariables(projectId: string): Promise<ProjectEnvVar[]>;
@@ -209,7 +208,6 @@ export interface GitpodServer extends JsonRpcServer<GitpodClient>, AdminServer,
209
208
  deleteGitpodToken(tokenHash: string): Promise<void>;
210
209
 
211
210
  // misc
212
- sendFeedback(feedback: string): Promise<string | undefined>;
213
211
  isGitHubAppEnabled(): Promise<boolean>;
214
212
  registerGithubApp(installationId: string): Promise<void>;
215
213
 
@@ -288,16 +286,26 @@ export interface GitpodServer extends JsonRpcServer<GitpodClient>, AdminServer,
288
286
 
289
287
  getStripePublishableKey(): Promise<string>;
290
288
  getStripeSetupIntentClientSecret(): Promise<string>;
289
+ findStripeSubscriptionId(attributionId: string): Promise<string | undefined>;
291
290
  findStripeSubscriptionIdForTeam(teamId: string): Promise<string | undefined>;
292
- subscribeTeamToStripe(teamId: string, setupIntentId: string, currency: Currency): Promise<void>;
291
+ createOrUpdateStripeCustomerForTeam(teamId: string, currency: string): Promise<void>;
292
+ createOrUpdateStripeCustomerForUser(currency: string): Promise<void>;
293
+ subscribeToStripe(attributionId: string, setupIntentId: string): Promise<void>;
294
+ subscribeTeamToStripe(teamId: string, setupIntentId: string): Promise<void>;
295
+ getStripePortalUrl(attributionId: string): Promise<string>;
293
296
  getStripePortalUrlForTeam(teamId: string): Promise<string>;
294
- getSpendingLimitForTeam(teamId: string): Promise<number | undefined>;
295
- setSpendingLimitForTeam(teamId: string, spendingLimit: number): Promise<void>;
297
+ getUsageLimit(attributionId: string): Promise<number | undefined>;
298
+ setUsageLimit(attributionId: string, usageLimit: number): Promise<void>;
299
+ getUsageLimitForTeam(teamId: string): Promise<number | undefined>;
300
+ setUsageLimitForTeam(teamId: string, spendingLimit: number): Promise<void>;
296
301
 
297
- listBilledUsage(req: BillableSessionRequest): Promise<BillableSession[]>;
302
+ listUsage(req: ListUsageRequest): Promise<ListUsageResponse>;
298
303
 
299
304
  setUsageAttribution(usageAttribution: string): Promise<void>;
300
305
 
306
+ getBillingModeForUser(): Promise<BillingMode>;
307
+ getBillingModeForTeam(teamId: string): Promise<BillingMode>;
308
+
301
309
  /**
302
310
  * Analytics
303
311
  */
@@ -325,9 +333,7 @@ export interface RateLimiterError {
325
333
 
326
334
  export interface CreateProjectParams {
327
335
  name: string;
328
- slug?: string;
329
- account: string;
330
- provider: string;
336
+ slug: string;
331
337
  cloneUrl: string;
332
338
  teamId?: string;
333
339
  userId?: string;
@@ -566,6 +572,18 @@ export class GitpodCompositeClient<Client extends GitpodClient> implements Gitpo
566
572
  }
567
573
  }
568
574
  }
575
+
576
+ onNotificationUpdated(): void {
577
+ for (const client of this.clients) {
578
+ if (client.onNotificationUpdated) {
579
+ try {
580
+ client.onNotificationUpdated();
581
+ } catch (error) {
582
+ console.error(error);
583
+ }
584
+ }
585
+ }
586
+ }
569
587
  }
570
588
 
571
589
  export type GitpodService = GitpodServiceImpl<GitpodClient, GitpodServer>;
@@ -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
  }
@@ -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;
@@ -48,6 +49,12 @@ export interface User {
48
49
 
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)
50
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;
51
58
  }
52
59
 
53
60
  export namespace User {
@@ -207,7 +214,21 @@ export interface AdditionalUserData {
207
214
  // additional user profile data
208
215
  profile?: ProfileDetails;
209
216
  }
210
-
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
+ }
211
232
  // The format in which we store User Profiles in
212
233
  export interface ProfileDetails {
213
234
  // when was the last time the user updated their profile information or has been nudged to do so.
@@ -273,6 +294,8 @@ export const WorkspaceFeatureFlags = {
273
294
  full_workspace_backup: undefined,
274
295
  persistent_volume_claim: undefined,
275
296
  protected_secrets: undefined,
297
+ workspace_class_limiting: undefined,
298
+ workspace_connection_limiting: undefined,
276
299
  };
277
300
  export type NamedWorkspaceFeatureFlag = keyof typeof WorkspaceFeatureFlags;
278
301
 
@@ -781,6 +804,12 @@ export interface RepositoryCloneInformation {
781
804
  checkoutLocation?: string;
782
805
  }
783
806
 
807
+ export interface CoreDumpConfig {
808
+ enabled?: boolean;
809
+ softLimit?: number;
810
+ hardLimit?: number;
811
+ }
812
+
784
813
  export interface WorkspaceConfig {
785
814
  mainConfiguration?: string;
786
815
  additionalRepositories?: RepositoryCloneInformation[];
@@ -793,6 +822,7 @@ export interface WorkspaceConfig {
793
822
  github?: GithubAppConfig;
794
823
  vscode?: VSCodeConfig;
795
824
  jetbrains?: JetBrainsConfig;
825
+ coreDump?: CoreDumpConfig;
796
826
 
797
827
  /** deprecated. Enabled by default **/
798
828
  experimentalNetwork?: boolean;
@@ -801,13 +831,12 @@ export interface WorkspaceConfig {
801
831
  * Where the config object originates from.
802
832
  *
803
833
  * repo - from the repository
804
- * project-db - from the "Project" stored in the database
805
834
  * definitly-gp - from github.com/gitpod-io/definitely-gp
806
835
  * derived - computed based on analyzing the repository
807
836
  * additional-content - config comes from additional content, usually provided through the project's configuration
808
837
  * default - our static catch-all default config
809
838
  */
810
- _origin?: "repo" | "project-db" | "definitely-gp" | "derived" | "additional-content" | "default";
839
+ _origin?: "repo" | "definitely-gp" | "derived" | "additional-content" | "default";
811
840
 
812
841
  /**
813
842
  * Set of automatically infered feature flags. That's not something the user can set, but
@@ -1482,10 +1511,12 @@ export interface Terms {
1482
1511
  readonly formElements?: object;
1483
1512
  }
1484
1513
 
1514
+ export type BillingStrategy = "other" | "stripe";
1485
1515
  export interface CostCenter {
1486
- readonly id: string;
1516
+ readonly id: AttributionId;
1487
1517
  /**
1488
1518
  * Unit: credits
1489
1519
  */
1490
1520
  spendingLimit: number;
1521
+ billingStrategy: BillingStrategy;
1491
1522
  }
@@ -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
  /**
@@ -27,7 +27,6 @@ export interface Project {
27
27
  teamId?: string;
28
28
  userId?: string;
29
29
  appInstallationId: string;
30
- config?: ProjectConfig;
31
30
  settings?: ProjectSettings;
32
31
  creationTime: string;
33
32
  /** This is a flag that triggers the HARD DELETION of this entity */
package/src/usage.ts CHANGED
@@ -6,45 +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;
22
-
23
- workspaceClass: string;
24
-
25
- // When the workspace started
26
- startTime: string;
18
+ export enum Ordering {
19
+ ORDERING_DESCENDING = 0,
20
+ ORDERING_ASCENDING = 1,
21
+ }
27
22
 
28
- // When the workspace ended. Not set when the workspace is still running.
29
- endTime?: string;
23
+ export interface PaginationRequest {
24
+ perPage: number;
25
+ page: number;
26
+ }
30
27
 
31
- // The credits used for this session
32
- credits: number;
28
+ export interface ListUsageResponse {
29
+ usageEntriesList: Usage[];
30
+ pagination?: PaginationResponse;
31
+ creditBalanceAtStart: number;
32
+ creditBalanceAtEnd: number;
33
+ }
33
34
 
34
- // TODO - maybe
35
- projectId?: string;
35
+ export interface PaginationResponse {
36
+ perPage: number;
37
+ totalPages: number;
38
+ total: number;
39
+ page: number;
36
40
  }
37
41
 
38
- export interface BillableSessionRequest {
42
+ export type UsageKind = "workspaceinstance" | "invoice";
43
+ export interface Usage {
44
+ id: string;
39
45
  attributionId: string;
40
- startedTimeOrder: SortOrder;
41
- from?: number;
42
- to?: number;
46
+ description: string;
47
+ credits: number;
48
+ effectiveTime?: number;
49
+ kind: UsageKind;
50
+ workspaceInstanceId: string;
51
+ draft: boolean;
52
+ metadata: WorkspaceInstanceUsageData | InvoiceUsageData;
43
53
  }
44
54
 
45
- 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
+ }
46
67
 
47
- export enum SortOrder {
48
- Descending = 0,
49
- Ascending = 1,
68
+ export interface InvoiceUsageData {
69
+ invoiceId: string;
70
+ startDate: string;
71
+ endDate: string;
50
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
 
@@ -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();
@@ -0,0 +1,93 @@
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
+ import { isAbortError } from "abort-controller-x";
8
+ import {
9
+ CallOptions,
10
+ ClientError,
11
+ ClientMiddleware,
12
+ ClientMiddlewareCall,
13
+ Status,
14
+ MethodDescriptor,
15
+ } from "nice-grpc-common";
16
+ import { GrpcMethodType, IClientCallMetrics } from "./grpc";
17
+
18
+ function getLabels(method: MethodDescriptor) {
19
+ const callType = method.requestStream
20
+ ? method.responseStream
21
+ ? "bidi_stream"
22
+ : "client_stream"
23
+ : method.responseStream
24
+ ? "server_stream"
25
+ : "unary";
26
+ const { path } = method;
27
+ const [serviceName, methodName] = path.split("/").slice(1);
28
+
29
+ return {
30
+ type: callType as GrpcMethodType,
31
+ service: serviceName,
32
+ method: methodName,
33
+ };
34
+ }
35
+
36
+ async function* incrementStreamMessagesCounter<T>(iterable: AsyncIterable<T>, callback: () => void): AsyncIterable<T> {
37
+ for await (const item of iterable) {
38
+ callback();
39
+ yield item;
40
+ }
41
+ }
42
+
43
+ export function prometheusClientMiddleware(metrics: IClientCallMetrics): ClientMiddleware {
44
+ return async function* prometheusClientMiddlewareGenerator<Request, Response>(
45
+ call: ClientMiddlewareCall<Request, Response>,
46
+ options: CallOptions,
47
+ ): AsyncGenerator<Response, Response | void, undefined> {
48
+ const labels = getLabels(call.method);
49
+
50
+ metrics.started(labels);
51
+
52
+ let settled = false;
53
+ let status: Status = Status.OK;
54
+
55
+ try {
56
+ let request;
57
+
58
+ if (!call.requestStream) {
59
+ request = call.request;
60
+ } else {
61
+ request = incrementStreamMessagesCounter(call.request, metrics.sent.bind(metrics, labels));
62
+ }
63
+
64
+ if (!call.responseStream) {
65
+ const response = yield* call.next(request, options);
66
+ settled = true;
67
+ return response;
68
+ } else {
69
+ yield* incrementStreamMessagesCounter(
70
+ call.next(request, options),
71
+ metrics.received.bind(metrics, labels),
72
+ );
73
+ settled = true;
74
+ return;
75
+ }
76
+ } catch (err) {
77
+ settled = true;
78
+ if (err instanceof ClientError) {
79
+ status = err.code;
80
+ } else if (isAbortError(err)) {
81
+ status = Status.CANCELLED;
82
+ } else {
83
+ status = Status.UNKNOWN;
84
+ }
85
+ throw err;
86
+ } finally {
87
+ if (!settled) {
88
+ status = Status.CANCELLED;
89
+ }
90
+ metrics.handled({ ...labels, code: Status[status] });
91
+ }
92
+ };
93
+ }
@@ -7,10 +7,16 @@
7
7
  import * as chai from "chai";
8
8
  const expect = chai.expect;
9
9
  import { suite, test } from "mocha-typescript";
10
- import { oneMonthLater } from "./timeutil";
10
+ import { daysBefore, hoursBefore, oneMonthLater } from "./timeutil";
11
11
 
12
12
  @suite()
13
13
  export class TimeutilSpec {
14
+ @test
15
+ testDaysBefore() {
16
+ const now = new Date().toISOString();
17
+ expect(daysBefore(now, 2)).to.be.eq(hoursBefore(now, 48));
18
+ }
19
+
14
20
  @test
15
21
  testTimeutil() {
16
22
  // targeting a 1st, 1th of Jan => 1st of Feb
@@ -39,4 +45,25 @@ export class TimeutilSpec {
39
45
  const later = oneMonthLater(from.toISOString(), day);
40
46
  expect(later, `expected ${later} to be equal ${expectation}`).to.be.equal(expectation.toISOString());
41
47
  }
48
+
49
+ @test
50
+ testDaysBefore2() {
51
+ const tests: { date: Date; daysEarlier: number; expectation: string }[] = [
52
+ {
53
+ date: new Date("2021-07-13T00:00:00.000Z"),
54
+ daysEarlier: 365,
55
+ expectation: "2020-07-13T00:00:00.000Z",
56
+ },
57
+ {
58
+ date: new Date("2019-02-01T00:00:00.000Z"),
59
+ daysEarlier: 365,
60
+ expectation: "2018-02-01T00:00:00.000Z",
61
+ },
62
+ ];
63
+
64
+ for (const t of tests) {
65
+ const actual = daysBefore(t.date.toISOString(), t.daysEarlier);
66
+ expect(actual).to.equal(t.expectation, `expected ${actual} to be equal ${t.expectation}`);
67
+ }
68
+ }
42
69
  }
@@ -47,6 +47,12 @@ export const orderAsc = (d1: string, d2: string): number => liftDate(d1, d2, (d1
47
47
  export const liftDate1 = <T>(d1: string, f: (d1: Date) => T): T => f(new Date(d1));
48
48
  export const liftDate = <T>(d1: string, d2: string, f: (d1: Date, d2: Date) => T): T => f(new Date(d1), new Date(d2));
49
49
 
50
+ export function daysBefore(date: string, days: number): string {
51
+ const result = new Date(date);
52
+ result.setDate(result.getDate() - days);
53
+ return result.toISOString();
54
+ }
55
+
50
56
  export function hoursBefore(date: string, hours: number): string {
51
57
  const result = new Date(date);
52
58
  result.setHours(result.getHours() - hours);
@@ -10,5 +10,5 @@ export interface SupportedWorkspaceClass {
10
10
  displayName: string;
11
11
  description: string;
12
12
  powerups: number;
13
- isDefault: boolean;
13
+ isSelected: boolean;
14
14
  }