Skip to content

Instantly share code, notes, and snippets.

@reecelucas
Last active March 28, 2020 15:15
Show Gist options
  • Select an option

  • Save reecelucas/a71b0cc0ebeee6da620cffc352eee1dc to your computer and use it in GitHub Desktop.

Select an option

Save reecelucas/a71b0cc0ebeee6da620cffc352eee1dc to your computer and use it in GitHub Desktop.

Revisions

  1. reecelucas revised this gist Mar 28, 2020. 1 changed file with 18 additions and 5 deletions.
    23 changes: 18 additions & 5 deletions usage-server.js
    Original file line number Diff line number Diff line change
    @@ -6,17 +6,30 @@ const app = express();

    const { isFeatureEnabled, getFeatureConfig } = getFeatureUtils({
    environment: "development",
    cookieName: "featureOverrides"
    cookieName: "featureOverrides",
    });

    const ensureFeatureUtils = (req, _res, next) => {
    if (!req.cookies) {
    throw new Error(
    "Server configuration error: ensureFeatureUtils middleware requires cookie-parser middleware upstream"
    );
    }

    const { featureOverrides } = req.cookies;

    req.isFeatureEnabled = (featureName) => isFeatureEnabled(featureName, featureOverrides);
    req.getFeatureConfig = () => getFeatureConfig(featureOverrides);
    next();
    };

    app.use(cookieParser());
    app.use(ensureFeatureUtils);

    app.get("/", (req, res) => {
    const featureOverrides = req.cookies.featureOverrides;

    console.log(getFeatureConfig(featureOverrides)); // { FEATURE_A: true, FEATURE_B: false }
    console.log(req.getFeatureConfig()); // { FEATURE_A: true, FEATURE_B: false }

    if (isFeatureEnabled("FEATURE_A", featureOverrides)) {
    if (req.isFeatureEnabled("FEATURE_A")) {
    res.send("FEATURE_A is enabled");
    } else {
    res.send("FEATURE_A is NOT enabled");
  2. reecelucas revised this gist Mar 27, 2020. 2 changed files with 3 additions and 4 deletions.
    5 changes: 2 additions & 3 deletions featureFlags.js
    Original file line number Diff line number Diff line change
    @@ -42,7 +42,7 @@ export const getFeatureUtils = ({ environment, cookieName }) => {
    const config = getConfig(environment, cookieValue);
    const value = config[featureName];

    if (!value) {
    if (value === undefined) {
    throw new Error(
    `${featureName} is not defined for the ${environment} environment`
    );
    @@ -51,8 +51,7 @@ export const getFeatureUtils = ({ environment, cookieName }) => {
    return value;
    };

    // `values` should be an object, E.g. { FEATURE_A: false }.
    // This function could be called on a feature-flipper UI to set features for local development.
    // `values` should be an object. E.g. setFeatureOverrides({ FEATURE_A: false })
    const setFeatureOverrides = values => {
    Cookies.set(cookieName, values);
    };
    2 changes: 1 addition & 1 deletion usage-client.js
    Original file line number Diff line number Diff line change
    @@ -12,7 +12,7 @@ const {
    console.log(isFeatureEnabled("FEATURE_B")); // false
    console.log(getFeatureConfig()); // { FEATURE_A: true, FEATURE_B: false }

    setFeatureOverrides("featureOverrides", { FEATURE_B: true });
    setFeatureOverrides({ FEATURE_B: true });

    // After refreshing page...
    console.log(isFeatureEnabled("FEATURE_B")); // true
  3. reecelucas revised this gist Mar 27, 2020. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion featureFlags.js
    Original file line number Diff line number Diff line change
    @@ -59,7 +59,7 @@ export const getFeatureUtils = ({ environment, cookieName }) => {

    const getFeatureConfig = cookieString => {
    const cookieValue = cookieString || cookieValueFromDocument;
    return getConfig(environment, cookieString);
    return getConfig(environment, cookieValue);
    };

    return { isFeatureEnabled, setFeatureOverrides, getFeatureConfig };
  4. reecelucas revised this gist Mar 26, 2020. 1 changed file with 6 additions and 6 deletions.
    12 changes: 6 additions & 6 deletions featureFlags.js
    Original file line number Diff line number Diff line change
    @@ -10,19 +10,19 @@ const getDefaultConfigForEnv = environment =>
    {}
    );

    const getConfigOverridesFromCookie = cookieValue => {
    const getConfigOverridesFromCookie = cookieString => {
    try {
    return cookieValue ? JSON.parse(cookieValue) : {};
    return cookieString ? JSON.parse(cookieString) : {};
    } catch (error) {
    throw new Error(
    "Could not determine feature config overrides: error parsing cookie value"
    );
    }
    };

    const getConfig = (environment, cookieValue) => {
    const getConfig = (environment, cookieString) => {
    const defaultConfig = getDefaultConfigForEnv(environment);
    const overrides = getConfigOverridesFromCookie(cookieValue);
    const overrides = getConfigOverridesFromCookie(cookieString);

    return {
    ...defaultConfig,
    @@ -39,7 +39,7 @@ export const getFeatureUtils = ({ environment, cookieName }) => {
    // be accessed on the `request` object. On the client we retrieve the cookie value from
    // the document.
    const cookieValue = cookieString || cookieValueFromDocument;
    const config = getConfig(environment, cookieString);
    const config = getConfig(environment, cookieValue);
    const value = config[featureName];

    if (!value) {
    @@ -62,5 +62,5 @@ export const getFeatureUtils = ({ environment, cookieName }) => {
    return getConfig(environment, cookieString);
    };

    return { isFeatureEnabled, getFeatureConfig, setFeatureOverrides };
    return { isFeatureEnabled, setFeatureOverrides, getFeatureConfig };
    };
  5. reecelucas created this gist Mar 26, 2020.
    19 changes: 19 additions & 0 deletions featureConfig.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,19 @@
    export default {
    FEATURE_A: {
    name: 'FEATURE_A',
    description: 'Human readable description of feature A',
    enabled: {
    development: true,
    production: false
    }
    },
    FEATURE_B: {
    name: 'FEATURE_B',
    description: 'Human readable description of feature B',
    enabled: {
    development: false,
    production: true
    }
    }
    };

    66 changes: 66 additions & 0 deletions featureFlags.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,66 @@
    import config from "./featureConfig";
    import Cookies from "js-cookie";

    const getDefaultConfigForEnv = environment =>
    Object.entries(config).reduce(
    (obj, [key, value]) => ({
    ...obj,
    [key]: value.enabled[environment]
    }),
    {}
    );

    const getConfigOverridesFromCookie = cookieValue => {
    try {
    return cookieValue ? JSON.parse(cookieValue) : {};
    } catch (error) {
    throw new Error(
    "Could not determine feature config overrides: error parsing cookie value"
    );
    }
    };

    const getConfig = (environment, cookieValue) => {
    const defaultConfig = getDefaultConfigForEnv(environment);
    const overrides = getConfigOverridesFromCookie(cookieValue);

    return {
    ...defaultConfig,
    ...overrides
    };
    };

    export const getFeatureUtils = ({ environment, cookieName }) => {
    const cookieValueFromDocument = Cookies.get(cookieName);

    const isFeatureEnabled = (featureName, cookieString) => {
    // If `cookieString` is provided we use this to determine feature overrides.
    // This is useful when using `isFeatureEnabled` on the server, where `cookieName` can
    // be accessed on the `request` object. On the client we retrieve the cookie value from
    // the document.
    const cookieValue = cookieString || cookieValueFromDocument;
    const config = getConfig(environment, cookieString);
    const value = config[featureName];

    if (!value) {
    throw new Error(
    `${featureName} is not defined for the ${environment} environment`
    );
    }

    return value;
    };

    // `values` should be an object, E.g. { FEATURE_A: false }.
    // This function could be called on a feature-flipper UI to set features for local development.
    const setFeatureOverrides = values => {
    Cookies.set(cookieName, values);
    };

    const getFeatureConfig = cookieString => {
    const cookieValue = cookieString || cookieValueFromDocument;
    return getConfig(environment, cookieString);
    };

    return { isFeatureEnabled, getFeatureConfig, setFeatureOverrides };
    };
    19 changes: 19 additions & 0 deletions usage-client.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,19 @@
    import { getFeatureUtils } from "./featureFlags";

    const {
    isFeatureEnabled,
    getFeatureConfig,
    setFeatureOverrides
    } = getFeatureUtils({
    environment: "development",
    cookieName: "featureOverrides"
    });

    console.log(isFeatureEnabled("FEATURE_B")); // false
    console.log(getFeatureConfig()); // { FEATURE_A: true, FEATURE_B: false }

    setFeatureOverrides("featureOverrides", { FEATURE_B: true });

    // After refreshing page...
    console.log(isFeatureEnabled("FEATURE_B")); // true
    console.log(getFeatureConfig()); // { FEATURE_A: true, FEATURE_B: true }
    24 changes: 24 additions & 0 deletions usage-server.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,24 @@
    import express from "express";
    import cookieParser from "cookie-parser";
    import { getFeatureUtils } from "./featureFlags";

    const app = express();

    const { isFeatureEnabled, getFeatureConfig } = getFeatureUtils({
    environment: "development",
    cookieName: "featureOverrides"
    });

    app.use(cookieParser());

    app.get("/", (req, res) => {
    const featureOverrides = req.cookies.featureOverrides;

    console.log(getFeatureConfig(featureOverrides)); // { FEATURE_A: true, FEATURE_B: false }

    if (isFeatureEnabled("FEATURE_A", featureOverrides)) {
    res.send("FEATURE_A is enabled");
    } else {
    res.send("FEATURE_A is NOT enabled");
    }
    });