From 9b32a6a9b7589dfe4f3491229c6f78a43ce5193a Mon Sep 17 00:00:00 2001 From: Vinicius de Lacerda Date: Thu, 6 Jun 2024 16:20:38 +0200 Subject: [PATCH] Remove boolean toggle remnants --- .changeset/silly-schools-hunt.md | 14 ++++ docs/booleanToggles.md | 82 -------------------- docs/dead-code-removal.md | 28 ++----- docs/optimizely-integration.md | 6 +- docs/simple-integration.md | 10 --- packages/lib/src/integrations/optimizely.ts | 39 ++++++---- packages/lib/src/integrations/simple.test.ts | 26 +------ packages/lib/src/integrations/simple.ts | 30 +------ packages/lib/src/types.ts | 3 - 9 files changed, 53 insertions(+), 185 deletions(-) create mode 100644 .changeset/silly-schools-hunt.md delete mode 100644 docs/booleanToggles.md diff --git a/.changeset/silly-schools-hunt.md b/.changeset/silly-schools-hunt.md new file mode 100644 index 0000000..0bfac80 --- /dev/null +++ b/.changeset/silly-schools-hunt.md @@ -0,0 +1,14 @@ +--- +'opticks': major +--- + +Upgrade Opticks optimizely integration to support Optimizely's JS SDK v5.3.2 + +Non-breaking changes: + +- `getOrSetCachedFeatureEnabled` is renamed to `getToggleDecisionStatus` + +Breaking changes: + +- `__checkIfUserIsInAudience`, an internal from Optimizely got updated to `checkIfUserIsInAudience`. Hence you need the version 5.3.2 of the JS SDK to run this version of opticks properly +- Removed deprecated `booleanToggle` and `getEnabledFeatures` and all mentions of it diff --git a/docs/booleanToggles.md b/docs/booleanToggles.md deleted file mode 100644 index dab353c..0000000 --- a/docs/booleanToggles.md +++ /dev/null @@ -1,82 +0,0 @@ -# Boolean Toggles - -## Boolean Toggles: Reading values - -Boolean Toggles as the name suggest, are either on or off. - -``` -booleanToggle('shouldShowSomething') // true or false -``` - -While these are simple to implement, using them directly tends to create code -that's hard to clean up fully. - -Consider the following toggle implementation: - -``` -// before -if (booleanToggle('foo')) foo() -if (booleanToggle('bar')) bar() -``` - -After the experiments concluded with `foo` winning and `bar` losing: - -``` -// after -if (true) foo() -if (false) bar() // never needed -``` - -While it works functionally, and code optimizers can eliminate any dead code -when generating a build, it still clutters your source code requiring manual -clean up efforts. - -You could assign a variable to add semantics: - -``` -// before -const shouldShowWarning = booleanToggle('warningToggle') -// ... -shouldShowWarning && renderWarning() -``` - -After toggle is true: - -``` -// after -const shouldShowWarning = true -// ... -shouldShowWarning && renderWarning() -``` - -Slightly better, but it would be great if we can prune dead code altogether, -considering the following would remain if the flag is false: - -``` -// after -const shouldShowWarning = false -// ... -shouldShowWarning && renderWarning() -``` - -## Boolean Toggles: Executing code - -You can pass a function to execute when a `toggle` is true. This can reduce the -amount of dangling leftover code after cleanup. - -``` -// before -always() -booleanToggle('warningToggle', () => renderWarning()) -``` - -``` -// after toggle is true -always() -renderWarning() -``` - -``` -// after toggle is false -always() -``` diff --git a/docs/dead-code-removal.md b/docs/dead-code-removal.md index 87df434..b08cd51 100644 --- a/docs/dead-code-removal.md +++ b/docs/dead-code-removal.md @@ -14,7 +14,7 @@ winners: - winning values are kept - for functions, the function body is kept -For the losing boolean toggles and losing multi toggle variants: +For the losing multi toggle variants: - losing toggles are pruned - if the losing side is a JSXExpression, we clean it up including the variables @@ -36,15 +36,13 @@ information. ## Running the codemods -There two codemods supplied with Opticks, one for Boolean Toggles, one for -Toggles, they can be found in the `src/transform` directory. +There is a codemod supplied with Opticks, that can be found in the `src/transform` directory. In order to clean all "losing" branches of the code, the codemods need to know -which toggle you're modifying, whether the toggle (for Boolean Toggles) or which -variation (for Multi Toggles) "won". +which toggle you're modifying, and which variation "won". Assuming you're running the codemods directly from the Opticks directory in your -`node_modules`, to declare the `b` side of the `foo` Multi Toggle the winner and +`node_modules`, to declare the `b` side of the `foo` Toggle the winner and prune all losing code in the `src` directory, run the script as follows: ```shell @@ -74,28 +72,17 @@ npm run clean:toggle -- --toggle=foo --winner=b The codemods are designed to work with TypeScript and they expect the `tsx` parser to be used. You can override the parser option from the consuming project to parse code other than TypeScript, but not all patterns might be cleaned up as intended. -### Boolean Toggles - -Boolean Toggle clean up works in a similar way, noting the winner accepts a -string value `'true'` or `'false'`: - -```shell -npm run clean:booleanToggle -- --toggle=foo --winner='true' -``` - ### Overriding the library import settings By default the codemods make assumptions on the name of the imports to clean, namely: ```typescript -import {booleanToggle} from 'opticks' -// or import {toggle} from 'opticks' ``` You can override these values via: -`--functionName=myLocalNameForMultiOrBooleanToggle` and +`--functionName=myLocalNameToggle` and `--packageName=myNameForOpticks` ## Cleaning Examples and Recipes @@ -132,7 +119,7 @@ const result = toggle('toggleFoo') // 'b' ``` A more interesting experiment would execute conditional code based on a multi -toggle. The trick here is that like Boolean Toggles, toggles accepts +toggle. The trick here is that toggles accept functions that will be executed only for a particular experiment branch. For example: @@ -225,8 +212,7 @@ of business logic. ### Conditional rendering The simplest way to render something conditionally is to assign a boolean to a -variable. Though this is probably better done with a boolean toggle, it's -possible with a toggle as well: +variable. ```typescript const shouldRenderIcon = toggle('SomethingWithIcon', false, true) diff --git a/docs/optimizely-integration.md b/docs/optimizely-integration.md index 7f026b9..36b84f0 100644 --- a/docs/optimizely-integration.md +++ b/docs/optimizely-integration.md @@ -93,11 +93,9 @@ forwarded to Optimizely with each call. ### The DataFile The Opticks Optimizely integration makes some assumptions on how the experiments -are set up. Optimizely supports two types of flags, "Feature Flags" (Boolean -Toggles in Opticks) and Experiments (Multi Toggles in Opticks). +are set up. Optimizely supports two types of flags, "Feature Flags" and Experiments . The Opticks library uses certain conventions to wrap both concepts in a -predictable API, where experiment variations are in the `a`, `b`, `c` format and -Boolean Toggles return only `true` or `false`. +predictable API, where experiment variations are in the `a`, `b`, `c` format. The following is subject to change, but right now Opticks uses both Feature Flags and the Experiments concepts of the Optimizely SDK which means you'll need diff --git a/docs/simple-integration.md b/docs/simple-integration.md index da19363..4f2429b 100644 --- a/docs/simple-integration.md +++ b/docs/simple-integration.md @@ -7,16 +7,6 @@ concern itself with generating a list of toggles or experiments, as there are many libraries and services out there doing it well already. It should be easy to write an adapter for whichever experimentation service or tool you're using. -## Boolean Toggles - -### Setting Toggles - -``` -setBooleanToggles({ foo: true, bar: false }) -``` - -## Multi Toggles - ### Setting Toggles ``` diff --git a/packages/lib/src/integrations/optimizely.ts b/packages/lib/src/integrations/optimizely.ts index 7c4e9b4..80cd526 100644 --- a/packages/lib/src/integrations/optimizely.ts +++ b/packages/lib/src/integrations/optimizely.ts @@ -1,4 +1,10 @@ -import OptimizelyLib, {EventDispatcher, Client, OptimizelyUserContext, NotificationListener, ListenerPayload} from '@optimizely/optimizely-sdk' +import OptimizelyLib, { + EventDispatcher, + Client, + OptimizelyUserContext, + NotificationListener, + ListenerPayload +} from '@optimizely/optimizely-sdk' import {ToggleFuncReturnType, ToggleIdType, VariantType} from '../types' import {toggle as baseToggle} from '../core/toggle' @@ -10,9 +16,8 @@ type AudienceSegmentationAttributesType = { [key in AudienceSegmentationAttributeKeyType]?: AudienceSegmentationAttributeValueType } -type BooleanToggleValueType = boolean -type ExperimentToggleValueType = string -type ToggleValueType = ExperimentToggleValueType | BooleanToggleValueType +type ExperimentToggleValueType = boolean | string +type ToggleValueType = ExperimentToggleValueType export type OptimizelyDatafileType = object @@ -25,7 +30,7 @@ let audienceSegmentationAttributes: AudienceSegmentationAttributesType = {} let userContext: OptimizelyUserContext type FeatureEnabledCacheType = { - [key in ToggleIdType]: BooleanToggleValueType + [key in ToggleIdType]: ExperimentToggleValueType } type ExperimentCacheType = {[key in ToggleIdType]: ExperimentToggleValueType} type CacheType = FeatureEnabledCacheType | ExperimentCacheType @@ -136,12 +141,11 @@ interface ActivateMVTNotificationPayload extends ListenerPayload { variationKey: VariantType } } -interface ActivateFlagNotificationPayload - extends ListenerPayload { +interface ActivateFlagNotificationPayload extends ListenerPayload { type: ExperimentType.flag decisionInfo: { - featureKey: ToggleIdType - featureEnabled: boolean + flagKey: ToggleIdType + enabled: boolean } } @@ -207,7 +211,7 @@ const validateUserId = (id) => { const getToggleDecisionStatus = ( toggleId: ToggleIdType -): BooleanToggleValueType => { +): ExperimentToggleValueType => { validateUserId(userId) const DEFAULT = false @@ -217,7 +221,10 @@ const getToggleDecisionStatus = ( return typeof value === 'boolean' ? value : DEFAULT } - userContext = optimizelyClient.createUserContext(userId, audienceSegmentationAttributes) + userContext = optimizelyClient.createUserContext( + userId, + audienceSegmentationAttributes + ) const decision = userContext.decide(toggleId) return (featureEnabledCache[toggleId] = decision.enabled) @@ -256,7 +263,10 @@ export const isUserInRolloutAudience = (toggleId: ToggleIdType) => { '' ) - if (decisionIfUserIsInAudience.result && !isPausedBooleanToggle(rolloutRule)) + if ( + decisionIfUserIsInAudience.result && + !isPausedBooleanToggle(rolloutRule) + ) isInAnyAudience = true } @@ -320,7 +330,10 @@ const convertBooleanToggleToFeatureVariant = (toggleId: ToggleIdType) => { * @param variants */ -export function toggle(toggleId: ToggleIdType, ...variants: A): ToggleFuncReturnType; +export function toggle( + toggleId: ToggleIdType, + ...variants: A +): ToggleFuncReturnType export function toggle(toggleId: ToggleIdType, ...variants) { // An A/B/C... test if (variants.length > 2) { diff --git a/packages/lib/src/integrations/simple.test.ts b/packages/lib/src/integrations/simple.test.ts index 30b2b31..8e7b379 100755 --- a/packages/lib/src/integrations/simple.test.ts +++ b/packages/lib/src/integrations/simple.test.ts @@ -1,29 +1,7 @@ -import {initialize, getBooleanToggle, getToggle} from './simple' +import {initialize, getToggle} from './simple' describe('Simple Integration', () => { - describe('Boolean Toggles', () => { - beforeEach(() => { - initialize({ - booleanToggles: { - foo: true, - bar: false - } - }) - }) - - it('Gets boolean toggles by id', () => { - expect(getBooleanToggle('foo')).toBeTruthy() - expect(getBooleanToggle('bar')).toBeFalsy() - }) - - it('defaults to false when toggle cannot be found', () => { - expect(getBooleanToggle('baz')).toEqual(false) - // @ts-expect-error invalid API call for testing purpose - expect(getBooleanToggle()).toEqual(false) - }) - }) - - describe('Multi Toggles', () => { + describe('Toggles', () => { beforeEach(() => { initialize({ toggles: { diff --git a/packages/lib/src/integrations/simple.ts b/packages/lib/src/integrations/simple.ts index 9584568..e02d12a 100755 --- a/packages/lib/src/integrations/simple.ts +++ b/packages/lib/src/integrations/simple.ts @@ -1,14 +1,7 @@ /** @deprecated */ -import type {BooleanToggleType, ToggleIdType, VariantType} from '../types' +import type {ToggleIdType, VariantType} from '../types' import {toggle as baseToggle} from '../core/toggle' -// This implementation expects you to populate a list of boolean toggles in -// advance, in the following format: -// { foo: true, bar: false } -export type BooleanToggleListType = { - [key in ToggleIdType]?: BooleanToggleType -} - // This implementation expects you to populate a list of multi toggle objects in // advance, in the following format: // { fooExperiment: {variant: 'a'}, barExperiment: {variant: 'b'} } @@ -16,25 +9,13 @@ export type toggleListType = { [key in ToggleIdType]?: {variant: VariantType} } -let booleanToggleList: BooleanToggleListType = {} let toggleList: toggleListType = {} -export const setBooleanToggles = (toggles: BooleanToggleListType) => { - booleanToggleList = toggles -} - // FIXME: FlowType export const setToggles = (toggles: any) => { toggleList = toggles } -export const getBooleanToggle = (toggleId: ToggleIdType) => { - const lowerCaseToggleId = toggleId && toggleId.toLowerCase() - return booleanToggleList.hasOwnProperty(lowerCaseToggleId) - ? booleanToggleList[lowerCaseToggleId] - : false -} - export const getToggle = (toggleId: ToggleIdType): VariantType => { const toggle = toggleList[toggleId && toggleId.toLowerCase()] @@ -43,13 +24,6 @@ export const getToggle = (toggleId: ToggleIdType): VariantType => { export const toggle = baseToggle(getToggle) -export const initialize = ({ - booleanToggles, - toggles -}: { - booleanToggles?: BooleanToggleListType - toggles?: toggleListType -}) => { - booleanToggles && setBooleanToggles(booleanToggles) +export const initialize = ({toggles}: {toggles?: toggleListType}) => { toggles && setToggles(toggles) } diff --git a/packages/lib/src/types.ts b/packages/lib/src/types.ts index ae680b7..d2fe220 100755 --- a/packages/lib/src/types.ts +++ b/packages/lib/src/types.ts @@ -7,9 +7,6 @@ export type ToggleType = { variant: VariantType } -// Boolean Toggle -export type BooleanToggleType = boolean - export type TogglerGetterType = (ToggleIdType) => any // Return type of `toggle` function