@gitpod/gitpod-protocol 0.1.5-se-cancel-reason.0 → 0.1.5-se-usage-view.1
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.
- package/data/gitpod-schema.json +44 -129
- package/lib/accounting-protocol.d.ts +1 -0
- package/lib/accounting-protocol.d.ts.map +1 -1
- package/lib/accounting-protocol.js +4 -0
- package/lib/accounting-protocol.js.map +1 -1
- package/lib/admin-protocol.d.ts +5 -0
- package/lib/admin-protocol.d.ts.map +1 -1
- package/lib/admin-protocol.js.map +1 -1
- package/lib/attribution.d.ts +20 -0
- package/lib/attribution.d.ts.map +1 -0
- package/lib/attribution.js +40 -0
- package/lib/attribution.js.map +1 -0
- package/lib/billing-mode.d.ts +40 -0
- package/lib/billing-mode.d.ts.map +1 -0
- package/lib/billing-mode.js +36 -0
- package/lib/billing-mode.js.map +1 -0
- package/lib/blocked-repositories-protocol.d.ts +13 -0
- package/lib/blocked-repositories-protocol.d.ts.map +1 -0
- package/lib/blocked-repositories-protocol.js +8 -0
- package/lib/blocked-repositories-protocol.js.map +1 -0
- package/lib/experiments/configcat-server.d.ts +2 -0
- package/lib/experiments/configcat-server.d.ts.map +1 -1
- package/lib/experiments/configcat-server.js +3 -1
- package/lib/experiments/configcat-server.js.map +1 -1
- package/lib/experiments/configcat.d.ts +3 -2
- package/lib/experiments/configcat.d.ts.map +1 -1
- package/lib/experiments/configcat.js +9 -3
- package/lib/experiments/configcat.js.map +1 -1
- package/lib/experiments/types.d.ts +7 -3
- package/lib/experiments/types.d.ts.map +1 -1
- package/lib/experiments/types.js +2 -0
- package/lib/experiments/types.js.map +1 -1
- package/lib/gitpod-service.d.ts +26 -4
- package/lib/gitpod-service.d.ts.map +1 -1
- package/lib/gitpod-service.js +1 -1
- package/lib/gitpod-service.js.map +1 -1
- package/lib/ide-protocol.d.ts +8 -0
- package/lib/ide-protocol.d.ts.map +1 -1
- package/lib/index.d.ts +1 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +1 -0
- package/lib/index.js.map +1 -1
- package/lib/messaging/client-call-metrics.d.ts +1 -17
- package/lib/messaging/client-call-metrics.d.ts.map +1 -1
- package/lib/messaging/client-call-metrics.js +1 -2
- package/lib/messaging/client-call-metrics.js.map +1 -1
- package/lib/messaging/error.d.ts +4 -1
- package/lib/messaging/error.d.ts.map +1 -1
- package/lib/messaging/error.js +9 -3
- package/lib/messaging/error.js.map +1 -1
- package/lib/plans.d.ts +1 -0
- package/lib/plans.d.ts.map +1 -1
- package/lib/plans.js +4 -0
- package/lib/plans.js.map +1 -1
- package/lib/protocol.d.ts +61 -3
- package/lib/protocol.d.ts.map +1 -1
- package/lib/protocol.js +104 -3
- package/lib/protocol.js.map +1 -1
- package/lib/protocol.spec.d.ts +7 -0
- package/lib/protocol.spec.d.ts.map +1 -0
- package/lib/protocol.spec.js +127 -0
- package/lib/protocol.spec.js.map +1 -0
- package/lib/team-subscription-protocol.d.ts +1 -0
- package/lib/team-subscription-protocol.d.ts.map +1 -1
- package/lib/team-subscription-protocol.js +4 -0
- package/lib/team-subscription-protocol.js.map +1 -1
- package/lib/teams-projects-protocol.d.ts +12 -0
- package/lib/teams-projects-protocol.d.ts.map +1 -1
- package/lib/teams-projects-protocol.js.map +1 -1
- package/lib/usage.d.ts +97 -0
- package/lib/usage.d.ts.map +1 -0
- package/lib/usage.js +14 -0
- package/lib/usage.js.map +1 -0
- package/lib/util/garbage-collected-cache.d.ts.map +1 -1
- package/lib/util/garbage-collected-cache.js +5 -0
- package/lib/util/garbage-collected-cache.js.map +1 -1
- package/lib/util/gitpod-host-url.d.ts +3 -0
- package/lib/util/gitpod-host-url.d.ts.map +1 -1
- package/lib/util/gitpod-host-url.js +18 -0
- package/lib/util/gitpod-host-url.js.map +1 -1
- package/lib/util/grpc.d.ts +19 -0
- package/lib/util/grpc.d.ts.map +1 -1
- package/lib/util/grpc.js +73 -1
- package/lib/util/grpc.js.map +1 -1
- package/lib/util/logging.spec.d.ts +7 -0
- package/lib/util/logging.spec.d.ts.map +1 -0
- package/lib/util/logging.spec.js +52 -0
- package/lib/util/logging.spec.js.map +1 -0
- package/lib/util/timeutil.d.ts +1 -0
- package/lib/util/timeutil.d.ts.map +1 -1
- package/lib/util/timeutil.js +7 -1
- package/lib/util/timeutil.js.map +1 -1
- package/lib/util/timeutil.spec.d.ts +1 -0
- package/lib/util/timeutil.spec.d.ts.map +1 -1
- package/lib/util/timeutil.spec.js +10 -0
- package/lib/util/timeutil.spec.js.map +1 -1
- package/lib/webhook-event.d.ts +44 -0
- package/lib/webhook-event.d.ts.map +1 -0
- package/lib/webhook-event.js +8 -0
- package/lib/webhook-event.js.map +1 -0
- package/lib/workspace-class.d.ts +14 -0
- package/lib/workspace-class.d.ts.map +1 -0
- package/lib/workspace-class.js +8 -0
- package/lib/workspace-class.js.map +1 -0
- package/lib/workspace-cluster.d.ts +5 -7
- package/lib/workspace-cluster.d.ts.map +1 -1
- package/lib/workspace-cluster.js.map +1 -1
- package/lib/workspace-instance.d.ts +2 -0
- package/lib/workspace-instance.d.ts.map +1 -1
- package/lib/wsready.d.ts +3 -3
- package/lib/wsready.d.ts.map +1 -1
- package/lib/wsready.js +0 -2
- package/lib/wsready.js.map +1 -1
- package/package.json +5 -2
- package/pkg-yarn.lock +3 -2
- package/provenance-bundle.jsonl +1 -1
- package/src/accounting-protocol.ts +3 -0
- package/src/admin-protocol.ts +8 -0
- package/src/attribution.ts +48 -0
- package/src/billing-mode.ts +67 -0
- package/src/blocked-repositories-protocol.ts +13 -0
- package/src/experiments/configcat-server.ts +4 -0
- package/src/experiments/configcat.ts +10 -5
- package/src/experiments/types.ts +10 -7
- package/src/gitpod-service.ts +35 -3
- package/src/ide-protocol.ts +10 -0
- package/src/index.ts +1 -0
- package/src/messaging/client-call-metrics.ts +1 -20
- package/src/messaging/error.ts +13 -4
- package/src/plans.ts +4 -0
- package/src/protocol.spec.ts +97 -0
- package/src/protocol.ts +163 -5
- package/src/team-subscription-protocol.ts +3 -0
- package/src/teams-projects-protocol.ts +13 -0
- package/src/usage.ts +128 -0
- package/src/util/garbage-collected-cache.ts +5 -0
- package/src/util/gitpod-host-url.ts +21 -0
- package/src/util/grpc.ts +89 -0
- package/src/util/logging.spec.ts +23 -0
- package/src/util/timeutil.spec.ts +7 -1
- package/src/util/timeutil.ts +6 -0
- package/src/webhook-event.ts +55 -0
- package/src/workspace-class.ts +14 -0
- package/src/workspace-cluster.ts +2 -4
- package/src/workspace-instance.ts +8 -0
- package/src/wsready.ts +5 -4
package/src/protocol.ts
CHANGED
|
@@ -45,6 +45,15 @@ export interface User {
|
|
|
45
45
|
markedDeleted?: boolean;
|
|
46
46
|
|
|
47
47
|
additionalData?: AdditionalUserData;
|
|
48
|
+
|
|
49
|
+
// 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
|
+
usageAttributionId?: string;
|
|
51
|
+
|
|
52
|
+
// The last time this user got verified somehow. The user is not verified if this is empty.
|
|
53
|
+
lastVerificationTime?: string;
|
|
54
|
+
|
|
55
|
+
// The phone number used for the last phone verification.
|
|
56
|
+
verificationPhoneNumber?: string;
|
|
48
57
|
}
|
|
49
58
|
|
|
50
59
|
export namespace User {
|
|
@@ -70,11 +79,14 @@ export namespace User {
|
|
|
70
79
|
}
|
|
71
80
|
|
|
72
81
|
/**
|
|
73
|
-
*
|
|
82
|
+
* Returns the stored email or if it doesn't exist returns the primaryEmail of the first identity this user signed up with.
|
|
74
83
|
* @param user
|
|
75
84
|
* @returns A primaryEmail, or undefined if there is none.
|
|
76
85
|
*/
|
|
77
86
|
export function getPrimaryEmail(user: User): string | undefined {
|
|
87
|
+
if (user.additionalData?.profile?.emailAddress) {
|
|
88
|
+
return user.additionalData?.profile?.emailAddress;
|
|
89
|
+
}
|
|
78
90
|
const identities = user.identities.filter((i) => !!i.primaryEmail);
|
|
79
91
|
if (identities.length <= 0) {
|
|
80
92
|
return undefined;
|
|
@@ -136,6 +148,50 @@ export namespace User {
|
|
|
136
148
|
}
|
|
137
149
|
user.additionalData.ideSettings = newIDESettings;
|
|
138
150
|
}
|
|
151
|
+
|
|
152
|
+
export function getProfile(user: User): Profile {
|
|
153
|
+
return {
|
|
154
|
+
name: User.getName(user!) || "",
|
|
155
|
+
email: User.getPrimaryEmail(user!) || "",
|
|
156
|
+
company: user?.additionalData?.profile?.companyName,
|
|
157
|
+
avatarURL: user?.avatarUrl,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export function setProfile(user: User, profile: Profile): User {
|
|
162
|
+
user.fullName = profile.name;
|
|
163
|
+
user.avatarUrl = profile.avatarURL;
|
|
164
|
+
|
|
165
|
+
if (!user.additionalData) {
|
|
166
|
+
user.additionalData = {};
|
|
167
|
+
}
|
|
168
|
+
if (!user.additionalData.profile) {
|
|
169
|
+
user.additionalData.profile = {};
|
|
170
|
+
}
|
|
171
|
+
user.additionalData.profile.emailAddress = profile.email;
|
|
172
|
+
user.additionalData.profile.companyName = profile.company;
|
|
173
|
+
user.additionalData.profile.lastUpdatedDetailsNudge = new Date().toISOString();
|
|
174
|
+
|
|
175
|
+
return user;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// The actual Profile of a User
|
|
179
|
+
export interface Profile {
|
|
180
|
+
name: string;
|
|
181
|
+
email: string;
|
|
182
|
+
company?: string;
|
|
183
|
+
avatarURL?: string;
|
|
184
|
+
}
|
|
185
|
+
export namespace Profile {
|
|
186
|
+
export function hasChanges(before: Profile, after: Profile) {
|
|
187
|
+
return (
|
|
188
|
+
before.name !== after.name ||
|
|
189
|
+
before.email !== after.email ||
|
|
190
|
+
before.company !== after.company ||
|
|
191
|
+
before.avatarURL !== after.avatarURL
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
139
195
|
}
|
|
140
196
|
|
|
141
197
|
export interface AdditionalUserData {
|
|
@@ -152,8 +208,34 @@ export interface AdditionalUserData {
|
|
|
152
208
|
knownGitHubOrgs?: string[];
|
|
153
209
|
// Git clone URL pointing to the user's dotfile repo
|
|
154
210
|
dotfileRepo?: string;
|
|
155
|
-
//
|
|
156
|
-
|
|
211
|
+
// preferred workspace classes
|
|
212
|
+
workspaceClasses?: WorkspaceClasses;
|
|
213
|
+
// additional user profile data
|
|
214
|
+
profile?: ProfileDetails;
|
|
215
|
+
}
|
|
216
|
+
export namespace AdditionalUserData {
|
|
217
|
+
export function set(user: User, partialData: Partial<AdditionalUserData>): User {
|
|
218
|
+
if (!user.additionalData) {
|
|
219
|
+
user.additionalData = {
|
|
220
|
+
...partialData,
|
|
221
|
+
};
|
|
222
|
+
} else {
|
|
223
|
+
user.additionalData = {
|
|
224
|
+
...user.additionalData,
|
|
225
|
+
...partialData,
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
return user;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
// The format in which we store User Profiles in
|
|
232
|
+
export interface ProfileDetails {
|
|
233
|
+
// when was the last time the user updated their profile information or has been nudged to do so.
|
|
234
|
+
lastUpdatedDetailsNudge?: string;
|
|
235
|
+
// the user's company name
|
|
236
|
+
companyName?: string;
|
|
237
|
+
// the user's email
|
|
238
|
+
emailAddress?: string;
|
|
157
239
|
}
|
|
158
240
|
|
|
159
241
|
export interface EmailNotificationSettings {
|
|
@@ -172,6 +254,11 @@ export type IDESettings = {
|
|
|
172
254
|
useLatestVersion?: boolean;
|
|
173
255
|
};
|
|
174
256
|
|
|
257
|
+
export interface WorkspaceClasses {
|
|
258
|
+
regular: string;
|
|
259
|
+
prebuild: string;
|
|
260
|
+
}
|
|
261
|
+
|
|
175
262
|
export interface UserPlatform {
|
|
176
263
|
uid: string;
|
|
177
264
|
userAgent: string;
|
|
@@ -204,8 +291,9 @@ export interface UserFeatureSettings {
|
|
|
204
291
|
*/
|
|
205
292
|
export const WorkspaceFeatureFlags = {
|
|
206
293
|
full_workspace_backup: undefined,
|
|
207
|
-
fixed_resources: undefined,
|
|
208
294
|
persistent_volume_claim: undefined,
|
|
295
|
+
protected_secrets: undefined,
|
|
296
|
+
workspace_class_limiting: undefined,
|
|
209
297
|
};
|
|
210
298
|
export type NamedWorkspaceFeatureFlag = keyof typeof WorkspaceFeatureFlags;
|
|
211
299
|
|
|
@@ -357,6 +445,68 @@ export namespace UserEnvVar {
|
|
|
357
445
|
}
|
|
358
446
|
}
|
|
359
447
|
|
|
448
|
+
export interface SSHPublicKeyValue {
|
|
449
|
+
name: string;
|
|
450
|
+
key: string;
|
|
451
|
+
}
|
|
452
|
+
export interface UserSSHPublicKey extends SSHPublicKeyValue {
|
|
453
|
+
id: string;
|
|
454
|
+
key: string;
|
|
455
|
+
userId: string;
|
|
456
|
+
fingerprint: string;
|
|
457
|
+
creationTime: string;
|
|
458
|
+
lastUsedTime?: string;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
export type UserSSHPublicKeyValue = Omit<UserSSHPublicKey, "key" | "userId">;
|
|
462
|
+
|
|
463
|
+
export namespace SSHPublicKeyValue {
|
|
464
|
+
export function validate(value: SSHPublicKeyValue): string | undefined {
|
|
465
|
+
if (value.name.length === 0) {
|
|
466
|
+
return "Title must not be empty.";
|
|
467
|
+
}
|
|
468
|
+
if (value.name.length > 255) {
|
|
469
|
+
return "Title too long. Maximum value length is 255 characters.";
|
|
470
|
+
}
|
|
471
|
+
if (value.key.length === 0) {
|
|
472
|
+
return "Key must not be empty.";
|
|
473
|
+
}
|
|
474
|
+
try {
|
|
475
|
+
getData(value);
|
|
476
|
+
} catch (e) {
|
|
477
|
+
return "Key is invalid. You must supply a key in OpenSSH public key format.";
|
|
478
|
+
}
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
export function getData(value: SSHPublicKeyValue) {
|
|
483
|
+
// Begins with 'ssh-rsa', 'ecdsa-sha2-nistp256', 'ecdsa-sha2-nistp384', 'ecdsa-sha2-nistp521', 'ssh-ed25519', 'sk-ecdsa-sha2-nistp256@openssh.com', or 'sk-ssh-ed25519@openssh.com'.
|
|
484
|
+
const regex =
|
|
485
|
+
/^(?<type>ssh-rsa|ecdsa-sha2-nistp256|ecdsa-sha2-nistp384|ecdsa-sha2-nistp521|ssh-ed25519|sk-ecdsa-sha2-nistp256@openssh\.com|sk-ssh-ed25519@openssh\.com) (?<key>.*?)( (?<email>.*?))?$/;
|
|
486
|
+
const resultGroup = regex.exec(value.key.trim());
|
|
487
|
+
if (!resultGroup) {
|
|
488
|
+
throw new Error("Key is invalid.");
|
|
489
|
+
}
|
|
490
|
+
return {
|
|
491
|
+
type: resultGroup.groups?.["type"] as string,
|
|
492
|
+
key: resultGroup.groups?.["key"] as string,
|
|
493
|
+
email: resultGroup.groups?.["email"] || undefined,
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
export function getFingerprint(value: SSHPublicKeyValue) {
|
|
498
|
+
const data = getData(value);
|
|
499
|
+
let buf = Buffer.from(data.key, "base64");
|
|
500
|
+
// gitlab style
|
|
501
|
+
// const hash = createHash("md5").update(buf).digest("hex");
|
|
502
|
+
// github style
|
|
503
|
+
const hash = createHash("sha256").update(buf).digest("base64");
|
|
504
|
+
return hash;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
export const MAXIMUM_KEY_LENGTH = 5;
|
|
508
|
+
}
|
|
509
|
+
|
|
360
510
|
export interface GitpodToken {
|
|
361
511
|
/** Hash value (SHA256) of the token (primary key). */
|
|
362
512
|
tokenHash: string;
|
|
@@ -565,7 +715,7 @@ export interface Workspace {
|
|
|
565
715
|
|
|
566
716
|
export type WorkspaceSoftDeletion = "user" | "gc";
|
|
567
717
|
|
|
568
|
-
export type WorkspaceType = "regular" | "prebuild" | "probe";
|
|
718
|
+
export type WorkspaceType = "regular" | "prebuild" | "probe"; // TODO(gpl) Removed prove here
|
|
569
719
|
|
|
570
720
|
export namespace Workspace {
|
|
571
721
|
export function getFullRepositoryName(ws: Workspace): string | undefined {
|
|
@@ -1352,3 +1502,11 @@ export interface Terms {
|
|
|
1352
1502
|
readonly content: string;
|
|
1353
1503
|
readonly formElements?: object;
|
|
1354
1504
|
}
|
|
1505
|
+
|
|
1506
|
+
export interface CostCenter {
|
|
1507
|
+
readonly id: string;
|
|
1508
|
+
/**
|
|
1509
|
+
* Unit: credits
|
|
1510
|
+
*/
|
|
1511
|
+
spendingLimit: number;
|
|
1512
|
+
}
|
|
@@ -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
|
/**
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import { PrebuiltWorkspaceState } from "./protocol";
|
|
8
8
|
import { v4 as uuidv4 } from "uuid";
|
|
9
9
|
import { DeepPartial } from "./util/deep-partial";
|
|
10
|
+
import { WebhookEvent } from "./webhook-event";
|
|
10
11
|
|
|
11
12
|
export interface ProjectConfig {
|
|
12
13
|
".gitpod.yml": string;
|
|
@@ -152,3 +153,15 @@ export interface TeamMembershipInvite {
|
|
|
152
153
|
/** This is a flag that triggers the HARD DELETION of this entity */
|
|
153
154
|
deleted?: boolean;
|
|
154
155
|
}
|
|
156
|
+
|
|
157
|
+
export interface PrebuildEvent {
|
|
158
|
+
id: string;
|
|
159
|
+
creationTime: string;
|
|
160
|
+
status: WebhookEvent.Status | WebhookEvent.PrebuildStatus;
|
|
161
|
+
message?: string;
|
|
162
|
+
prebuildId?: string;
|
|
163
|
+
projectId?: string;
|
|
164
|
+
cloneUrl?: string;
|
|
165
|
+
branch?: string;
|
|
166
|
+
commit?: string;
|
|
167
|
+
}
|
package/src/usage.ts
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
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 { User, WorkspaceType } from "./protocol";
|
|
8
|
+
|
|
9
|
+
export interface BillableSession {
|
|
10
|
+
// The id of the one paying the bill
|
|
11
|
+
attributionId: string;
|
|
12
|
+
|
|
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;
|
|
27
|
+
|
|
28
|
+
// When the workspace ended. Not set when the workspace is still running.
|
|
29
|
+
endTime?: string;
|
|
30
|
+
|
|
31
|
+
// The credits used for this session
|
|
32
|
+
credits: number;
|
|
33
|
+
|
|
34
|
+
// TODO - maybe
|
|
35
|
+
projectId?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface ExtendedBillableSession extends BillableSession {
|
|
39
|
+
contextURL?: string;
|
|
40
|
+
user?: Pick<User.Profile, "name" | "avatarURL">;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* This is a paginated request
|
|
45
|
+
*/
|
|
46
|
+
export interface ListBilledUsageRequest {
|
|
47
|
+
attributionId: string;
|
|
48
|
+
fromDate?: number;
|
|
49
|
+
toDate?: number;
|
|
50
|
+
perPage: number;
|
|
51
|
+
page: number;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface ListBilledUsageResponse {
|
|
55
|
+
sessions: ExtendedBillableSession[];
|
|
56
|
+
totalCreditsUsed: number;
|
|
57
|
+
totalPages: number;
|
|
58
|
+
totalSessions: number;
|
|
59
|
+
perPage: number;
|
|
60
|
+
page: number;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export type BillableWorkspaceType = WorkspaceType;
|
|
64
|
+
|
|
65
|
+
// types below are copied over from components/usage-api/typescript/src/usage/v1/usage_pb.d.ts
|
|
66
|
+
|
|
67
|
+
export interface ListUsageRequest {
|
|
68
|
+
attributionId: string;
|
|
69
|
+
from?: number;
|
|
70
|
+
to?: number;
|
|
71
|
+
order: Ordering;
|
|
72
|
+
pagination?: PaginationRequest;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export enum Ordering {
|
|
76
|
+
ORDERING_DESCENDING = 0,
|
|
77
|
+
ORDERING_ASCENDING = 1,
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface PaginationRequest {
|
|
81
|
+
perPage: number;
|
|
82
|
+
page: number;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface ListUsageResponse {
|
|
86
|
+
usageEntriesList: Usage[];
|
|
87
|
+
pagination?: PaginationResponse;
|
|
88
|
+
creditBalanceAtStart: number;
|
|
89
|
+
creditBalanceAtEnd: number;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export interface PaginationResponse {
|
|
93
|
+
perPage: number;
|
|
94
|
+
totalPages: number;
|
|
95
|
+
total: number;
|
|
96
|
+
page: number;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export type UsageKind = "workspaceinstance" | "invoice";
|
|
100
|
+
export interface Usage {
|
|
101
|
+
id: string;
|
|
102
|
+
attributionId: string;
|
|
103
|
+
description: string;
|
|
104
|
+
credits: number;
|
|
105
|
+
effectiveTime?: number;
|
|
106
|
+
kind: UsageKind;
|
|
107
|
+
workspaceInstanceId: string;
|
|
108
|
+
draft: boolean;
|
|
109
|
+
metadata: WorkspaceInstanceUsageData | InvoiceUsageData;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// the equivalent golang shape is maintained in `/workspace/gitpod/`components/usage/pkg/db/usage.go`
|
|
113
|
+
export interface WorkspaceInstanceUsageData {
|
|
114
|
+
workspaceid: string;
|
|
115
|
+
workspaceType: WorkspaceType;
|
|
116
|
+
workspaceClass: string;
|
|
117
|
+
contextURL: string;
|
|
118
|
+
startTime: string;
|
|
119
|
+
endTime?: string;
|
|
120
|
+
userName: string;
|
|
121
|
+
userAvatarURL: string;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export interface InvoiceUsageData {
|
|
125
|
+
invoiceId: string;
|
|
126
|
+
startDate: string;
|
|
127
|
+
endDate: string;
|
|
128
|
+
}
|
|
@@ -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();
|
|
@@ -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
|
package/src/util/timeutil.ts
CHANGED
|
@@ -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);
|
|
@@ -0,0 +1,55 @@
|
|
|
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
|
+
export interface WebhookEvent {
|
|
8
|
+
id: string;
|
|
9
|
+
creationTime: string;
|
|
10
|
+
type: "push" | string;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Typically the webhook installer is referenced here.
|
|
14
|
+
*/
|
|
15
|
+
authorizedUserId?: string;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* webhook event's payload
|
|
19
|
+
*/
|
|
20
|
+
rawEvent: string;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The general status of the received webhook event.
|
|
24
|
+
*/
|
|
25
|
+
status: WebhookEvent.Status;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Optional message to help understand errors with handling events.
|
|
29
|
+
*/
|
|
30
|
+
message?: string;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* If the webhook event is considered to trigger a prebuild, the `prebuildStatus`
|
|
34
|
+
* contains a more specific status.
|
|
35
|
+
*/
|
|
36
|
+
prebuildStatus?: WebhookEvent.PrebuildStatus;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* If `prebuildStatus` is `prebuild_triggered` this points to a prebuild.
|
|
40
|
+
*/
|
|
41
|
+
prebuildId?: string;
|
|
42
|
+
|
|
43
|
+
projectId?: string;
|
|
44
|
+
|
|
45
|
+
cloneUrl?: string;
|
|
46
|
+
|
|
47
|
+
branch?: string;
|
|
48
|
+
|
|
49
|
+
commit?: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export namespace WebhookEvent {
|
|
53
|
+
export type Status = "received" | "dismissed_unauthorized" | "ignored" | "processed";
|
|
54
|
+
export type PrebuildStatus = "ignored_unconfigured" | "prebuild_trigger_failed" | "prebuild_triggered";
|
|
55
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
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
|
+
export interface SupportedWorkspaceClass {
|
|
8
|
+
id: string;
|
|
9
|
+
category: string;
|
|
10
|
+
displayName: string;
|
|
11
|
+
description: string;
|
|
12
|
+
powerups: number;
|
|
13
|
+
isSelected: boolean;
|
|
14
|
+
}
|
package/src/workspace-cluster.ts
CHANGED
|
@@ -56,12 +56,10 @@ export type WorkspaceManagerConnectionInfo = Pick<WorkspaceCluster, "name" | "ur
|
|
|
56
56
|
export type AdmissionConstraint =
|
|
57
57
|
| AdmissionConstraintFeaturePreview
|
|
58
58
|
| AdmissionConstraintHasPermission
|
|
59
|
-
|
|
|
60
|
-
| AdmissionConstraintHasMoreResources;
|
|
59
|
+
| AdmissionConstraintHasClass;
|
|
61
60
|
export type AdmissionConstraintFeaturePreview = { type: "has-feature-preview" };
|
|
62
61
|
export type AdmissionConstraintHasPermission = { type: "has-permission"; permission: PermissionName };
|
|
63
|
-
export type
|
|
64
|
-
export type AdmissionConstraintHasMoreResources = { type: "has-more-resources" };
|
|
62
|
+
export type AdmissionConstraintHasClass = { type: "has-class"; id: string; displayName: string };
|
|
65
63
|
|
|
66
64
|
export namespace AdmissionConstraint {
|
|
67
65
|
export function is(o: any): o is AdmissionConstraint {
|