Skip to content

Commit

Permalink
Axon 144 experiment auth UI flow statsig set up (#143)
Browse files Browse the repository at this point in the history
* AXON-144: added experiment support in atlascode

* AXON-144 fix default value on fail

* AXON-144: cleaned up AA exp code and changed fg and exp value fetching to initialization to avoid refetching

* AXON-144: added static field for FeatureFlagClient to hold flags and exp + added error handling to exp fetching

* AXON-144: fix import

* AXON-144: refactor error handling
  • Loading branch information
cabella-dot authored Feb 25, 2025
1 parent 453b2c1 commit 8f5bd85
Show file tree
Hide file tree
Showing 7 changed files with 98 additions and 8 deletions.
2 changes: 1 addition & 1 deletion src/container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ export class Container {
analyticsApi: VSCAnalyticsApi,
bitbucketHelper: CheckoutHelper,
) {
if (FeatureFlagClient.checkGate(Features.EnableNewUriHandler)) {
if (FeatureFlagClient.featureGates[Features.EnableNewUriHandler]) {
console.log('Using new URI handler');
context.subscriptions.push(AtlascodeUriHandler.create(analyticsApi, bitbucketHelper));
} else {
Expand Down
5 changes: 4 additions & 1 deletion src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { registerResources } from './resources';
import { GitExtension } from './typings/git';
import { pid } from 'process';
import { startListening } from './atlclients/negotiate';
import { FeatureFlagClient } from './util/featureFlags';

const AnalyticDelay = 5000;

Expand Down Expand Up @@ -165,4 +166,6 @@ async function sendAnalytics(version: string, globalState: Memento) {
}

// this method is called when your extension is deactivated
export function deactivate() {}
export function deactivate() {
FeatureFlagClient.dispose();
}
8 changes: 7 additions & 1 deletion src/lib/ipc/toUI/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@ export enum CommonMessageType {
OnlineStatus = 'onlineStatus',
PMFStatus = 'pmfStatus',
UpdateFeatureFlags = 'updateFeatureFlags',
UpdateExperimentValues = 'updateExperimentValues',
}

export type CommonMessage =
| ReducerAction<CommonMessageType.Error, HostErrorMessage>
| ReducerAction<CommonMessageType.OnlineStatus, OnlineStatusMessage>
| ReducerAction<CommonMessageType.PMFStatus, PMFMessage>
| ReducerAction<CommonMessageType.UpdateFeatureFlags, UpdateFeatureFlagsMessage>;
| ReducerAction<CommonMessageType.UpdateFeatureFlags, UpdateFeatureFlagsMessage>
| ReducerAction<CommonMessageType.UpdateExperimentValues, UpdateExperimentValuesMessage>;

export interface HostErrorMessage {
reason: string;
Expand All @@ -28,3 +30,7 @@ export interface PMFMessage {
export interface UpdateFeatureFlagsMessage {
featureFlags: { [key: string]: boolean };
}

export interface UpdateExperimentValuesMessage {
experimentValues: { [key: string]: any };
}
2 changes: 1 addition & 1 deletion src/react/atlascode/onboarding/OnboardingPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ export const OnboardingPage: React.FunctionComponent = () => {
setUseAuthUI(featureValue);
}
});
}, []);
}, [controller]);
function getSteps() {
if (useAuthUI) {
return ['Setup Jira', 'Setup BitBucket', 'Explore'];
Expand Down
53 changes: 52 additions & 1 deletion src/util/featureFlags/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { AnalyticsClient } from '../../analytics-node-client/src/client.min';

import FeatureGates, { FeatureGateEnvironment, Identifiers } from '@atlaskit/feature-gate-js-client';
import { AnalyticsClientMapper, EventBuilderInterface } from './analytics';
import { Features } from './features';
import { ExperimentGates, ExperimentGateValues, Experiments, FeatureGateValues, Features } from './features';

export type FeatureFlagClientOptions = {
analyticsClient: AnalyticsClient;
Expand All @@ -13,6 +13,15 @@ export type FeatureFlagClientOptions = {
export class FeatureFlagClient {
private static analyticsClient: AnalyticsClientMapper;
private static eventBuilder: EventBuilderInterface;
private static _featureGates: FeatureGateValues;
static get featureGates(): FeatureGateValues {
return this._featureGates;
}

private static _experimentValues: ExperimentGateValues;
static get experimentValues(): ExperimentGateValues {
return this._experimentValues;
}

public static async initialize(options: FeatureFlagClientOptions): Promise<void> {
const targetApp = process.env.ATLASCODE_FX3_TARGET_APP || '';
Expand Down Expand Up @@ -45,6 +54,10 @@ export class FeatureFlagClient {
console.log(`FeatureGates: ${feat} -> ${FeatureGates.checkGate(feat)}`);
}
})
.then(async () => {
this._featureGates = await this.evaluateFeatures();
this._experimentValues = await this.evaluateExperiments();
})
.catch((err) => {
console.warn(`FeatureGates: Failed to initialize client. ${err}`);
console.warn('FeatureGates: Disabling feature flags');
Expand All @@ -66,6 +79,28 @@ export class FeatureFlagClient {
return gateValue;
}

public static checkExperimentValue(experiment: string): any {
let gateValue: any;
const experimentGate = ExperimentGates[experiment];
if (!experimentGate) {
return undefined;
}
if (FeatureGates === null) {
console.warn(
`FeatureGates: FeatureGates is not initialized. Returning default value: ${experimentGate.defaultValue}`,
);
gateValue = experimentGate.defaultValue;
} else {
gateValue = FeatureGates.getExperimentValue(
experimentGate.gate,
experimentGate.parameter,
experimentGate.defaultValue,
);
}
console.log(`ExperimentGateValue: ${experiment} -> ${gateValue}`);
return gateValue;
}

public static async evaluateFeatures() {
const featureFlags = await Promise.all(
Object.values(Features).map(async (feature) => {
Expand All @@ -78,4 +113,20 @@ export class FeatureFlagClient {

return featureFlags.reduce((acc, val) => ({ ...acc, ...val }), {});
}

public static async evaluateExperiments() {
const experimentGates = await Promise.all(
Object.values(Experiments).map(async (experiment) => {
return {
[experiment]: await this.checkExperimentValue(experiment),
};
}),
);

return experimentGates.reduce((acc, val) => ({ ...acc, ...val }), {});
}

static dispose() {
FeatureGates.shutdownStatsig();
}
}
19 changes: 19 additions & 0 deletions src/util/featureFlags/features.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,22 @@ export enum Features {
EnableNewUriHandler = 'atlascode-enable-new-uri-handler',
EnableAuthUI = 'atlascode-enable-auth-ui',
}

export enum Experiments {
NewAuthUI = 'atlascode_new_auth_ui',
}

export const ExperimentGates: ExperimentGate = {
[Experiments.NewAuthUI]: {
gate: 'atlascode_new_auth_ui',
parameter: 'isEnabled',
defaultValue: false,
},
};

type ExperimentPayload = { gate: string; parameter: string; defaultValue: any };
type ExperimentGate = Record<string, ExperimentPayload>;

export type FeatureGateValues = Record<string, boolean>;

export type ExperimentGateValues = Record<string, any>;
17 changes: 14 additions & 3 deletions src/webview/singleViewFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,9 +133,20 @@ export class SingleWebview<FD, R> implements ReactWebview<FD> {
}

// Send feature gates to the panel in a message
const featureFlags = await FeatureFlagClient.evaluateFeatures();
console.log(`FeatureGates: sending ${JSON.stringify(featureFlags)}`);
this.postMessage({ command: CommonMessageType.UpdateFeatureFlags, featureFlags: featureFlags });
this.fireFeatureGates();
this.fireExperimentGates();
}

private async fireFeatureGates() {
const featureGates = FeatureFlagClient.featureGates;
console.log(`FeatureGates: sending ${JSON.stringify(featureGates)}`);
this.postMessage({ command: CommonMessageType.UpdateFeatureFlags, featureFlags: featureGates });
}

private async fireExperimentGates() {
const experimentValues = FeatureFlagClient.experimentValues;
console.log(`ExperimentValues: sending ${JSON.stringify(experimentValues)}`);
this.postMessage({ command: CommonMessageType.UpdateExperimentValues, experimentValues });
}

private onViewStateChanged(e: WebviewPanelOnDidChangeViewStateEvent) {
Expand Down

0 comments on commit 8f5bd85

Please sign in to comment.