From ec0a27d1aae318358511d1873b121f69fb2444ea Mon Sep 17 00:00:00 2001 From: Aaron Chambers Date: Tue, 24 Nov 2020 15:38:44 +0000 Subject: [PATCH] =?UTF-8?q?=E2=9C=8D=EF=B8=8F=20Update=20README=20and=20up?= =?UTF-8?q?grade=20instructions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 484 +++++++++++++++---------------------------- UPGRADING_TO_v2.x.md | 249 ++++++++++++++++++++++ 2 files changed, 418 insertions(+), 315 deletions(-) create mode 100644 UPGRADING_TO_v2.x.md diff --git a/README.md b/README.md index 12e137a0..cf2c5d0d 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,39 @@ -# ember-launch-darkly +# Ember Launch Darkly -[![Build Status](https://travis-ci.org/adopted-ember-addons/ember-launch-darkly.svg?branch=master)](https://travis-ci.org/adopted-ember-addons/ember-launch-darkly) +[![Build Status](https://travis-ci.com/adopted-ember-addons/ember-launch-darkly.svg?branch=master)](https://travis-ci.com/adopted-ember-addons/ember-launch-darkly) This addon wraps the [Launch Darkly](https://launchdarkly.com/) feature flagging service and provides helpers to implement feature flagging in your application +## Compatibility + +| Addon version | Ember version | | +| :----------------------------------------------------------------------------------------: | :-------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------: | +| [v2.0](https://github.com/adopted-ember-addons/ember-launch-darkly/releases/tag/v2.0.0) | >= [v3.17](https://github.com/emberjs/ember.js/releases/tag/v3.17.3) | [README](README.md) | +| <= [v1.0](https://github.com/adopted-ember-addons/ember-launch-darkly/releases/tag/v1.0.0) | <= [v3.16](https://github.com/emberjs/ember.js/releases/tag/v3.16.10) | [README](https://github.com/adopted-ember-addons/ember-launch-darkly/blob/v1.0.0/README.md) | + +## Table of Contents + +1. [Installation](#installation) +2. [Configuration](#configuration) + 1. [`clientSideId`](#clientsideid-required) + 2. [`mode`](#mode) + 3. [`localFlags`](#localflags) + 4. [`streamingFlags`](#streamingflags) + 5. [`bootstrap`](#bootstrap) + 6. [Launch Darkly specific config](#launch-darkly-specific-config) +3. [Usage](#usage) + 1. [`initialize`](#initialize) + 2. [`identify`](#identify) + 3. [`variation` (template helper)](#variation-template-helper) + 4. [`variation` (javascript helper)](#variation-javascript-helper) +4. [Local feature flags](#local-feature-flags) +5. [Streaming feature flags](#streaming-feature-flags) +6. [Content Security Policy](#content-security-policy) +7. [Test helpers](#test-helpers) + 1. [Acceptance tests](#acceptance-tests) + 2. [Integration tests](#integration-tests) +8. [Upgrading to v2.0](#upgrading-to-v20) + ## Installation ```bash @@ -15,14 +45,14 @@ $ ember install ember-launch-darkly ember-launch-darkly can be configured from `config/environment.js` as follows: ```js -module.exports = function(environment) { +module.exports = function (environment) { let ENV = { launchDarkly: { // options - } + }, }; - return ENV + return ENV; }; ``` @@ -30,79 +60,49 @@ ember-launch-darkly supports the following configuration options: ### `clientSideId` (required) -The client side ID generated by Launch Darkly which is available in your [account settings page](https://app.launchdarkly.com/settings#/projects). See the Launch Darkly docs for [more information on how the client side ID is used](https://docs.launchdarkly.com/docs/js-sdk-reference#section-initializing-the-client). +The client-side ID generated by Launch Darkly which is available in your [account settings page](https://app.launchdarkly.com/settings#/projects). See the Launch Darkly docs for [more information on how the client side ID is used](https://docs.launchdarkly.com/docs/js-sdk-reference#section-initializing-the-client). -### `local` +### `mode` -Specify that you'd like to pull feature flags from your local config instead of remotely from Launch Darkly. This is likely appropriate when running in the `development` environment or an external environment for which you don't have Launch Darkly set up. +The mode in which the Launch Darkly client will run, either `local` or `remote`. When running in `remote` mode, feature flags will be fetched from the Launch Darkly service as you'd expect. This is the mode you want to be running in in production. -This option will also make the launch darkly service available in the browser console so that feature flags can be enabled/disabled manually. +When running in `local` mode, feature flags will be fetched from the `localFlags` defined in the config. This is likely appropriate when running the app locally, or in an external environment for which you don't have Launch Darkly setup. It allows you to have a sandboxed feature flag set that is not dependent on the Launch Darkly service or the state of the flags stored in Launch Darkly itself. -_Default_: `false` in production, `true` in all other environments +_Default_: `local` -### `localFeatureFlags` +_Possible Values_: `local`, `remote` -A list of initial values for your feature flags. This property is only used when `local: true` to populate the list of feature flags for environments such as local development where it's not desired to store the flags in Launch Darkly. +### `localFlags` + +A list of initial values for your feature flags. This property is only used when `mode: 'local'` to populate the list of feature flags for environments such as local development where it's not desired to fetch the flags from Launch Darkly. _Default_: `null` -### `streaming` +### `streamingFlags` -Streaming options for the feature flags for which you'd like to subscribe to realtime updates. See the [Streaming Feature Flags section](#streaming-feature-flags) for more detailed info on what the possible options are for streaming flags. +Streaming options for the feature flags for which you'd like to subscribe to real-time updates. See the [Streaming Feature Flags section](#streaming-feature-flags) for more detailed info on what the possible options are for streaming flags. _Default_: `false` -## Content Security Policy - -If you have CSP enabled in your ember application, you will need to add Launch Darkly to the `connect-src` like so: - -```js -// config/environment.js - -module.exports = function(environment) { - let ENV = { - //snip - - contentSecurityPolicy: { - 'connect-src': ['https://*.launchdarkly.com'] - } +### `bootstrap` - //snip - }; -}; -``` - -## Usage +The Launch Darkly client supports the idea of [bootstrapping your feature flags with an initial set of values](https://docs.launchdarkly.com/sdk/client-side/javascript#bootstrapping) so that the `variation` function can be called before the flags have been fetched from Launch Darkly. -### Launch Darkly Service +If the `bootstrap` property is set to `localFlags`, ember-launch-darkly will use the flags specified in `localFlags` as the bootstrap flag values passed to Launch Darkly. Other than that, the `bootstrap` property will be passed directly through to Launch Darkly. -ember-launch-darkly automatically injects the launch darkly service, as `launchDarkly` in to the following Ember objects: +_Default_: `null` -- all `route`s -- all `controller`s -- all `component`s -- `router:main` +_Possible Values_: `localFlags` otherwise whatever Launch Darkly expects based on its [Bootstrapping documentation](https://docs.launchdarkly.com/sdk/client-side/javascript#bootstrapping). -This means that it can be accessed without an explicit injection, like so: +### Launch Darkly specific config -```js -// /app/application/route.js +Any other properties passed in as configuration will be passed straight through to Launch Darkly. -import Route from '@ember/routing/route'; +_Possible Values_: [As documented in the section titled "Customizing your client" in the Launch Darkly documentation](https://docs.launchdarkly.com/sdk/client-side/javascript#customizing-your-client). -export default Route.extend({ - model() { - let user = { - key: 'aa0ceb', - anonymous: true - }; +> A note on `sendEventsOnlyForVariation`. When this flag is set to `false`, then events are sent, for every single feature flag, to Launch Darkly when `client.allFlags()` is called. An event is what tells Launch Darkly when a flag was last requested, which is how you can tell on the feature flags list, which flags were requested and when. This can be misleading because a user didn't actually request a flag, it was ember-launch-darkly that requested `allFlags` which is needed to know which flags exist. This could be confusing if a version of your code no longer has references to a feature flag but it still exists in Launch Darkly. You may see that the flag was requested even though there is no code in the wild that actually should be requesting it. Therefore, ember-launch-darkly sets this flag to `true` to avoid sending those events when we fetch `allFlags`. You are, however, welcome to set it back to `false` in the config if you wish - just know that this means you'll be seeing "Requested at" times for flags that you may not expect. - return this.launchDarkly.initialize(user); - } -}); -``` - -Due to Ember not allowing auto injection of a service in to another service, we are currently unable to auto inject `launchDarkly` in to other services. This means that if you would like to check for Launch Darkly flags in your service, you will need to explicitly inject the `launchDarkly` service yourself. +## Usage ### Initialize @@ -119,66 +119,51 @@ See the [Launch Darkly User documentation](https://docs.launchdarkly.com/sdk/cli import Route from '@ember/routing/route'; -export default Route.extend({ - model() { - let user = { - key: 'aa0ceb' - }; - - return this.launchDarkly.initialize(user); - } -}); -``` - -If you set the `anonymous` flag to true, then the key is not required. +import { initialize } from 'ember-launch-darkly'; -See the [Launch Darkly Anonymous User documentation](https://docs.launchdarkly.com/sdk/client-side/javascript#users) for more on the `anonymous` flag. - -```js -// /app/application/route.js - -import Route from '@ember/routing/route'; - -export default Route.extend({ - model() { +export default class ApplicationRoute extends Route { + async model() { let user = { - anonymous: true + key: 'aa0ceb', }; - return this.launchDarkly.initialize(user); + return await initialize(user); } -}); +} ``` ### Identify -If you initialized Launch Darkly with an anonymous user and want to re-initialize it for a specific user to receive the flags for that user, you can use `identify`. This can only be called after `initialize` has been called. +If you initialized Launch Darkly with an anonymous user and want to re-initialize it for a specific user to receive the flags for that user, you can use `identify`. This must be called after `initialize` has been called. ```js // /app/session/route.js import Route from '@ember/routing/route'; +import { inject as service } from '@ember/service'; + +import { identify } from 'ember-launch-darkly'; -export default Route.extend({ - session: service(), +export default class SessionRoute extends Route { + @service session; model() { return this.session.getSession(); }, - afterModel(session) { + async afterModel(session) { let user = { - key: session.get('user.id'), - firstName: session.get('user.firstName'), - email: session.get('user.email') + key: session.user.id, + firstName: session.user.firstName, + email: session.user.email }; - return this.launchDarkly.identify(user); + return await identify(user); } -}); +} ``` -### Templates +### variation (template helper) ember-launch-darkly provides a `variation` helper to check your feature flags in your handlebars templates. @@ -206,270 +191,107 @@ If your feature flag is a multivariate based flag, you might use it in an `{{wit {{/with}} ``` -### Javascript +### variation (javascript helper) If your feature flag is a boolean based flag, you might use it in a function like so: ```js // /app/components/login-page/component.js -import Component from '@ember/component'; -import { computed } from '@ember/object'; - -export default Component.extend({ - actions: { - getPrice() { - if (this.launchDarkly.variation('new-price-plan')) { - return 99.00; - } - - return 199.00; - }, - } -}); -``` - -If your feature flag is a multivariate based flag, you might use it in a function like so: - -```js -// /app/components/login-page/component.js - -import Component from '@ember/component'; -import { computed } from '@ember/object'; - -export default Component.extend({ - actions: { - getPrice() { - switch (this.launchDarkly.variation('new-pricing-plan')) { - case 'plan-a': - return 99.00; - case 'plan-b': - return 89.00 - case 'plan-c': - return 79.00 - } - - return 199.00; - } - } -}); -``` - -And if you want to check a flag in a computed property, and have it recompute when the flag changes, you'll want to make sure you add the flag as a dependent key to the CP, as follows: - -```js -// /app/components/login-page/component.js - -import Component from '@ember/component'; -import { computed } from '@ember/object'; - -export default Component.extend({ - price: computed('launchDarkly.new-price-plan', function() { - if (this.launchDarkly['new-price-plan']) { - return 99.00; - } - - return 199.00; - }) -}); -``` - -### [EXPERIMENTAL] `variation` Javascript helper - -ember-launch-darkly provides a special, _experimental_, `variation` import that can be used in Javascript files such as Components, to make the invocation and checking of feature flags a little nicer in the JS code. - -This helper is backed by a Babel transform that essentially replaces the helper with the code above in the [Javascript](#javascript) section. The main benefits this helper provides is: - -- Automatically adds feature flags as dependent keys to computed properties so they are recalculated when flags change -- Removes boiler plate launch darkly code (injection of service, referencing service in functions, etc) -- Provide a syntax that is parallel to the `variation` helper used in templates - -The babel transform that replaces this `variation` helper in the JS code is experimental. YMMV. - -If you would like to try it out, simply enable it in your `ember-cli-build.js` as follows: - -```js -// ember-cli-build.js - -const EmberApp = require('ember-cli/lib/broccoli/ember-app'); - -module.exports = function(defaults) { - let app = new EmberApp(defaults, { - babel: { - plugins: [ require.resolve('ember-launch-darkly/babel-plugin') ] - } - }); - - return app.toTree(); -}; -``` - -Then import the helper from `ember-launch-darkly` and use it as follows: - -```js -// /app/components/login-page/component.js - import Component from '@ember/component'; import { variation } from 'ember-launch-darkly'; -export default Component.extend({ - price() { - if (variation('new-pricing-plan')) { - return 99.00; +export default class LoginPageComponent extends Component { + get price() { + if (variation('new-price-plan')) { + return 99.0; } - return 199.00; + return 199.0; } -}); -``` - -If you would like the feature flag to recompute a computed property when it -changes, you will need to also use the `computedWithVariation` import, like so: - -```js -// /app/components/login-page/component.js - -import Component from '@ember/component'; -import { variation, computedWithVariation as computed } from 'ember-launch-darkly'; - -export default Component.extend({ - price: computed(function() { - if (variation('new-pricing-plan')) { - return 99.00; - } - - return 199.00; - }) -}); -``` - -The `computedWithvariation` import is literally a direct re-export of the `@ember/object` computed. We need to re-export it so that we can signal to the babel transform where to auto insert the feature flag dependent keys. Because `computedWithVariation` is a re-export you can alias it as `computed` (like above) and use it anywhere you would use a normal `computed`. - -For reference, the babel transform should transform the above code in to, _roughly_, the following: - -```js -// /app/components/login-page/component.js - -import Component from '@ember/component'; -import { computed } from '@ember/object'; - -export default Component.extend({ - price: computed('launchDarkly.new-pricing-plan', function() { - if (this.launchDarkly['new-pricing-plan']) { - return 99.00; - } - - return 199.00; - }) -}); -``` - -It's important to note that the `variation` helper will only work when used -inside objects that have an `owner`. This is because the helper is transformed -in to a call to `this.launchDarkly` which is an injected service. Therefore, the -`variation` helper will not work in a computed returned from, say, a function. -Like so: - -```js -export default function myCoolFn() { - if (variation('foo')) { - return 'I love cheese'; - } - - return 'I love bacon'; } ``` -For the reason above, if you would like to use the variation helper in one of your own objects that -extends `EmberObject`, you will need to create the `EmberObject` instance with -an owner injection, like so: +If your feature flag is a multivariate based flag, you might use it in a function like so: ```js // /app/components/login-page/component.js import Component from '@ember/component'; -import EmberObject from '@ember/object'; -import { getOwner } from '@ember/application'; -const MyFoo = EmberObject.extend({ - price() { - return variation('my-price'); - } -}); - -export default Component.extend({ - createFoo() { - let owner = getOwner(this); +import { variation } from 'ember-launch-darkly'; - return MyFoo.create(owner.ownerInjection(), { - someProp: 'bar' - }); +export default class LoginPageComponent extends Component { + get price() { + switch (variation('new-pricing-plan')) { + case 'plan-a': + return 99.00; + case 'plan-b': + return 89.00 + case 'plan-c': + return 79.00 + default: + return 199.00; + } } }); - ``` -As this babel transform is experimental, it does not currently support the -following: - -- Native classes -- Decorators - -We will endeavour to add support for these things in the future. +Because ember-launch-darkly is built for Ember Octane, its feature flags are tracked. This means that when using the `variation` helper, if a flag value changes, code that references it will be automatically recomputed. ## Local feature flags -When `local: true` is set in the Launch Darkly configuration, ember-launch-darkly will retrieve the feature flags and their values from `config/environment.js` instead of the Launch Darkly service. This is useful for development purposes so you don't need to set up a new environment in Launch Darkly, your app doesn't need to make a request for the flags, and you can easily change the value of the flags from the browser console. +When `mode: 'local'` is set in the Launch Darkly configuration, ember-launch-darkly will retrieve the feature flags and their values from `config/environment.js` instead of the Launch Darkly service. This is useful for development purposes so you don't need to set up a new environment in Launch Darkly, your app doesn't need to make a request for the flags, and you can easily change the value of the flags from the browser console. The local feature flags are defined in `config/environment.js` like so: ```js let ENV = { launchDarkly: { - local: true, - localFeatureFlags: { + mode: 'local', + localFlags: { 'apply-discount': true, - 'new-pricing-plan': 'plan-a' - } - } -} + 'new-pricing-plan': 'plan-a', + }, + }, +}; ``` -When `local: true`, the Launch Darkly feature service is available in the browser console via `window.ld`. The service provides the following helper methods to manipulate feature flags: +When `mode: 'local'`, the Launch Darkly flags context is available in the JS console via `window.__LD__`. The context object provides the following helper methods to manipulate feature flags: ```js -> ld.variation('new-pricing-plan', 'plan-a') // return the current value of the feature flag providing a default if it doesn't exist +> window.__LD__.get('new-pricing-plan', 'plan-a') // return the current value of the feature flag providing a default ('plan-a' if it doesn't exist (the default is optional) -> ld.setVariation('new-pricing-plan', 'plan-x') // set the variation value +> window.__LD__.set('new-pricing-plan', 'plan-x') // set the variation value -> ld.enable('apply-discount') // helper to set the return value to `true` -> ld.disable('apply-discount') // helper to set the return value to `false` +> window.__LD__.enable('apply-discount') // helper to set the return value to `true` +> window.__LD__.disable('apply-discount') // helper to set the return value to `false` -> ld.allFlags() // return the current list of feature flags and their values +> window.__LD__.allFlags // return the current list of feature flags and their values -> ld.user() // return the user that the client has been initialized with +> window.__LD__.user // return the user that the client has been initialized with ``` -## Streaming Feature Flags +## Streaming feature flags -Launch Darkly supports the ability to subscribe to changes to feature flags so that apps can react in real-time to these changes. The [`streaming` configuration option](#streaming) allows you to specify, in a couple of ways, which flags you'd like to stream. +Launch Darkly supports the ability to subscribe to changes to feature flags so that apps can react in real-time to these changes. The [`streamingFlags` configuration option](#streaming-flags) allows you to specify, in a couple of ways, which flags you'd like to stream. To disable streaming completely, use the following configuration: ```js launchDarkly: { - streaming: false + streamingFlags: false; } ``` -_Note, this is the default behaviour if the `streaming` option is not specified._ +_Note, this is the default behaviour if the `streamingFlags` option is not specified._ To stream all flags, use the following configuration: ```js launchDarkly: { - streaming: true + streamingFlags: true; } ``` @@ -477,8 +299,8 @@ To get more specific, you can select to stream all flags except those specified: ```js launchDarkly: { - streaming: { - allExcept: ['apply-discount', 'new-login'] + streamingFlags: { + allExcept: ['apply-discount', 'new-login']; } } ``` @@ -487,20 +309,40 @@ And, finally, you can specify only which flags you would like to stream: ```js launchDarkly: { - streaming: { + streamingFlags: { 'apply-discount': true } } ``` -As Launch Darkly's realtime updates to flags uses the [Event Source API](https://developer.mozilla.org/en-US/docs/Web/API/EventSource), certain browsers will require a polyfill to be included. ember-launch-darkly uses [EmberCLI targets](http://rwjblue.com/2017/04/21/ember-cli-targets/) to automatically decide whether or not to include the polyfill. Ensure your project contains a valid `config/targets.js` file if you require this functionality. +As Launch Darkly's real-time updates to flags uses the [Event Source API](https://developer.mozilla.org/en-US/docs/Web/API/EventSource), certain browsers will require a polyfill to be included. ember-launch-darkly uses [EmberCLI targets](http://rwjblue.com/2017/04/21/ember-cli-targets/) to automatically decide whether or not to include the polyfill. Ensure your project contains a valid `config/targets.js` file if you require this functionality. + +## Content Security Policy + +If you have CSP enabled in your ember application, you will need to add Launch Darkly to the `connect-src` like so: + +```js +// config/environment.js + +module.exports = function (environment) { + let ENV = { + //snip + + contentSecurityPolicy: { + 'connect-src': ['https://*.launchdarkly.com'], + }, + + //snip + }; +}; +``` -## Test Helpers +## Test helpers -### Acceptance Tests +### Acceptance tests -Add the `setupLaunchDarkly` hook to the top of your test file. This will ensure that Launch Darkly uses a test stub client which defaults your feature flags to -`false` instead of using what is defined in the `localFeatureFlags` config. This allows your tests to start off in a known default state. +Add the `setupLaunchDarkly` hook to the top of your test file. This will ensure that Launch Darkly uses defaults your feature flags to +`false` instead of using what is defined in the `localFlags` config. This allows your tests to start off in a known default state. ```js import { module, test } from 'qunit'; @@ -509,15 +351,19 @@ import { setupApplicationTest } from 'ember-qunit'; import { setupLaunchDarkly } from 'ember-launch-darkly/test-support'; -module('Acceptance | Homepage', function(hooks) { +module('Acceptance | Homepage', function (hooks) { setupApplicationTest(hooks); setupLaunchDarkly(hooks); - test('links go to the new homepage', async function(assert) { + test('links go to the new homepage', async function (assert) { await visit('/'); await click('a.pricing'); - assert.equal(currentRoute(), 'pricing', 'Should be on the old pricing page'); + assert.equal( + currentRoute(), + 'pricing', + 'Should be on the old pricing page' + ); }); }); ``` @@ -525,22 +371,26 @@ module('Acceptance | Homepage', function(hooks) { ember-launch-darkly provides a test helper, `withVariation`, to make it easy to turn feature flags on and off in acceptance tests. ```js -module('Acceptance | Homepage', function(hooks) { +module('Acceptance | Homepage', function (hooks) { setupApplicationTest(hooks); setupLaunchDarkly(hooks); - test('links go to the new homepage', async function(assert) { - this.withVariation('new-pricing-plan', 'plan-a'); + test('links go to the new homepage', async function (assert) { + await this.withVariation('new-pricing-plan', 'plan-a'); await visit('/'); await click('a.pricing'); - assert.equal(currentRoute(), 'pricing', 'Should be on the old pricing page'); + assert.equal( + currentRoute(), + 'pricing', + 'Should be on the old pricing page' + ); }); }); ``` -### Integration Tests +### Integration tests Use the `setupLaunchDarkly` hook and `withVariation` helper in component tests to control feature flags as well. @@ -552,11 +402,11 @@ import hbs from 'htmlbars-inline-precompile'; import { setupLaunchDarkly } from 'ember-launch-darkly/test-support'; -module('Integration | Component | foo', function(hooks) { +module('Integration | Component | foo', function (hooks) { setupRenderingTest(hooks); setupLaunchDarkly(hooks); - test('new pricing', async function(assert) { + test('new pricing', async function (assert) { await render(hbs` {{#if (variation "new-pricing-page")}}

£ 99

@@ -565,15 +415,19 @@ module('Integration | Component | foo', function(hooks) { {{/if}} `); - this.withVariation('new-pricing-page'); + await this.withVariation('new-pricing-page'); - assert.equal(this.element.querySelector('.price').textContent.trim(), '£ 99', 'New pricing displayed'); + assert.equal( + this.element.querySelector('.price').textContent.trim(), + '£ 99', + 'New pricing displayed' + ); }); }); ``` -## TODO +## Upgrading to v2.0 -- Implement support for `secure` mode ([#9](https://github.com/ember-launch-darkly/ember-launch-darkly/issues/9)) +v2.0 of the addon is built for Ember Octane (>= v3.17) and beyond. It contains breaking changes from the previous releases. If you would like to upgrade from v1.0 or earlier, please following the instructions in [UPGRADING_TO_v2.x.md](UPGRADING_TO_v2.x.md) file.

Made with :heart: by The Ember Launch Darkly Team

diff --git a/UPGRADING_TO_v2.x.md b/UPGRADING_TO_v2.x.md new file mode 100644 index 00000000..5ea497c1 --- /dev/null +++ b/UPGRADING_TO_v2.x.md @@ -0,0 +1,249 @@ +# Upgrading to 2.x + +## Overview + +The approach taken in [v2.0](https://github.com/adopted-ember-addons/ember-launch-darkly/releases/tag/v2.0.0) of this addon has changed drastically from versions [<= v1.0](https://github.com/adopted-ember-addons/ember-launch-darkly/releases/tag/v1.0.0) +and will require some modifications to config and code in order to upgrade. The steps below outline the things you will need to change in order to upgrade to [v2.0](https://github.com/adopted-ember-addons/ember-launch-darkly/releases/tag/v2.0.0). + +## Migrate config + +Mirgate config from this: + +```js +// config/environment.js + +module.exports = function (environment) { + let ENV = { + launchDarkly: { + clientSideId: '1234', + local: true, + localFeatureFlags: { + foo: true, + }, + streaming: { + foo: true, + }, + }, + }; +}; +``` + +to this: + +```js +module.exports = function (environment) { + let ENV = { + launchDarkly: { + clientSideId: '1234', + mode: 'local', + localFlags: { + foo: true, + }, + streamingFlags: { + foo: true, + }, + }, + }; +}; +``` + +## Migrate `this.launchDarkly.initialize` + +Ember Launch Darkly no longer uses services and so the initialization should change from this: + +```js +// /app/application/route.js + +import Route from '@ember/routing/route'; + +export default Route.extend({ + model() { + let user = { + key: 'aa0ceb', + anonymous: true, + }; + + return this.launchDarkly.initialize(user); + }, +}); +``` + +to this: + +```js +// /app/application/route.js + +import Route from '@ember/routing/route'; + +import { initialize } from 'ember-launch-darkly'; + +export default class ApplicationRoute extends Route { + async model() { + let user = { + key: 'aa0ceb', + }; + + return await initialize(user); + } +} +``` + +## Migrate `this.launchDarkly.identify` + +Ember Launch Darkly no longer uses services and so the identification should change from this: + +```js +// /app/session/route.js + +import Route from '@ember/routing/route'; + +export default Route.extend({ + session: service(), + + model() { + return this.session.getSession(); + }, + + afterModel(session) { + let user = { + key: session.get('user.id'), + firstName: session.get('user.firstName'), + email: session.get('user.email'), + }; + + return this.launchDarkly.identify(user); + }, +}); +``` + +to this: + +```js +// /app/session/route.js + +import Route from '@ember/routing/route'; +import { inject as service } from '@ember/service'; + +import { identify } from 'ember-launch-darkly'; + +export default class SessionRoute extends Route { + @service session; + + model() { + return this.session.getSession(); + }, + + async afterModel(session) { + let user = { + key: session.user.id, + firstName: session.user.firstName, + email: session.user.email + }; + + return await identify(user); + } +} +``` + +## Migrate `this.launchDarkly.variation` + +Ember Launch Darkly no longer uses services and so checking of variations should change from this: + +```js +// /app/components/login-page/component.js + +import Component from '@ember/component'; +import { computed } from '@ember/object'; + +export default Component.extend({ + actions: { + getPrice() { + if (this.launchDarkly.variation('new-price-plan')) { + return 99.0; + } + + return 199.0; + }, + }, +}); +``` + +to this: + +```js +// /app/components/login-page/component.js + +import Component from '@ember/component'; + +import { variation } from 'ember-launch-darkly'; + +export default class LoginPageComponent extends Component { + get price() { + if (variation('new-price-plan')) { + return 99.0; + } + + return 199.0; + } +} +``` + +## A note on the experimental `variation` Javascript helper + +Versions of this addon pre `v2.0` included [an experimental Babel transform that allowed users to import a `variation` function](https://github.com/adopted-ember-addons/ember-launch-darkly/tree/v0.7.0#javascript) in to Javascript instead of +referencing `this.launchDarkly.variation`. It was more than a shorthand for the variation function - it also tried to be a bit clever and, when used inside +a computed property, attempted to add the feature flags as dependent keys of the computed property so as to ensure the computed property was re-computed if +the flag changed. + +While this helper was useful, it was infinitely problematic. Thankfully, in an Ember Octane world, or more specifically, an `@tracked` world, this is totally unnecessary. +Any code that checks for feature flags will re-compute if the flag changes based on the fact that the flags are now tracked in v2.0. + +Because the import of the `variation` function in v2.0 is the same as the import of the old babel transform helper, no changes should need to be made here. + +However, if you are using [v1.0.0](https://github.com/adopted-ember-addons/ember-launch-darkly/releases/tag/v1.0.0) specifically, there are likely a +couple of changes you will need to make. + +No doubt you will have stopped using the `computedWithVariation` helper ([DOC](https://github.com/adopted-ember-addons/ember-launch-darkly/tree/v1.0.0#experimental-variation-javascript-helper)) as you won't be using computed properties in Octane any more and it doesn't work with +the `@computed` decorator. + +However, you should also remove the code in your `ember-cli-build.js` that enables the Babel transform. So, remove this: + +```js +// ember-cli-build.js + +const EmberApp = require('ember-cli/lib/broccoli/ember-app'); + +module.exports = function (defaults) { + let app = new EmberApp(defaults, { + babel: { + plugins: [require.resolve('ember-launch-darkly/babel-plugin')], // <---- Remove this plugin. + }, + }); + + return app.toTree(); +}; +``` + +## Accessing local feature flags from the JS console + +If you want to access and modify local feature flags from the JS console you will need to change from doing this: + +```js +ld.variation('new-pricing-plan', 'plan-a'); // return the current value of the feature flag providing a default if it doesn't exist +ld.setVariation('new-pricing-plan', 'plan-x'); // set the variation value +ld.enable('apply-discount'); // helper to set the return value to `true` +ld.disable('apply-discount'); // helper to set the return value to `false` +ld.allFlags(); // return the current list of feature flags and their values +ld.user(); +``` + +to this: + +```js +> window.__LD__.get('new-pricing-plan', 'plan-a') // return the current value of the feature flag providing a default ('plan-a' if it doesn't exist (the default is optional) +> window.__LD__.set('new-pricing-plan', 'plan-x') // set the variation value +> window.__LD__.enable('apply-discount') // helper to set the return value to `true` +> window.__LD__.disable('apply-discount') // helper to set the return value to `false` +> window.__LD__.allFlags // return the current list of feature flags and their values +> window.__LD__.user // return the user that the client has been initialized with +```