Code Monkey home page Code Monkey logo

ember-feature-flags's Introduction

ember-feature-flags Build Status Ember Observer Score

An ember-cli addon to provide feature flags.

Note to users of ember.js >= 3.1

Referencing the features service must be done using get as it is a proxy.

Installation

ember install ember-feature-flags

Usage

This addon provides a service named features available for injection into your routes, controllers, components, etc.

For example you may check if a feature is enabled:

Native class syntax:

import Controller from '@ember/controller';
import { inject as service } from '@ember/service';
export default class BillingPlansController extends Controller {
  @service features;
  get plans() {
    if (this.features.isEnabled('newBillingPlans')) {
      // Return new plans
    } else {
      // Return old plans
    }
  }
}

Classic Ember syntax:

import Controller from '@ember/controller';
import { inject as service } from '@ember/service';
export default Controller.extend({
  features: service(),
  plans() {
    if (this.get('features').isEnabled('new-billing-plans')) {
      // Return new plans
    } else {
      // Return old plans
    }
  }
});

Features are also available as properties of features. They are camelized.

Native class syntax:

import Controller from '@ember/controller';
import { inject as service } from '@ember/service';
export default class BillingPlansController extends Controller {
  @service features;
  get plans() {
    if (this.features.get('newBillingPlans')) {
      // Return new plans
    } else {
      // Return old plans
    }
  }
}

Classic Ember syntax:

import Controller from '@ember/controller';
import { inject as service } from '@ember/service';
import { computed } from '@ember/object';
export default Controller.extend({
  features: service(),
  plans: computed('features.newBillingPlans', function(){
    if (this.get('features.newBillingPlans')) {
      // Return new plans
    } else {
      // Return old plans
    }
  })
});

Check whether a feature is enabled in a template (be sure to inject the features service into the template's backing JavaScript):

// templates/components/homepage-link.hbs
{{#if features.newHomepage}}
  {{link-to "new.homepage"}}
{{else}}
  {{link-to "old.homepage"}}
{{/if}}

NOTE: features service must be injected into the respective component:

Native class syntax:

// components/homepage-link.js
export default class HomepageLink extends Component {
  @service features;
}

Classic Ember syntax:

// components/homepage-link.js
export default Component.extend({
  features: service()
});

Alternatively you can use a template helper named feature-flag:

// templates/components/homepage-link.hbs
{{#if (feature-flag 'newHomepage')}}
  {{link-to "new.homepage"}}
{{else}}
  {{link-to "old.homepage"}}
{{/if}}

Features can be toggled at runtime, and are bound:

Native class syntax:

  this.features.enable('newHomepage');
  this.features.disable('newHomepage');

Classic Ember syntax:

this.get('features').enable('newHomepage');
this.get('features').disable('newHomepage');

Features can be set in bulk:

Native class syntax:

this.features.setup({
  "new-billing-plans": true,
  "new-homepage": false
})

Classic Ember syntax:

this.get('features').setup({
  "new-billing-plans": true,
  "new-homepage": false
});

You may want to set the flags based on the result of a fetch:

// routes/application.js
features: inject(),
beforeModel() {
   return fetch('/my-flag/api').then((data) => {
     features.setup(data.json());
  });
}

NOTE: setup methods reset previously setup flags and their state.

You can get list of known feature flags via flags computed property:

this.get('features').setup({
  "new-billing-plans": true,
  "new-homepage": false
});

this.get('features.flags') // ['newBillingPlans', 'newHomepage']

Configuration

config.featureFlags

You can configure a set of initial feature flags in your app's config/environment.js file. This is an easy way to change settings for a given environment. For example:

// config/environment.js
module.exports = function(environment) {
  var ENV = {
    featureFlags: {
      'show-spinners': true,
      'download-cats': false
    }
  };

  if (environment === 'production') {
    ENV.featureFlags['download-cats'] = true;
  }

  return ENV;
};

ENV.LOG_FEATURE_FLAG_MISS

Will log when a feature flag is queried and found to be off, useful to prevent cursing at the app, wondering why your feature is not working.

Test Helpers

enableFeature / disableFeature

Turns on or off a feature for the test in which it is called. Requires ember-cli-qunit >= 4.1.0 and the newer style of tests that use setupTest, setupRenderingTest, setupApplicationTest.

Example:

import { enableFeature, disableFeature } from 'ember-feature-flags/test-support';

module('Acceptance | Awesome page', function(hooks) {
  setupApplicationTest(hooks);

  test('it displays the expected welcome message', async function (assert) {
    enableFeature('new-welcome-message');

    await visit('/');

    assert.dom('h1.welcome-message').hasText('Welcome to the new website!');

    disableFeature('new-welcome-message');

    await settled();

    assert.dom('h1.welcome-message').hasText('This is our old website, upgrade coming soon');
  });
});

withFeature

"Old"-style acceptance tests can utilize withFeature test helper to turn on a feature for the test. To use, import into your test-helper.js: import 'ember-feature-flags/test-support/helpers/with-feature' and add to your test .jshintrc, it will now be available in all of your tests.

Example:

import 'ember-feature-flags/test-support/helpers/with-feature';

test( "links go to the new homepage", function () {
  withFeature( 'new-homepage' );

  visit('/');
  click('a.home');
  andThen(function(){
    equal(currentRoute(), 'new.homepage', 'Should be on the new homepage');
  });
});

Integration Tests

If you use this.features.isEnabled() in components under integration test, you will need to inject a stub service in your tests. Using ember-qunit 0.4.16 or later, here's how to do this:

let featuresService = Service.extend({
  isEnabled() {
    return false;
  }
});

moduleForComponent('my-component', 'Integration | Component | my component', {
  integration: true,
  beforeEach() {
    this.register('service:features', featuresService);
    getOwner(this).inject('component', 'features', 'service:features');
  }
});

Note: for Ember before 2.3.0, you'll need to use ember-getowner-polyfill.

Development

Installation

  • git clone this repository
  • cd ember-feature-flags`
  • yarn install

Running

Running Tests

  • ember try:each (Test against multiple ember versions)
  • ember test
  • ember test --server

Deploying

  • See RELEASE.md

ember-feature-flags's People

Contributors

akatov avatar alisdair avatar brigittewarner avatar chriskrycho avatar dandehavilland avatar danmonroe avatar ember-tomster avatar gib avatar heroiceric avatar ibroadfo avatar jcope2013 avatar jeffdaley avatar kategengler avatar mixonic avatar odoe avatar pgengler avatar sergeastapov avatar simonexmachina avatar slindberg avatar techn1x avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

ember-feature-flags's Issues

Omission of a feature flag should not be the same as `false`

Omitting a feature flag should not be interpreted the same as setting a feature flag to false. This limits the scope of feature flags significantly, as it makes it arduous to permanently turn off a feature flag. This is what I've seen as the optimal feature flag workflow:

  1. Develop a new feature under a feature flag (flag is true in dev env, false in production)
  2. When feature is ready for beta testing in production, set to true in production
  3. When feature is stable in production, remove flag entirely from feature flag list
  4. After removing flag entirely, refactor code as time/bandwidth allows to remove checks for that feature flag

This means that applications don't have to track a list of feature flags that are always set to true in every environment. You shouldn't need to maintain feature flags indefinitely, their main value is in being set to false to disable a feature in production while allowing its code to be shipped as part of the app so as to avoid long-lived unmerged feature branches.

@jasonmit @thoov might have thoughts on this as well, as they're familiar with the above process I mentioned.

Introduce feature-flag template helper

Introduce a template helper feature-flag that can be used to conditionally render some content:

# templates/my-component.hbs
{{#if (feature-flag 'newHomepage')}}
  {{link-to "new.homepage"}}
{{else}}
  {{link-to "old.homepage"}}
{{/if}}

This can be achieved today with features service but it requires you to inject the service to a component:

# templates/my-component.hbs
{{#if features.newHomepage)}}
  {{link-to "new.homepage"}}
{{else}}
  {{link-to "old.homepage"}}
{{/if}}

# component/my-component.js
export default Component.extend({
  features: service()
});

Also it would not be possible to use that addon with template only component once RFC #278 lands.

Rerendering Late

My Code is rendering Late. That is when i set the flags, the backend changes but not the front end. it changes only when another change is produced.

That is the first change is only visible during the second change and second during the third

```

if (this.plan == "Ultra") {
console.log("ultra");
this.get("features").setup({
express: true,
pro: true,
enterprise: true,
ultimate: true,
ultra: true
});
}

Remove use of globals

Instead of using window.Features, it would be nice to have it in the environment config in config/environment.js instead.

Let me know if there was a specific reason for using globals rather than that, I wouldn't be surprised if there was, lol

Release Possible?

Hi friends!

Is it possible to cut a releasing containing 06b33fd? It'd be super nice to silence the "volatile" deprecation warning.

<3

[Feature Request] Conditional Compile

ember-cli-conditional-compile is a similar addon that (imo) lacks the elegance of the ember-feature-flags implementation. However, one concept it has that I find really compelling is the ability to conditionally exclude certain files from being compiled altogether if a feature flag is false, by way of regex in config/environment. That addon is also outdated and apparently doesn't work in Ember 1.13.8. Instead of focusing on trying to fix up that addon, my thought was that it would be great to take the best features implementation (which is this addon) and add the conditional compile concept here, so that the community has a single addon providing the best of both features.

Errors in integration tests in ember 3.1.0

Hi, at first thanks for great addon,

but i have small problem with it, after i've updated application to ember 3.1 version
my integration tests started failing with error

Assertion Failed: You attempted to access the `didRemoveListener` property (of <app@service:features::ember601>).
Since Ember 3.1, this is usually fine as you no longer need to use `.get()`
to access computed properties. However, in this case, the object in question
is a special kind of Ember object (a proxy). Therefore, it is still necessary
to use `.get('didRemoveListener')` in this case.

i couldn't find such code in this addon, so i tried to run ember-feature-flags tests and they do fail if you change ember version to 3.1

Is there any way to solve this problem?

[Question] `feature-flag` helper not CP aware?

While working on some acceptance/application tests, I noticed that this form of feature detection will update when a feature is disabled at runtime:

{{#if features.featureName}}
...functionality
{{/if}}

However if I use thefeature-flag helper, disabling the feature at runtime will not trigger an update to this conditional:

{{#if (feature-flag 'featureName')}}
...functionality
{{/if}}

Is this expected behavior? If so it might be worth mentioning in the README.

This is in Ember 2.18.2 and with ember-feature-flags 5.0.

This is awesome

This is not an issue, I just wanted to let you know that this addon is great, exactly what I needed.

Many thanks!

Use if-feature rather than ff

The following may be more readable:

 {{#if-feature 'new-homepage'}}
    {{link-to "new.homepage"}}
  {{else}}
    {{link-to "old.homepage"}}
  {{/if-feature}}

than

 {{#ff 'new-homepage'}}
    {{link-to "new.homepage"}}
  {{else}}
    {{link-to "old.homepage"}}
  {{/ff}}

Edit: updated with @jakecraige's suggestion (initially I'd suggested feature).

Add .idea to npmignore.

You've published your .idea folder presumably unintentionally since it's not in the repo.

ember-feature-flags-flip

I am going to build ember-feature-flags-flip -- The goal is to make it possible to toggle feature flags from within the app itself. I'll probably start with something like:

window.flipFlag('new-homepage')

Let me know if you have any input or thoughts! Thanks!

contextual featureFlags

Hi,

I'm wondering if I could use this add-on for the following use case

I need a particular 'feature' to enabled based on the context in which it is 'asked'. Meaning a form should only show and validate and submit some fields depending on the model. (creation date).

So to be able to ask the following

{{#if feature.enableNewFormElements model}}
   ... // render additional element

and

if( this.get('feature').isEnabled(model)}}
   ... // validate and submit additional element

I would need to be able to something, like;

this.get('feature').set('enableNewFormElements', function(model) {
    return moment(model('inceptionDate')).isAfter(moment('2018-04-12'), 'day');
})

I don't believe this is possible at this time. But would you consider it a good addition?

As a work around I can simply enable/disable when the component initialises. Much like I'm doing at the moment based on feature flags for the currentUser. But then again I could also use a simple computed property for that.

edit. a simple this.get('feature').set({'enableNewFormElements': true}), like setUp works but then without a reset, would be a simple but powerful addition.

Best practice for API based feature flags

I was wondering if there are any docs or examples on best practices/usage when it comes to using this addon with API based feature flags. These APIs are mostly based on user permission as well as the environment.

LOG_FEATURE_FLAG_MISS not logging

When setting ENV.LOG_FEATURE_FLAG_MISS = true; I am not seeing the logging of the miss. Looking through the logFeatureFlagMissEnabled() it assumes that .ENV is off of the window object and by default it is not.

I was able to get logging of misses to work by manually attaching .ENV to the window object, but that doesn't seem to be the 'right' solution,

I am on ember 1.13.10.

[Feature Request] Use flags in custom Adapter

Perhaps I miss something, but Feature flags seems to be usable only in routers, controlers and templates.
It would be great to be able to use them in Adapters.
I there a way to make this possible ?

Way of setting feature flag without resetting existing feature flags.

It would be really good if you could set feature flags in bulk without resetting the existing flags like setup does. Something like:

features.merge({
  "new-billing-plans": true,
  "new-homepage": false
});

The reason why I want to do this is because there are specific feature flags I need to set in the config file, and there are other flags that I need to set during runtime, because they depend on things like who the user is. However, there is no way to succinctly set a bunch of variables at runtime without overwriting the ones that have already been set.

P.S. It might also be worth stating in the README that setup resets all flags that have been currently set. This wasn't immediately obvious.

Discussion - support for numeric feature values

The app I work on has features that can be enabled/disabled, but also has feature 'limits' (think Slack's "You have 4 more integrations available..."). This would require passing feature values straight through without the boolean cast, and also possibly some type sniffing for things like numbers encoded as strings.

The only way I can see it breaking backwards compatibility is if someone is doing something like:

// This might happen if features are set through environment variables
this.features.enable('foo', '1');

// A bit contrived, but still valid
if (this.features.get('foo') === true) {
  // ...
}

I could keep the default behavior of casting to a boolean by hiding this behind an opt-in config setting, e.g. featureFlagsNumericValues.

The readability of this.features.enable('integration-limit', 5) suffers a little too, but not too badly IMO.

I realize this kind of goes against the name of the entire project, so is this something you'd be open to supporting? If so, I'll get to work on a PR ๐Ÿ˜„

export features as standard ember service

trying to inject the features service into other components using Ember.inject.service() raises an error:

export default Ember.Component.extend({
  features: Ember.inject.service()
});

Uncaught Error: Attempting to inject an unknown injection: service:features

RFC: Support for query params

It would be useful in my app to have feature flags which are switched off but can be switched on via a query param. This would allow specific users to test a feature before it is enabled for all users.

For example www.app.com?enabledFeatures=new-billing,new-settings would turn on the new-billing and new-settings flags.

More details:

  • The query param would default to enabledFeatures= but can be configured. (I don't think this could be called features= because this would have to be a property on the controller and it might conflict with the service).
  • This whole query-param option could be switched on or off.

In the future we could also consider:

  • Support specifying for each feature flag if it can be toggled via a queryParam
  • Support disabling features via query param, although I don't see this as being as useful...

Please let me know if you think this is a good idea. I'd be happy to submit a PR.

Strip flagged code from builds.

@Cryrivers has been working on ember-feature-flag-solution to spike out how to do the JS and template code stripping. It would be awesome if there could be some collaboration on that....

use feature flags in models

Hello, I understand why it's not a default case, but then how would you do to inject the features service into models? features: Ember.inject.service doesn't seem to work.

Thanks

You attempted to access the isNamespace property (of service:features)

Seeing this error in tests on Ember >= 3.4 when using withFeature helper:

You attempted to access the isNamespace property (of service:features)

Fixed it by extending the features service in the app with:

///app/services/features.js
import FeaturesService from 'ember-feature-flags/services/features';
import config from '../config/environment';

export default FeaturesService.extend({
  config,
  init() { //see https://github.com/kategengler/ember-feature-flags/blob/06b33fd5d6e78538c8611cdf134e93adc3a04e71/app/services/features.js
    this._super(...arguments);
    if (config.featureFlags) {
      this.setup(config.featureFlags);
    }
  },
  unknownProperty(key) {
    if (key === 'isNamespace') {
      return null; //see https://github.com/emberjs/ember.js/issues/16521#issuecomment-382465594 (need `null` or `undefined`)
    }
    return this._super(...arguments);
  }

`withFeature` Instructions Appear to be Incorrect

When attempting to following the instructions provided for including the withFeature helper into my acceptance tests, I see the following error:

Uncaught Error: Could not find module `ember-feature-flags/tests/helpers/with-feature` imported from `my-app/tests/test-helper`

Instead the following worked:

// in tests/test-helper.js
import './helpers/with-feature';

Am I doing something wrong, or does the README need to be updated? I'm more than happy to provide a patch should the latter be true.

setup does not call notifyPropertyChange

I'm fetching all enabled features from my backend and would thus like to do:

this.features.setup(this.model.get('features'));

However this does not re-render my templates where I have used the toggles as notifyPropertyChange isn't called from within setup().

Perhaps this is per design, in which case I would suggest updating the documentation to reflect that.

Expose _flags as a public API

Hi,

I was wondering if you would be open to making the _flags API public? We were hoping to get a list of all the flags that are currently available in our app so we can toggle them. I'm more than happy to pull the PR together if you are ๐Ÿ˜„

Error message: Assertion Failed: You attempted to access the `willMergeMixin` property (of <mortgage-app@service:features

When I start Ember Inspector, I get this warning in console:

Error message: Assertion Failed: You attempted to access the `willMergeMixin` property (of <mortgage-app@service:features

Since Ember 3.1, this is usually fine as you no longer need to use `.get()`
to access computed properties. However, in this case, the object in question
is a special kind of Ember object (a proxy). Therefore, it is still necessary
to use `.get('willMergeMixin')` in this case.

Is this warning being caused by ember-feature-flag or Inspector or else?

Starting with Ember 2.1 providing two arguments to an initializer will trigger a deprecation

Ember-feature flags throws a deprecation in Ember 2.2.0

DEPRECATION: The `initialize` method for Application initializer 'ember-feature-flags' should take only one argument - `App`, an instance of an `Application`. [deprecation id: ember-application.app-initializer-initialize-arguments] See http://emberjs.com/deprecations/v2.x/#toc_initializer-arity for more details.
        at HANDLERS.(anonymous function) (http://localhost:8014/assets/vendor.js:15190:7)
        at raiseOnDeprecation (http://localhost:8014/assets/vendor.js:15100:12)
        at HANDLERS.(anonymous function) (http://localhost:8014/assets/vendor.js:15190:7)
        at invoke (http://localhost:8014/assets/vendor.js:15206:7)
        at deprecate (http://localhost:8014/assets/vendor.js:15159:32)
        at Object.deprecate (http://localhost:8014/assets/vendor.js:25055:37)
        at http://localhost:8014/assets/vendor.js:13870:28
        at http://localhost:8014/assets/vendor.js:13907:9
        at Object.visit [as default] (http://localhost:8014/assets/vendor.js:64205:5)

Docs missing Glimmer/native-class examples

It would be helpful to show examples of the new component-class syntax, e.g.,:

import Component from '@glimmer/component';
import { inject as service } from '@ember/service';
export default class PlansComponent extends Component {
  @service features;
  get plans() {
    if (this.features.get('newBillingPlans') {
      // Return new plans
    } else {
      // Return old plans
    }
  }
}
import Component from '@glimmer/component';
import { inject as service } from '@ember/service';
export default class PlansComponent extends Component {
  @service features;
  get plans() {
    if (this.features.isEnabled('newBillingPlans') {
      // Return new plans
    } else {
      // Return old plans
    }
  }
}

and so on.

Would you like me to open a PR?

Explicit flag value

We should check explicitly for a true value as a condition to turn ON the flags.
In case if someone will use strings based value "false", "true" eg using dotEnv settings (which does not support boolean values) the addon will gonna interpret "false" as true which is not expected.

_featureIsEnabled(feature) {
let normalizeFeature = this._normalizeFlag(feature);
return this._flags[normalizeFeature] || false;
},

As the best solution, we should parse the flag value (in case of string) and look for

  • true
  • false
  • 0
  • 1

Or as a minimum, we should check explicitly if the value === true

Helper does not work with htmlbars

Ember helpers no longer support blocks w/ htmlbars enabled. This addon will currently prevent your app from working in greater than Ember 1.10 beta.

A potential resolution is to remove the helper. It was nice to make flag use obvious in templates, but since features is injected on all components and controllers, it should be fine to use in a plain if, if we make each flag a defined property on features.

A potential downside is that this would restrict the naming of the flags, previously, they could be any string.

Old way:

{{#if-feature 'new-feature'}}
   <div>Something</div>
{{else}}
  <div>Else</div>
{{/if-feature}}

New way:

{{#if features.newFeature}}
  <div>Something</div>
{{else}}
  <div>Else</div>
{{/if}}

An alternative would be to write a new helper that would resemble the previous helper, be bound and make use of the existing if helper internally. I think this is preferred and can build off the work in #5

this.features undefined in component integration tests

It seems that test helpers are only available in acceptance tests. I can't find that documented anywhere but that seems to be the case. So we are trying to set the flags using the service. In a component integration test this has a features property but it is undefined. Is there a way to do this?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.