Skip to content

Commit 83a277d

Browse files
authored
Merge pull request #6467 from voxel51/feat/feature-flags-v0
implement basic feature flags
2 parents e010b9e + 6b2d56d commit 83a277d

File tree

26 files changed

+1327
-20
lines changed

26 files changed

+1327
-20
lines changed

app/packages/analytics/src/useTrackEvent.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,12 @@ export default function useTrackEvent() {
1313
const info = useRecoilValue<AnalyticsInfo>(analyticsInfo);
1414
return useCallback(
1515
(eventName: string, properties?: Record<string, any>) => {
16-
const analytics = usingAnalytics(info);
17-
analytics.trackEvent(eventName, properties);
16+
try {
17+
const analytics = usingAnalytics(info);
18+
analytics.trackEvent(eventName, properties);
19+
} catch (err) {
20+
console.warn("Failed to track event", err);
21+
}
1822
},
1923
[info]
2024
);
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2+
3+
# dependencies
4+
/node_modules
5+
/.pnp
6+
.pnp.js
7+
8+
# testing
9+
/coverage
10+
11+
# production
12+
/build
13+
14+
# misc
15+
.DS_Store
16+
.env.local
17+
.env.development.local
18+
.env.test.local
19+
.env.production.local
20+
21+
npm-debug.log*
22+
yarn-debug.log*
23+
yarn-error.log*
24+
25+
dist
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
{
2+
"name": "@fiftyone/feature-flags",
3+
"version": "1.0.0",
4+
"scripts": {
5+
"dev": "vite",
6+
"build": "vite build",
7+
"preview": "vite preview",
8+
"watch": "nodemon --watch ./src --ext js,jsx,ts,tsx --exec 'yarn build'",
9+
"test": "vitest"
10+
},
11+
"files": [
12+
"dist"
13+
],
14+
"main": "src/index.ts",
15+
"dependencies": {
16+
"@fiftyone/analytics": "*"
17+
},
18+
"devDependencies": {
19+
"@testing-library/dom": "^10.4.0",
20+
"@testing-library/react": "^16.2.0",
21+
"@typescript-eslint/eslint-plugin": "^6.0.0",
22+
"@typescript-eslint/parser": "^6.0.0",
23+
"eslint": "^8.0.0",
24+
"jsdom": "^26.0.0",
25+
"typescript": "^4.7.4",
26+
"vite": "^5.4.12",
27+
"vite-plugin-externals": "^0.5.0",
28+
"vitest": "^1.6.0"
29+
},
30+
"fiftyone": {
31+
"script": "dist/index.umd.js"
32+
},
33+
"packageManager": "[email protected]",
34+
"peerDependencies": {
35+
"jotai": "*",
36+
"react": "*"
37+
}
38+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { getFetchFunction } from "@fiftyone/utilities";
2+
3+
/**
4+
* Response type for ListFeatures API.
5+
*/
6+
export type ListFeaturesResponse = {
7+
features: string[];
8+
};
9+
10+
/**
11+
* List enabled features.
12+
*/
13+
export const listEnabledFeatures = (): Promise<ListFeaturesResponse> => {
14+
return getFetchFunction()("GET", "/features");
15+
};
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
/**
2+
* Enumeration of active feature flags.
3+
*/
4+
export enum FeatureFlag {}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from "./featureClient";
2+
export * from "./flags";
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { useFeature } from "../hooks";
2+
import { Fragment } from "react";
3+
import { FeatureFlag } from "../client";
4+
5+
/**
6+
* Component which conditionally renders children based on the status of a feature.
7+
*
8+
* @param feature Feature identifier
9+
* @param children Component to render if the feature is enabled
10+
* @param fallback Optional fallback component to render if the feature is disabled
11+
*/
12+
export const FeatureFlagged = ({
13+
feature,
14+
children,
15+
fallback,
16+
}: {
17+
feature: FeatureFlag;
18+
children: React.ReactNode;
19+
fallback?: React.ReactNode;
20+
}) => {
21+
const isFeatureEnabled = useFeature({ feature });
22+
23+
const content = isFeatureEnabled ? children : fallback ?? <Fragment />;
24+
return <>{content}</>;
25+
};
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from "./FeatureFlagged";
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from "./useFeature";
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { useFeatureCache } from "./useFeatureCache";
2+
import { FeatureFlag } from "../client";
3+
import { useTrackEvent } from "@fiftyone/analytics";
4+
import { useEffect } from "react";
5+
6+
/**
7+
* Hook which provides the status of a given feature.
8+
*
9+
* @param feature Feature identifier
10+
* @param enableTracking If enabled, will emit tracking events
11+
* @returns true if the feature is enabled, else false
12+
*/
13+
export const useFeature = ({
14+
feature,
15+
enableTracking,
16+
}: {
17+
feature: FeatureFlag;
18+
enableTracking?: boolean;
19+
}): boolean => {
20+
const cache = useFeatureCache();
21+
const trackEvent = useTrackEvent();
22+
23+
const isEnabled = cache.isFeatureEnabled(feature.toString());
24+
25+
useEffect(() => {
26+
if (enableTracking) {
27+
trackEvent("VFF:CHECK", {
28+
feature,
29+
isEnabled,
30+
});
31+
}
32+
}, [enableTracking, feature, isEnabled]);
33+
34+
return isEnabled;
35+
};

0 commit comments

Comments
 (0)