From 820f7cdc4354317d034704adf7e5a27f166125fa Mon Sep 17 00:00:00 2001 From: Christian Abella Date: Fri, 21 Feb 2025 13:37:35 -1000 Subject: [PATCH 1/2] AXON-144: added experiment support in atlascode --- src/lib/ipc/toUI/common.ts | 8 ++++- .../atlascode/onboarding/OnboardingPage.tsx | 18 +++++++++-- src/util/featureFlags/client.ts | 30 ++++++++++++++++++- src/util/featureFlags/features.ts | 20 +++++++++++++ src/webview/singleViewFactory.ts | 17 +++++++++-- 5 files changed, 86 insertions(+), 7 deletions(-) diff --git a/src/lib/ipc/toUI/common.ts b/src/lib/ipc/toUI/common.ts index 1970ca0f..fb912494 100644 --- a/src/lib/ipc/toUI/common.ts +++ b/src/lib/ipc/toUI/common.ts @@ -5,13 +5,15 @@ export enum CommonMessageType { OnlineStatus = 'onlineStatus', PMFStatus = 'pmfStatus', UpdateFeatureFlags = 'updateFeatureFlags', + UpdateExperimentValues = 'updateExperimentValues', } export type CommonMessage = | ReducerAction | ReducerAction | ReducerAction - | ReducerAction; + | ReducerAction + | ReducerAction; export interface HostErrorMessage { reason: string; @@ -28,3 +30,7 @@ export interface PMFMessage { export interface UpdateFeatureFlagsMessage { featureFlags: { [key: string]: boolean }; } + +export interface UpdateExperimentValuesMessage { + experimentValues: { [key: string]: any }; +} diff --git a/src/react/atlascode/onboarding/OnboardingPage.tsx b/src/react/atlascode/onboarding/OnboardingPage.tsx index 7e7016a2..7036cad5 100644 --- a/src/react/atlascode/onboarding/OnboardingPage.tsx +++ b/src/react/atlascode/onboarding/OnboardingPage.tsx @@ -21,6 +21,7 @@ import { CommonMessageType } from 'src/lib/ipc/toUI/common'; import { OnboardingActionType } from 'src/lib/ipc/fromUI/onboarding'; import { JiraOnboarding } from './JiraOnboarding'; import { BitbucketOnboarding } from './BitbucketOnboarding'; +import { Experiments } from 'src/util/featureFlags/features'; const useStyles = makeStyles((theme: Theme) => ({ root: { @@ -61,6 +62,7 @@ export const OnboardingPage: React.FunctionComponent = () => { const { authDialogController, authDialogOpen, authDialogProduct, authDialogEntry } = useAuthDialog(); const [activeStep, setActiveStep] = React.useState(0); const [useAuthUI, setUseAuthUI] = React.useState(false); + const [useAuthExperiment, setUseAuthExperiment] = React.useState(false); const [jiraSignInText, setJiraSignInText] = useState('Sign In to Jira Cloud'); const [bitbucketSignInText, setBitbucketSignInText] = useState('Sign In to Bitbucket Cloud'); const [jiraSignInFlow, setJiraSignInFlow] = useState(jiraValueSet.cloud); @@ -72,9 +74,21 @@ export const OnboardingPage: React.FunctionComponent = () => { if (message.command === CommonMessageType.UpdateFeatureFlags) { const featureValue = message.featureFlags[Features.EnableAuthUI]; setUseAuthUI(featureValue); + } else if (message.command === CommonMessageType.UpdateExperimentValues) { + let experimentValue: boolean; + try { + experimentValue = message.experimentValues[Experiments.NewAuthUIAA] as boolean; + } catch { + controller.postMessage({ + type: OnboardingActionType.Error, + error: new Error(`Experiment value not found`), + }); + experimentValue = false; + } + setUseAuthExperiment(experimentValue); } }); - }, []); + }, [controller]); function getSteps() { if (useAuthUI) { return ['Setup Jira', 'Setup BitBucket', 'Explore']; @@ -362,7 +376,7 @@ export const OnboardingPage: React.FunctionComponent = () => { padding: '24px', }} > - {useAuthUI ? authUI_v1 : oldAuthUI} + {useAuthUI && useAuthExperiment ? authUI_v1 : oldAuthUI} diff --git a/src/util/featureFlags/client.ts b/src/util/featureFlags/client.ts index c6c4739d..06fb2325 100644 --- a/src/util/featureFlags/client.ts +++ b/src/util/featureFlags/client.ts @@ -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, Experiments, Features } from './features'; export type FeatureFlagClientOptions = { analyticsClient: AnalyticsClient; @@ -66,6 +66,22 @@ export class FeatureFlagClient { return gateValue; } + public static checkExperimentValue(experiment: string): any { + let gateValue: any = undefined; + if (FeatureGates === null) { + console.warn('FeatureGates: FeatureGates is not initialized. Defaulting to Undefined'); + } else { + console.log(ExperimentGates[experiment]); + gateValue = FeatureGates.getExperimentValue( + ExperimentGates[experiment].gate, + ExperimentGates[experiment].parameter, + ExperimentGates[experiment].defaultValue, + ); + } + console.log(`ExperimentGateValue: ${experiment} -> ${gateValue}`); + return gateValue; + } + public static async evaluateFeatures() { const featureFlags = await Promise.all( Object.values(Features).map(async (feature) => { @@ -78,4 +94,16 @@ 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 }), {}); + } } diff --git a/src/util/featureFlags/features.ts b/src/util/featureFlags/features.ts index 5e17c856..6beb509e 100644 --- a/src/util/featureFlags/features.ts +++ b/src/util/featureFlags/features.ts @@ -2,3 +2,23 @@ export enum Features { EnableNewUriHandler = 'atlascode-enable-new-uri-handler', EnableAuthUI = 'atlascode-enable-auth-ui', } + +export enum Experiments { + NewAuthUI = 'atlascode_new_auth_ui', + NewAuthUIAA = 'atlascode_new_auth_ui_aa', +} + +export const ExperimentGates: ExperimentGate = { + [Experiments.NewAuthUI]: { + gate: 'atlascode_new_auth_ui', + parameter: 'isEnabled', + defaultValue: false, + }, + [Experiments.NewAuthUIAA]: { + gate: 'atlascode_new_auth_ui_aa', + parameter: 'isEnabled', + defaultValue: `bruh`, + }, +}; + +type ExperimentGate = { [key: string]: { gate: string; parameter: string; defaultValue: any } }; diff --git a/src/webview/singleViewFactory.ts b/src/webview/singleViewFactory.ts index c019bbea..f25c5235 100644 --- a/src/webview/singleViewFactory.ts +++ b/src/webview/singleViewFactory.ts @@ -133,9 +133,20 @@ export class SingleWebview implements ReactWebview { } // 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 = await FeatureFlagClient.evaluateFeatures(); + console.log(`FeatureGates: sending ${JSON.stringify(featureGates)}`); + this.postMessage({ command: CommonMessageType.UpdateFeatureFlags, featureFlags: featureGates }); + } + + private async fireExperimentGates() { + const experimentValues = await FeatureFlagClient.evaluateExperiments(); + console.log(`ExperimentValues: sending ${JSON.stringify(experimentValues)}`); + this.postMessage({ command: CommonMessageType.UpdateExperimentValues, experimentValues }); } private onViewStateChanged(e: WebviewPanelOnDidChangeViewStateEvent) { From 0d9d3657e42b5d1724faf10842285cc7fdca64c7 Mon Sep 17 00:00:00 2001 From: Christian Abella Date: Fri, 21 Feb 2025 14:13:22 -1000 Subject: [PATCH 2/2] AXON-144 fix default value on fail --- src/util/featureFlags/client.ts | 13 ++++++++----- src/util/featureFlags/features.ts | 2 +- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/util/featureFlags/client.ts b/src/util/featureFlags/client.ts index 06fb2325..6cbee10e 100644 --- a/src/util/featureFlags/client.ts +++ b/src/util/featureFlags/client.ts @@ -67,15 +67,18 @@ export class FeatureFlagClient { } public static checkExperimentValue(experiment: string): any { - let gateValue: any = undefined; + let gateValue: any; + const experimentGate = ExperimentGates[experiment]; if (FeatureGates === null) { - console.warn('FeatureGates: FeatureGates is not initialized. Defaulting to Undefined'); + console.warn( + `FeatureGates: FeatureGates is not initialized. Returning default value: ${experimentGate.defaultValue}`, + ); } else { console.log(ExperimentGates[experiment]); gateValue = FeatureGates.getExperimentValue( - ExperimentGates[experiment].gate, - ExperimentGates[experiment].parameter, - ExperimentGates[experiment].defaultValue, + experimentGate.gate, + experimentGate.parameter, + experimentGate.defaultValue, ); } console.log(`ExperimentGateValue: ${experiment} -> ${gateValue}`); diff --git a/src/util/featureFlags/features.ts b/src/util/featureFlags/features.ts index 6beb509e..e073ef82 100644 --- a/src/util/featureFlags/features.ts +++ b/src/util/featureFlags/features.ts @@ -17,7 +17,7 @@ export const ExperimentGates: ExperimentGate = { [Experiments.NewAuthUIAA]: { gate: 'atlascode_new_auth_ui_aa', parameter: 'isEnabled', - defaultValue: `bruh`, + defaultValue: false, }, };