Skip to content

Commit

Permalink
Merge pull request #244 from Flagsmith/feat/hasfeature-fallback
Browse files Browse the repository at this point in the history
Support fallback for hasFeature, add options.skipAnalytics
  • Loading branch information
kyle-ssg authored Sep 4, 2024
2 parents e4b4b46 + f90266d commit 69209a3
Show file tree
Hide file tree
Showing 6 changed files with 72 additions and 18 deletions.
17 changes: 11 additions & 6 deletions flagsmith-core.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
DynatraceObject,
GetValueOptions,
HasFeatureOptions,
IDatadogRum,
IFlags,
IFlagsmith,
Expand Down Expand Up @@ -151,8 +152,8 @@ const Flagsmith = class {
javaLongOrObject: {},
}
Object.keys(this.flags).map((key) => {
setDynatraceValue(traits, FLAGSMITH_CONFIG_ANALYTICS_KEY + key, this.getValue(key, {}, true))
setDynatraceValue(traits, FLAGSMITH_FLAG_ANALYTICS_KEY + key, this.hasFeature(key, true))
setDynatraceValue(traits, FLAGSMITH_CONFIG_ANALYTICS_KEY + key, this.getValue(key, { skipAnalytics: true }))
setDynatraceValue(traits, FLAGSMITH_FLAG_ANALYTICS_KEY + key, this.hasFeature(key, { skipAnalytics: true }))
})
Object.keys(this.traits).map((key) => {
setDynatraceValue(traits, FLAGSMITH_TRAIT_ANALYTICS_KEY + key, this.getTrait(key))
Expand Down Expand Up @@ -588,7 +589,7 @@ const Flagsmith = class {
res = flag.value;
}

if (!skipAnalytics) {
if (!options?.skipAnalytics && !skipAnalytics) {
this.evaluateFlag(key, "VALUE");
}

Expand Down Expand Up @@ -656,13 +657,17 @@ const Flagsmith = class {
}
};

hasFeature = (key: string, skipAnalytics?: boolean) => {
hasFeature = (key: string, options?: HasFeatureOptions) => {
// Support legacy skipAnalytics boolean parameter
const usingNewOptions = typeof options === 'object'
const flag = this.flags && this.flags[key.toLowerCase().replace(/ /g, '_')];
let res = false;
if (flag && flag.enabled) {
if (!flag && usingNewOptions && typeof options.fallback !== 'undefined') {
res = options?.fallback
} else if (flag && flag.enabled) {
res = true;
}
if (!skipAnalytics) {
if ((usingNewOptions && !options.skipAnalytics) || !options) {
this.evaluateFlag(key, "ENABLED");
}

Expand Down
2 changes: 1 addition & 1 deletion lib/flagsmith-es/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "flagsmith-es",
"version": "4.0.3",
"version": "4.1.0",
"description": "Feature flagging to support continuous development. This is an esm equivalent of the standard flagsmith npm module.",
"main": "./index.js",
"type": "module",
Expand Down
2 changes: 1 addition & 1 deletion lib/flagsmith/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "flagsmith",
"version": "4.0.3",
"version": "4.1.0",
"description": "Feature flagging to support continuous development",
"main": "./index.js",
"repository": {
Expand Down
2 changes: 1 addition & 1 deletion lib/react-native-flagsmith/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "react-native-flagsmith",
"version": "4.0.3",
"version": "4.1.0",
"description": "Feature flagging to support continuous development",
"main": "./index.js",
"repository": {
Expand Down
22 changes: 22 additions & 0 deletions test/functions.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Sample test
import { getFlagsmith } from './test-constants';

describe('Flagsmith.functions', () => {

beforeEach(() => {
// Avoid mocks, but if you need to add them here
});
test('should use a fallback when the feature is undefined', async () => {
const onChange = jest.fn()
const {flagsmith,initConfig, AsyncStorage,mockFetch} = getFlagsmith({onChange})
await flagsmith.init(initConfig);

expect(flagsmith.getValue("deleted_feature",{fallback:"foo"})).toBe("foo");
expect(flagsmith.hasFeature("deleted_feature",{fallback:true})).toBe(true);
expect(flagsmith.hasFeature("deleted_feature",{fallback:false})).toBe(false);
expect(flagsmith.hasFeature("font_size",{fallback:false})).toBe(true);
expect(flagsmith.getValue("font_size",{fallback:100})).toBe(16);
expect(flagsmith.getValue("font_size")).toBe(16);
expect(flagsmith.hasFeature("font_size")).toBe(true);
})
});
45 changes: 36 additions & 9 deletions types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,16 @@ export declare type IFlags<F extends string = string> = Record<F, IFlagsmithFeat
export declare type ITraits<T extends string = string> = Record<T, IFlagsmithTrait>;

export declare type GetValueOptions<T = Array<any> | object> = {
json?: boolean;
skipAnalytics?: boolean
json?: boolean
fallback?: T
}

export declare type HasFeatureOptions = {
skipAnalytics?: boolean
fallback?: boolean
} | boolean


export interface IRetrieveInfo {
isFromServer: boolean;
Expand Down Expand Up @@ -149,14 +155,35 @@ export interface IFlagsmith<F extends string = string, T extends string = string
*/
stopListening: () => void;
/**
* Get the whether a flag is enabled e.g. flagsmith.hasFeature("powerUserFeature")
*/
hasFeature: (key: F) => boolean;

/**
* Get the value of a particular remote config e.g. flagsmith.getValue("font_size")
*/
getValue<T = IFlagsmithValue>(key: F, options?: GetValueOptions<T>): IFlagsmithValue<T>;
* Returns whether a feature is enabled, or a fallback value if it does not exist.
* @param {HasFeatureOptions} [optionsOrSkipAnalytics=false] If `true`, will not track analytics for this flag
* evaluation. Using a boolean for this parameter is deprecated - use `{ skipAnalytics: true }` instead.
* @param [optionsOrSkipAnalytics.fallback=false] Returns this value if the feature does not exist.
* @param [optionsOrSkipAnalytics.skipAnalytics=false] If `true`, do not track analytics for this feature evaluation.
* @example
* flagsmith.hasFeature("power_user_feature")
* @example
* flagsmith.hasFeature("enabled_by_default_feature", { fallback: true })
*/
hasFeature: (key: F, optionsOrSkipAnalytics?: HasFeatureOptions) => boolean;

/**
* Returns the value of a feature, or a fallback value.
* @param [options.json=false] Deserialise the feature value using `JSON.parse` and return the result or `options.fallback`.
* @param [options.fallback=null] Return this value in any of these cases:
* * The feature does not exist.
* * The feature has no value.
* * `options.json` is `true` and the feature's value is not valid JSON.
* @param [options.skipAnalytics=false] If `true`, do not track analytics for this feature evaluation.
* @param [skipAnalytics=false] Deprecated - use `options.skipAnalytics` instead.
* @example
* flagsmith.getValue("remote_config") // "{\"hello\":\"world\"}"
* flagsmith.getValue("remote_config", { json: true }) // { hello: "world" }
* @example
* flagsmith.getValue("font_size") // "12px"
* flagsmith.getValue("font_size", { json: true, fallback: "8px" }) // "8px"
*/
getValue<T = IFlagsmithValue>(key: F, options?: GetValueOptions<T>, skipAnalytics?: boolean): IFlagsmithValue<T>;

/**
* Get the value of a particular trait for the identified user
Expand Down

0 comments on commit 69209a3

Please sign in to comment.