Skip to content

Commit 9329b13

Browse files
committed
Add experiments using ConfigCat with PortsView
1 parent 06783e7 commit 9329b13

File tree

6 files changed

+130
-8
lines changed

6 files changed

+130
-8
lines changed

Diff for: extensions/gitpod-shared/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"@gitpod/gitpod-protocol": "main",
2222
"@gitpod/supervisor-api-grpc": "main",
2323
"bufferutil": "^4.0.1",
24+
"configcat-node": "^8.0.0",
2425
"js-yaml": "^4.1.0",
2526
"reconnecting-websocket": "^4.4.0",
2627
"utf-8-validate": "^5.0.2",

Diff for: extensions/gitpod-shared/src/experiments.ts

+89
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Gitpod. All rights reserved.
3+
*--------------------------------------------------------------------------------------------*/
4+
5+
import * as vscode from 'vscode';
6+
import * as configcat from 'configcat-node';
7+
import * as configcatcommon from 'configcat-common';
8+
import * as semver from 'semver';
9+
import Log from './common/logger';
10+
11+
const EXPERTIMENTAL_SETTINGS = [
12+
'gitpod.experimental.portsView.enabled',
13+
];
14+
15+
export class ExperimentalSettings {
16+
private configcatClient: configcatcommon.IConfigCatClient;
17+
private extensionVersion: semver.SemVer;
18+
19+
constructor(key: string, extensionVersion: string, private logger: Log) {
20+
this.configcatClient = configcat.createClientWithLazyLoad(key, {
21+
logger: {
22+
debug(): void { },
23+
log(): void { },
24+
info(): void { },
25+
warn(message: string): void { logger.warn(`ConfigCat: ${message}`); },
26+
error(message: string): void { logger.error(`ConfigCat: ${message}`); }
27+
},
28+
requestTimeoutMs: 1500,
29+
cacheTimeToLiveSeconds: 60
30+
});
31+
this.extensionVersion = new semver.SemVer(extensionVersion);
32+
}
33+
34+
async get<T>(key: string, userId?: string): Promise<T | undefined> {
35+
const config = vscode.workspace.getConfiguration('gitpod');
36+
const values = config.inspect<T>(key.substring('gitpod.'.length));
37+
if (!values || !EXPERTIMENTAL_SETTINGS.includes(key)) {
38+
this.logger.error(`Cannot get invalid experimental setting '${key}'`);
39+
return values?.globalValue ?? values?.defaultValue;
40+
}
41+
if (this.isPreRelease()) {
42+
// PreRelease versions always have experiments enabled by default
43+
return values.globalValue ?? values.defaultValue;
44+
}
45+
if (values.globalValue !== undefined) {
46+
// User setting have priority over configcat so return early
47+
return values.globalValue;
48+
}
49+
50+
const user = userId ? new configcatcommon.User(userId) : undefined;
51+
const configcatKey = key.replace(/\./g, '_'); // '.' are not allowed in configcat
52+
const experimentValue = (await this.configcatClient.getValueAsync(configcatKey, undefined, user)) as T | undefined;
53+
54+
return experimentValue ?? values.defaultValue;
55+
}
56+
57+
async inspect<T>(key: string, userId?: string): Promise<{ key: string; defaultValue?: T; globalValue?: T; experimentValue?: T } | undefined> {
58+
const config = vscode.workspace.getConfiguration('gitpod');
59+
const values = config.inspect<T>(key.substring('gitpod.'.length));
60+
if (!values || !EXPERTIMENTAL_SETTINGS.includes(key)) {
61+
this.logger.error(`Cannot inspect invalid experimental setting '${key}'`);
62+
return values;
63+
}
64+
65+
const user = userId ? new configcatcommon.User(userId) : undefined;
66+
const configcatKey = key.replace(/\./g, '_'); // '.' are not allowed in configcat
67+
const experimentValue = (await this.configcatClient.getValueAsync(configcatKey, undefined, user)) as T | undefined;
68+
69+
return { key, defaultValue: values.defaultValue, globalValue: values.globalValue, experimentValue };
70+
}
71+
72+
isUserOverride(key: string): boolean {
73+
const config = vscode.workspace.getConfiguration('gitpod');
74+
const values = config.inspect(key.substring('gitpod.'.length));
75+
return values?.globalValue !== undefined;
76+
}
77+
78+
forceRefreshAsync(): Promise<void> {
79+
return this.configcatClient.forceRefreshAsync();
80+
}
81+
82+
private isPreRelease() {
83+
return this.extensionVersion.minor % 2 === 1;
84+
}
85+
86+
dispose(): void {
87+
this.configcatClient.dispose();
88+
}
89+
}

Diff for: extensions/gitpod-shared/src/extension.ts

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { registerActiveLanguageAnalytics, registerUsageAnalytics } from './analy
77
import { createGitpodExtensionContext, GitpodExtensionContext, registerDefaultLayout, registerNotifications, registerWorkspaceCommands, registerWorkspaceSharing, registerWorkspaceTimeout } from './features';
88

99
export { GitpodExtensionContext, registerTasks, SupervisorConnection, registerIpcHookCli } from './features';
10+
export { ExperimentalSettings } from './experiments';
1011
export * from './gitpod-plugin-model';
1112

1213
export async function setupGitpodContext(context: vscode.ExtensionContext): Promise<GitpodExtensionContext | undefined> {

Diff for: extensions/gitpod-web/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
"engines": {
1717
"vscode": "^1.58.2"
1818
},
19+
"configcatKey": "WBLaCPtkjkqKHlHedziE9g/LEAOCNkbuUKiqUZAcVg7dw",
1920
"enabledApiProposals": [
2021
"resolvers",
2122
"contribViewsRemote",

Diff for: extensions/gitpod-web/src/extension.ts

+20-8
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import * as grpc from '@grpc/grpc-js';
99
import * as fs from 'fs';
1010
import * as os from 'os';
1111
import * as uuid from 'uuid';
12-
import { GitpodPluginModel, GitpodExtensionContext, setupGitpodContext, registerTasks, registerIpcHookCli } from 'gitpod-shared';
12+
import { GitpodPluginModel, GitpodExtensionContext, setupGitpodContext, registerTasks, registerIpcHookCli, ExperimentalSettings } from 'gitpod-shared';
1313
import { GetTokenRequest } from '@gitpod/supervisor-api-grpc/lib/token_pb';
1414
import { PortsStatus, ExposedPortInfo, PortsStatusRequest, PortsStatusResponse, PortVisibility, OnPortExposedAction } from '@gitpod/supervisor-api-grpc/lib/status_pb';
1515
import { TunnelVisiblity, TunnelPortRequest, RetryAutoExposeRequest, CloseTunnelRequest } from '@gitpod/supervisor-api-grpc/lib/port_pb';
@@ -516,8 +516,8 @@ function getNonce() {
516516

517517
interface PortItem { port: GitpodWorkspacePort; isWebview?: boolean }
518518

519-
function registerPorts(context: GitpodExtensionContext): void {
520-
const isPortsViewExperimentEnable = vscode.workspace.getConfiguration('gitpod.experimental.portsView').get<boolean>('enabled');
519+
async function registerPorts(context: GitpodExtensionContext): Promise<void> {
520+
const isPortsViewExperimentEnable = await getPortsViewExperimentEnable();
521521

522522
const portMap = new Map<number, GitpodWorkspacePort>();
523523
const tunnelMap = new Map<number, vscode.TunnelDescription>();
@@ -577,6 +577,18 @@ function registerPorts(context: GitpodExtensionContext): void {
577577
}
578578
});
579579
}
580+
581+
const packageJSON = context.extension.packageJSON;
582+
const experiments = new ExperimentalSettings(packageJSON.configcatKey, packageJSON.version, context.logger);
583+
context.subscriptions.push(experiments);
584+
585+
const isSaaSGitpod = context.info.getGitpodHost() === 'https://gitpod.io';
586+
async function getPortsViewExperimentEnable(): Promise<boolean> {
587+
return isSaaSGitpod
588+
? (await experiments.get<boolean>('gitpod.experimental.portsView.enabled', (await context.user).id))!
589+
: vscode.workspace.getConfiguration('gitpod').get<boolean>('experimental.portsView.enabled')!;
590+
}
591+
580592
context.subscriptions.push(observePortsStatus());
581593
context.subscriptions.push(vscode.commands.registerCommand('gitpod.resolveExternalPort', (portNumber: number) => {
582594
// eslint-disable-next-line no-async-promise-executor
@@ -657,7 +669,7 @@ function registerPorts(context: GitpodExtensionContext): void {
657669

658670
const portsStatusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right);
659671
context.subscriptions.push(portsStatusBarItem);
660-
function updateStatusBar(): void {
672+
async function updateStatusBar(): Promise<void> {
661673
const exposedPorts: number[] = [];
662674

663675
for (const port of portMap.values()) {
@@ -679,8 +691,8 @@ function registerPorts(context: GitpodExtensionContext): void {
679691

680692
portsStatusBarItem.text = text;
681693
portsStatusBarItem.tooltip = tooltip;
682-
const isPortsViewExperimentEnable = vscode.workspace.getConfiguration('gitpod.experimental.portsView').get<boolean>('enabled');
683-
portsStatusBarItem.command = isPortsViewExperimentEnable ? 'gitpod.portsView.focus' : 'gitpod.ports.reveal';
694+
695+
portsStatusBarItem.command = (await getPortsViewExperimentEnable()) ? 'gitpod.portsView.focus' : 'gitpod.ports.reveal';
684696
portsStatusBarItem.show();
685697
}
686698
updateStatusBar();
@@ -820,11 +832,11 @@ function registerPorts(context: GitpodExtensionContext): void {
820832
vscode.commands.executeCommand('gitpod.api.connectLocalApp', apiPort);
821833
}
822834
}));
823-
vscode.workspace.onDidChangeConfiguration((e: vscode.ConfigurationChangeEvent) => {
835+
vscode.workspace.onDidChangeConfiguration(async (e: vscode.ConfigurationChangeEvent) => {
824836
if (!e.affectsConfiguration('gitpod.experimental.portsView.enabled')) {
825837
return;
826838
}
827-
const isPortsViewExperimentEnable = vscode.workspace.getConfiguration('gitpod.experimental.portsView').get<boolean>('enabled');
839+
const isPortsViewExperimentEnable = await getPortsViewExperimentEnable();
828840
vscode.commands.executeCommand('setContext', 'gitpod.portsView.visible', isPortsViewExperimentEnable);
829841
gitpodWorkspaceTreeDataProvider.updateIsPortsViewExperimentEnable(isPortsViewExperimentEnable ?? false);
830842
updateStatusBar();

Diff for: extensions/yarn.lock

+18
Original file line numberDiff line numberDiff line change
@@ -479,6 +479,19 @@ [email protected]:
479479
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
480480
integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==
481481

482+
configcat-common@^6.0.0:
483+
version "6.0.0"
484+
resolved "https://registry.yarnpkg.com/configcat-common/-/configcat-common-6.0.0.tgz#ccdb9bdafcb6a89144cac17faaab60ac960fed2a"
485+
integrity sha512-C/lCeTKiFk9kPElRF3f4zIkvVCLKgPJuzrKbIMHCru89mvfH5t4//hZ9TW8wPJOAje6xB6ZALutDiIxggwUvWA==
486+
487+
configcat-node@^8.0.0:
488+
version "8.0.0"
489+
resolved "https://registry.yarnpkg.com/configcat-node/-/configcat-node-8.0.0.tgz#6a7d2072a848552971d91e2e44c424bfda606d21"
490+
integrity sha512-4n4yLMpXWEiB4vmj0HuV3ArgImOEHgT+ZhP+y6N6zdwP1Z4KhQHA3btbDtZbqNw1meaVzhQMjRnpV+k/3Zr8XQ==
491+
dependencies:
492+
configcat-common "^6.0.0"
493+
tunnel "0.0.6"
494+
482495
cookie@^0.4.2:
483496
version "0.4.2"
484497
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.2.tgz#0e41f24de5ecf317947c82fc789e06a884824432"
@@ -1673,6 +1686,11 @@ tr46@~0.0.3:
16731686
resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a"
16741687
integrity sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=
16751688

1689+
1690+
version "0.0.6"
1691+
resolved "https://registry.yarnpkg.com/tunnel/-/tunnel-0.0.6.tgz#72f1314b34a5b192db012324df2cc587ca47f92c"
1692+
integrity sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==
1693+
16761694
16771695
version "4.8.2"
16781696
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.8.2.tgz#e3b33d5ccfb5914e4eeab6699cf208adee3fd790"

0 commit comments

Comments
 (0)