launchdarkly / react-client-sdk

LaunchDarkly Client-side SDK for React.js
Other
86 stars 68 forks source link

Overriding Feature Flag Manually #53

Closed mustafashabib closed 3 years ago

mustafashabib commented 3 years ago

Is your feature request related to a problem? Please describe. I'd love to know how to manually override a feature flag using the useFlags hook to force certain variations for testing (headless browser for example) purposes

Describe the solution you'd like Set a localStorage var or cookie and have the LD react client bias to using those values before checking with the LD service.

Describe alternatives you've considered Creating our own useFlags hook that wraps the LD hook but which has this behavior.

bwoskow-ld commented 3 years ago

Hi @mustafashabib ,

Are you using jest? If so check out https://github.com/launchdarkly-labs/jest-launchdarkly-mock to manually set flag values in your tests.

mustafashabib commented 3 years ago

Thanks @bwoskow-ld - that's definitely useful. We were thinking of how we might test this for cypress end to end tests (or even to manually override just for dev purposes while we're working through a new feature to force a specific variant).

Thanks

bwoskow-ld commented 3 years ago

Is the jest-launchdarkly-mock library sufficient for your testing use case? Let me know if there's anything else or if I can close this ticket.

As for overriding values for development, our best practice is to have a unique LaunchDarkly environment per developer on your team. This way, each developer can use the LaunchDarkly UI to manage flags for their local environment without impacting any other developers or environments.

bwoskow-ld commented 3 years ago

@mustafashabib I'm going to close this issue now. Let me know if there's anything else on the topic you want to add.

nweajoseph commented 2 years ago

Is the jest-launchdarkly-mock library sufficient for your testing use case? Let me know if there's anything else or if I can close this ticket.

As for overriding values for development, our best practice is to have a unique LaunchDarkly environment per developer on your team. This way, each developer can use the LaunchDarkly UI to manage flags for their local environment without impacting any other developers or environments.

this sounds cool, but in my workplace we have limited licenses. only 3 people have access to launchdarkly as users, so there's no way for all of us to use a unique environment to toggle flags arbitrarily during development. for us, some way to manually define the flag values would be ideal:

const ldProviderConfig: ProviderConfig = {
  clientSideID: process.env.LD_CLIENT_ID || '',
  user: ldAnonUser,
  options: {
    flags: {
      'flag-being-developed': true // this flag will always resolve to true in this client
      'flag-not-being-developed': false // this flag will always resolve to false in this client
    }
  },
}
const LDProvider = withLDProvider(ldProviderConfig)(App)

or something like that

nweajoseph commented 2 years ago

i originally tried to write my own context provider / wrapper that offered consumers some predefined static flag values and left the others alone, but i don't see any way to hook into withLDProvider that really allows that strategy

mustafashabib commented 2 years ago

Here's what we ended up doing -- the ENABLE_LAUNCHDARKLY envvar is set on the env which our headless testing process has set to false, and we set localStorage values with any overrides we happen to be testing in the moment. This also lets us force a flag variant locally or in our other environments manually by doing something like

localStorage.setItem("override-flags", JSON.stringify({"myFlagName":  true}));
import type { LDFlagSet } from 'launchdarkly-js-sdk-common';
import { useFlags as useLDFlags } from 'launchdarkly-react-client-sdk';
import { useState } from 'react';
import { ENABLE_LAUNCHDARKLY } from '../constants/env';

const useFlags = (): LDFlagSet => {
    const getManualOverrides = (): LDFlagSet => {
        try {
            return JSON.parse(localStorage.getItem('override-flags') || '{}');
        } catch {
            return {};
        }
    };

    const [overrides, setOverrides] = useState(getManualOverrides());

    window.onstorage = (): void => {
        setOverrides(getManualOverrides());
    };

    const ldFlags = useLDFlags();

    if (ENABLE_LAUNCHDARKLY) {
        const mergedFlags = { ...ldFlags, ...(overrides as LDFlagSet) } as LDFlagSet;
        return mergedFlags;
    } else return overrides as LDFlagSet;
};

export default useFlags;

and now we just use this hook in place of the useFlags vended by the LD library.

nweajoseph commented 2 years ago

@mustafashabib oh i see that makes sense. you merge the static flag values after fetching the real ones.

biggytech commented 2 years ago

Would be ideal if we could to add some manual set flags into ProviderConfig. It is very handy for local development, for example.

kellyrmilligan commented 2 years ago

I think this should be re-opened. for larger teams, it is really nice to locally be able to toggle things off and on regardless of which env you're pointed to. would be nice to have something like react-query's dev tools to add flags manually and do a merge of sorts.

kellyrmilligan commented 2 years ago

as fyi, my company added something similar to the above as well, but suited to our own needs. There is definitely a need for this IMO.

@mustafashabib 's initial code was a great start for my needs!

kyin42 commented 1 year ago

Just wanted to leave a note for anyone attempting to use a custom useFlags wrapper hook and who is using targeting/experiments, as far as I can tell wrapping useFlags removes the Proxy and will not send evaluation events anymore. I haven't figured out how to keep these events firing yet, so if you go this route maybe instead of returning flags return a function to fetch a flag. or as a shotgun approach turn sendEventsOnlyForVariation off which was turned on in https://github.com/launchdarkly/react-client-sdk/releases/tag/2.27.0 which makes sure all evaluations are sent.

As an reference my useFlags wrapper looks like this:

import type { LDFlagSet } from 'launchdarkly-js-sdk-common';
import { useFlags as useLDFlags } from 'launchdarkly-react-client-sdk';
import { isLocal } from '../config';

// This attempts to import ../flagOverrides.ts but uses a default {} if it doesn't exist
let flagOverrides: { default: Partial<LDFlagSet> } = { default: {} };
try {
  // eslint-disable-next-line global-require, import/no-unresolved, import/extensions
  flagOverrides = require('../flagOverrides');
} catch (ex) {
  // do nothing: use default empty flags
}

// a way to override flags for development until launchdarkly supports this https://github.com/launchdarkly/react-client-sdk/issues/53
// view flagOverrides.ts in the root directory for more information
const useFlags = () => {
  const ldFlags = useLDFlags();

  if (isLocal) {
    const mergedFlags = { ...ldFlags, ...flagOverrides.default };
    return mergedFlags;
  }
  return ldFlags;
};

export default useFlags;

But I'd image mustafashabib has the same issue with the proxy

kevinfiol commented 1 year ago

Surprising this feature is not included as part of the SDK. Thanks all for posting your solutions.

Feshchenko commented 1 year ago

@bwoskow-ld, hi. I don't want to create a new issue but want to add another use case for merging flags from the ProviderConfig. For example, I'm a QA and want to quickly disable/enable some flags wo/ going to admin system. In our App we have a custom hook that merges query params flagsOn and flagsOff to flags from the useFlags. It would be great to have an option in the LDProvider to merge flags values wo/ work arounds.