Skip to content

Commit 5bb465b

Browse files
authoredMar 6, 2025
Axon 201 implement experiment flow split (#174)
* AXON-201: refactored exp fetching from statsig + implemented new auth exp flow * AXON-201: fix tests * AXON-201: updates * AXON-201: fixes * AXON-201: updates
1 parent 172e307 commit 5bb465b

File tree

6 files changed

+65
-33
lines changed

6 files changed

+65
-33
lines changed
 

‎src/container.ts

+7-4
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ import { VSCWelcomeActionApi } from './webview/welcome/vscWelcomeActionApi';
6565
import { VSCWelcomeWebviewControllerFactory } from './webview/welcome/vscWelcomeWebviewControllerFactory';
6666
import { WelcomeAction } from './lib/ipc/fromUI/welcome';
6767
import { WelcomeInitMessage } from './lib/ipc/toUI/welcome';
68-
import { FeatureFlagClient, Features } from './util/featureFlags';
68+
import { Experiments, FeatureFlagClient, Features } from './util/featureFlags';
6969
import { EventBuilder } from './util/featureFlags/eventBuilder';
7070
import { AtlascodeUriHandler } from './uriHandler';
7171
import { CheckoutHelper } from './bitbucket/interfaces';
@@ -79,7 +79,7 @@ const isDebuggingRegex = /^--(debug|inspect)\b(-brk\b|(?!-))=?/;
7979
const ConfigTargetKey = 'configurationTarget';
8080

8181
export class Container {
82-
static initialize(context: ExtensionContext, config: IConfig, version: string) {
82+
static async initialize(context: ExtensionContext, config: IConfig, version: string) {
8383
const analyticsEnv: string = this.isDebugging ? 'staging' : 'prod';
8484

8585
this._analyticsClient = analyticsClient({
@@ -187,7 +187,7 @@ export class Container {
187187

188188
this._loginManager = new LoginManager(this._credentialManager, this._siteManager, this._analyticsClient);
189189
this._bitbucketHelper = new BitbucketCheckoutHelper(context.globalState);
190-
FeatureFlagClient.initialize({
190+
await FeatureFlagClient.initialize({
191191
analyticsClient: this._analyticsClient,
192192
identifiers: {
193193
analyticsAnonymousId: env.machineId,
@@ -200,6 +200,7 @@ export class Container {
200200
.finally(() => {
201201
this.initializeUriHandler(context, this._analyticsApi, this._bitbucketHelper);
202202
this.initializeNewSidebarView(context, config);
203+
this.initializeAAExperiment();
203204
});
204205

205206
context.subscriptions.push((this._helpExplorer = new HelpExplorer()));
@@ -209,7 +210,9 @@ export class Container {
209210
const telemetryConfig = workspace.getConfiguration('telemetry');
210211
return telemetryConfig.get<boolean>('enableTelemetry', true);
211212
}
212-
213+
static initializeAAExperiment() {
214+
FeatureFlagClient.checkExperimentValue(Experiments.AtlascodeAA);
215+
}
213216
static initializeUriHandler(
214217
context: ExtensionContext,
215218
analyticsApi: VSCAnalyticsApi,

‎src/extension.ts

+35-5
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import { registerResources } from './resources';
2323
import { GitExtension } from './typings/git';
2424
import { pid } from 'process';
2525
import { startListening } from './atlclients/negotiate';
26-
import { FeatureFlagClient } from './util/featureFlags';
26+
import { Experiments, FeatureFlagClient } from './util/featureFlags';
2727

2828
const AnalyticDelay = 5000;
2929

@@ -32,7 +32,6 @@ export async function activate(context: ExtensionContext) {
3232
const atlascode = extensions.getExtension('atlassian.atlascode')!;
3333
const atlascodeVersion = atlascode.packageJSON.version;
3434
const previousVersion = context.globalState.get<string>(GlobalStateVersionKey);
35-
3635
registerResources(context);
3736

3837
Configuration.configure(context);
@@ -42,7 +41,7 @@ export async function activate(context: ExtensionContext) {
4241
context.globalState.update('rulingPid', pid);
4342

4443
try {
45-
Container.initialize(context, configuration.get<IConfig>(), atlascodeVersion);
44+
await Container.initialize(context, configuration.get<IConfig>(), atlascodeVersion);
4645

4746
registerCommands(context);
4847
activateCodebucket(context);
@@ -63,11 +62,13 @@ export async function activate(context: ExtensionContext) {
6362
Container.clientManager.requestSite(site);
6463
});
6564

66-
if (previousVersion === undefined && window.state.focused) {
67-
commands.executeCommand(Commands.ShowOnboardingPage); //This is shown to users who have never opened our extension before
65+
// new user for auth exp
66+
if (previousVersion === undefined) {
67+
initializeNewAuthExperiment();
6868
} else {
6969
showWelcomePage(atlascodeVersion, previousVersion);
7070
}
71+
7172
const delay = Math.floor(Math.random() * Math.floor(AnalyticDelay));
7273
setTimeout(() => {
7374
sendAnalytics(atlascodeVersion, context.globalState);
@@ -165,6 +166,35 @@ async function sendAnalytics(version: string, globalState: Memento) {
165166
});
166167
}
167168

169+
function initializeNewAuthExperiment() {
170+
let onboardingFlow = FeatureFlagClient.checkExperimentValue(Experiments.NewAuthUI);
171+
Logger.debug(`Onboarding Experiment flow: ${onboardingFlow}`);
172+
if (!onboardingFlow) {
173+
onboardingFlow = 'control';
174+
}
175+
176+
switch (onboardingFlow) {
177+
case 'settings': {
178+
commands.executeCommand(Commands.ShowConfigPage);
179+
break;
180+
}
181+
case 'legacy': {
182+
commands.executeCommand(Commands.ShowOnboardingPage);
183+
break;
184+
}
185+
case 'new': {
186+
commands.executeCommand(Commands.ShowOnboardingPage);
187+
break;
188+
}
189+
default: {
190+
// control
191+
if (window.state.focused) {
192+
commands.executeCommand(Commands.ShowOnboardingPage);
193+
}
194+
break;
195+
}
196+
}
197+
}
168198
// this method is called when your extension is deactivated
169199
export function deactivate() {
170200
FeatureFlagClient.dispose();

‎src/react/atlascode/onboarding/OnboardingPage.tsx

+9-7
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@ import { OnboardingControllerContext, useOnboardingController } from './onboardi
1515
import ProductSelector from './ProductSelector';
1616
import { SimpleSiteAuthenticator } from './SimpleSiteAuthenticator';
1717
import { AtlascodeErrorBoundary } from '../common/ErrorBoundary';
18-
import { AnalyticsView } from 'src/analyticsTypes';
19-
import { Features } from 'src/util/featureFlags';
20-
import { CommonMessageType } from 'src/lib/ipc/toUI/common';
21-
import { OnboardingActionType } from 'src/lib/ipc/fromUI/onboarding';
18+
import { AnalyticsView } from '../../../analyticsTypes';
19+
import { Experiments } from '../../../util/featureFlags';
20+
import { CommonMessageType } from '../../../lib/ipc/toUI/common';
21+
import { OnboardingActionType } from '../../../lib/ipc/fromUI/onboarding';
2222
import { JiraOnboarding } from './JiraOnboarding';
2323
import { BitbucketOnboarding } from './BitbucketOnboarding';
2424

@@ -69,9 +69,11 @@ export const OnboardingPage: React.FunctionComponent = () => {
6969
React.useEffect(() => {
7070
window.addEventListener('message', (event) => {
7171
const message = event.data;
72-
if (message.command === CommonMessageType.UpdateFeatureFlags) {
73-
const featureValue = message.featureFlags[Features.EnableAuthUI];
74-
setUseAuthUI(featureValue);
72+
if (message.command === CommonMessageType.UpdateExperimentValues) {
73+
const experimentValue = message.experimentValues[Experiments.NewAuthUI];
74+
if (typeof experimentValue === 'string' && experimentValue !== undefined && experimentValue !== null) {
75+
setUseAuthUI(experimentValue === 'new');
76+
}
7577
}
7678
});
7779
}, [controller]);

‎src/util/featureFlags/client.test.ts

+9-7
Original file line numberDiff line numberDiff line change
@@ -102,19 +102,21 @@ describe('FeatureFlagClient', () => {
102102
expect(FeatureFlagClient.featureGates[featureName]).toBe(true);
103103
});
104104

105-
it('experiments default values are correctly assigned', async () => {
105+
it('experiments overrides are correctly applied', async () => {
106+
process.env.ATLASCODE_EXP_OVERRIDES_STRING = `${experimentName}=another value`;
106107
await FeatureFlagClient.initialize(options);
107-
expect(FeatureGates.getExperimentValue).toHaveBeenCalled();
108-
expect(Object.keys(FeatureFlagClient.experimentValues).length).toBe(1);
109108
expect(FeatureFlagClient.experimentValues[experimentName]).toBeDefined();
110-
expect(FeatureFlagClient.experimentValues[experimentName]).toBe('a default value');
109+
expect(FeatureFlagClient.experimentValues[experimentName]).toBe('another value');
111110
});
111+
});
112112

113-
it('experiments overrides are correctly applied', async () => {
114-
process.env.ATLASCODE_EXP_OVERRIDES_STRING = `${experimentName}=another value`;
113+
describe('checkExperimentValue', () => {
114+
it('should return the value of the experiment and save value in client', async () => {
115115
await FeatureFlagClient.initialize(options);
116+
const value = FeatureFlagClient.checkExperimentValue(experimentName);
116117
expect(FeatureFlagClient.experimentValues[experimentName]).toBeDefined();
117-
expect(FeatureFlagClient.experimentValues[experimentName]).toBe('another value');
118+
expect(FeatureFlagClient.experimentValues[experimentName]).toBe('a default value');
119+
expect(value).toBe('a default value');
118120
});
119121
});
120122
});

‎src/util/featureFlags/client.ts

+3-8
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ export class FeatureFlagClient {
7272

7373
private static finalizeInit(): void {
7474
this._featureGates = this.evaluateFeatures();
75-
this._experimentValues = this.evaluateExperiments();
75+
this._experimentValues = {} as ExperimentGateValues;
7676

7777
const ffSplit = (process.env.ATLASCODE_FF_OVERRIDES || '')
7878
.split(',')
@@ -136,7 +136,7 @@ export class FeatureFlagClient {
136136
return gateValue;
137137
}
138138

139-
private static checkExperimentValue(experiment: Experiments): any {
139+
static checkExperimentValue(experiment: Experiments): any {
140140
const experimentGate = ExperimentGates[experiment];
141141
if (!experimentGate) {
142142
return undefined;
@@ -153,6 +153,7 @@ export class FeatureFlagClient {
153153
);
154154
}
155155
console.log(`ExperimentGateValue: ${experiment} -> ${gateValue}`);
156+
this._experimentValues[experiment] = gateValue;
156157
return gateValue;
157158
}
158159

@@ -162,12 +163,6 @@ export class FeatureFlagClient {
162163
return featureFlags;
163164
}
164165

165-
private static evaluateExperiments(): ExperimentGateValues {
166-
const experimentGates = {} as ExperimentGateValues;
167-
Object.values(Experiments).forEach((exp) => (experimentGates[exp] = this.checkExperimentValue(exp)));
168-
return experimentGates;
169-
}
170-
171166
static dispose() {
172167
FeatureGates.shutdownStatsig();
173168
}

‎src/util/featureFlags/features.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ export enum Experiments {
1111

1212
export const ExperimentGates: Record<Experiments, ExperimentPayload> = {
1313
[Experiments.NewAuthUI]: {
14-
parameter: 'isEnabled',
15-
defaultValue: false,
14+
parameter: 'onboardingFlow',
15+
defaultValue: 'control',
1616
},
1717
[Experiments.AtlascodeAA]: {
1818
parameter: 'isEnabled',

0 commit comments

Comments
 (0)
Failed to load comments.