Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RFC: Support for query params #40

Open
iezer opened this issue Feb 11, 2016 · 5 comments
Open

RFC: Support for query params #40

iezer opened this issue Feb 11, 2016 · 5 comments

Comments

@iezer
Copy link

iezer commented Feb 11, 2016

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.

@sarus
Copy link

sarus commented Feb 22, 2016

What would be really neat is to support integration with ember inspector so you can turn features on and off right from the chrome inspector.

@kategengler
Copy link
Owner

I think this would be appropriate as an addon to this addon.

Getting the flags into the Ember Inspector is something I'm interested in doing. Originally, when the flags were just an Ember object you could change them in there, but this undocumented functionality was lost via PR.

@SergeAstapov
Copy link
Contributor

A simple instance initializer that enables this feature can look like:

export function initialize (appInstance) {
    const features = appInstance.lookup('service:features');

    const enabledFeatures = getQueryParam('enabledFeatures');
    if (enabledFeatures) {
        for (const feature of enabledFeatures.split(',')) {
            features.enable(feature);
        }
    }
}

export default {
    name: 'feature-flags',
    initialize
};

where getQueryParam can be implemented in various ways, e.g. with some npm package like query-string.

@rtablada
Copy link

rtablada commented Jul 7, 2022

We just implemented an augmentation/extension for the features service to support query params and sessionStorage persistence (for tab persistence for FFs), I think a different way would be to have better docs and hooks for enable/disabling features.

This way teams can have a dedicated way to initialize and persist FFs with QPs, dynamic HTML Head, server request, etc.

This is our extended features service:

import Features from 'ember-feature-flags/services/features';
import config from '../config/environment';
import { inject as service } from '@ember/service';

const FEATURE_QP_REGEX = /^feature\[(.*)\]/;

export default class FeaturesService extends Features {
  config = config;
  @service('router') router;

  init() {
    super.init(...arguments);

    if (config.featureFlags) {
      let featureFlags = {
        ...config.featureFlags,
        ...this.sessionFeatureFlags,
        ...this.initQueryFeatureFlags(),
      };

      this.setup(featureFlags);
    }
  }

  get sessionFeatureFlags() {
    return JSON.parse(sessionStorage.featureFlags ?? '{}');
  }

  set sessionFeatureFlags(value = {}) {
    sessionStorage.featureFlags = JSON.stringify(value);
  }

  initQueryFeatureFlags() {
    let featureFlags = {};
    const params = new URLSearchParams(window.location.search);

    // Search query params for feature flags
    params.forEach((value, key) => {
      const matches = key.match(FEATURE_QP_REGEX);
      if (matches) {
        let [, flag] = matches;

        featureFlags[flag] = value !== 'false' ? true : false;
      }
    });

    return featureFlags;
  }

  persistFeatureFlag(flag, value) {
    let sessionFeatureFlags = this.sessionFeatureFlags;

    if (config.featureFlags[flag] == value) {
      delete sessionFeatureFlags[flag];
    } else {
      sessionFeatureFlags[flag] = value;
    }

    this.sessionFeatureFlags = sessionFeatureFlags;
  }

  enable(flag) {
    super.enable(flag);

    this.persistFeatureFlag(flag, true);
  }

  disable(flag) {
    super.disable(flag);

    this.persistFeatureFlag(flag, false);
  }
}

But, I think that there could be an API using initializers to have an app initializer like and the above implementation would become:

import { featureFlagInitializer } from 'ember-feature-flags';
import config from '../../config/environment';

const getSessionFeatureFlags = () =>  JSON.parse(sessionStorage.featureFlags ?? '{}');

const getQueryFeatureFlags = (featureFlags) => {
  let featureFlags = {};
  const params = new URLSearchParams(window.location.search);

  // Search query params for feature flags
  params.forEach((value, key) => {
    const matches = key.match(FEATURE_QP_REGEX);
    if (matches) {
      let [, flag] = matches;

      featureFlags[flag] = value !== 'false' ? true : false;
    }
  });

  return featureFlags;
}

export const initialize = featureFlagInitializer((featuresService, appInstanace) => {
  // New function that is called and passed to `setup`
  featuresService.addFeatureFlagInitializer((featureFlags) => {
    return {
      ...featureFlags,
      ...getSessionFeatureFlags(),
      ...getQueryFeatureFlags(featureFlags),
    }
  });

  featuresService.on('toggleFeature', (flag, value) => {
    let sessionFeatureFlags = getSessionFeatureFlags();

    if (config.featureFlags[flag] == value) {
      delete sessionFeatureFlags[flag];
    } else {
      sessionFeatureFlags[flag] = value;
    }

    sessionStorage.featureFlags = JSON.stringify(sessionFeatureFlags);
  });
});

export default {
    name: 'feature-flags',
    initialize
};

The change would be adding some initialization hook and then using Ember.Evented to an event listener for toggleFeature (I'm using a single event rather than having separate enable/disable but 🤷🏽)

@kategengler
Copy link
Owner

Thanks for sharing your solution.

I wouldn't recommend using Ember.Evented nor initializers in new code, so I would stick with the first way, for now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants