Skip to content

Commit

Permalink
Remove boolean toggle remnants
Browse files Browse the repository at this point in the history
  • Loading branch information
Vinicius de Lacerda committed Jun 6, 2024
1 parent d750ed2 commit 9b32a6a
Show file tree
Hide file tree
Showing 9 changed files with 53 additions and 185 deletions.
14 changes: 14 additions & 0 deletions .changeset/silly-schools-hunt.md
Original file line number Diff line number Diff line change
@@ -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
82 changes: 0 additions & 82 deletions docs/booleanToggles.md

This file was deleted.

28 changes: 7 additions & 21 deletions docs/dead-code-removal.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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)
Expand Down
6 changes: 2 additions & 4 deletions docs/optimizely-integration.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 0 additions & 10 deletions docs/simple-integration.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

```
Expand Down
39 changes: 26 additions & 13 deletions packages/lib/src/integrations/optimizely.ts
Original file line number Diff line number Diff line change
@@ -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'

Expand All @@ -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

Expand All @@ -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
Expand Down Expand Up @@ -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
}
}

Expand Down Expand Up @@ -207,7 +211,7 @@ const validateUserId = (id) => {

const getToggleDecisionStatus = (
toggleId: ToggleIdType
): BooleanToggleValueType => {
): ExperimentToggleValueType => {
validateUserId(userId)

const DEFAULT = false
Expand All @@ -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)
Expand Down Expand Up @@ -256,7 +263,10 @@ export const isUserInRolloutAudience = (toggleId: ToggleIdType) => {
''
)

if (decisionIfUserIsInAudience.result && !isPausedBooleanToggle(rolloutRule))
if (
decisionIfUserIsInAudience.result &&
!isPausedBooleanToggle(rolloutRule)
)
isInAnyAudience = true
}

Expand Down Expand Up @@ -320,7 +330,10 @@ const convertBooleanToggleToFeatureVariant = (toggleId: ToggleIdType) => {
* @param variants
*/

export function toggle<A extends any[]>(toggleId: ToggleIdType, ...variants: A): ToggleFuncReturnType<A>;
export function toggle<A extends any[]>(
toggleId: ToggleIdType,
...variants: A
): ToggleFuncReturnType<A>
export function toggle(toggleId: ToggleIdType, ...variants) {
// An A/B/C... test
if (variants.length > 2) {
Expand Down
26 changes: 2 additions & 24 deletions packages/lib/src/integrations/simple.test.ts
Original file line number Diff line number Diff line change
@@ -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: {
Expand Down
30 changes: 2 additions & 28 deletions packages/lib/src/integrations/simple.ts
Original file line number Diff line number Diff line change
@@ -1,40 +1,21 @@
/** @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'} }
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()]

Expand All @@ -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)
}
3 changes: 0 additions & 3 deletions packages/lib/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 9b32a6a

Please sign in to comment.