Skip to content

Commit

Permalink
feat: feature flags context (#1802)
Browse files Browse the repository at this point in the history
* feat: feature flags context

* feature-flags hot key

* fix imports
  • Loading branch information
mikeldking authored Nov 27, 2023
1 parent d6aa76e commit a2732cd
Show file tree
Hide file tree
Showing 5 changed files with 135 additions and 5 deletions.
16 changes: 16 additions & 0 deletions app/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"react-dom": "18.2",
"react-error-boundary": "^4.0.3",
"react-hook-form": "^7.43.8",
"react-hotkeys-hook": "^4.4.1",
"react-relay": "^15.0.0",
"react-resizable-panels": "^0.0.54",
"react-router": "^6.7.0",
Expand Down
13 changes: 8 additions & 5 deletions app/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { ThemeProvider as EmotionThemeProvider } from "@emotion/react";

import { Provider, theme } from "@arizeai/components";

import { FeatureFlagsProvider } from "./contexts/FeatureFlagsContext";
import { NotificationProvider, ThemeProvider, useTheme } from "./contexts";
import { GlobalStyles } from "./GlobalStyles";
import RelayEnvironment from "./RelayEnvironment";
Expand All @@ -26,11 +27,13 @@ export function AppContent() {
<EmotionThemeProvider theme={theme}>
<RelayEnvironmentProvider environment={RelayEnvironment}>
<GlobalStyles />
<Suspense>
<NotificationProvider>
<AppRoutes />
</NotificationProvider>
</Suspense>
<FeatureFlagsProvider>
<Suspense>
<NotificationProvider>
<AppRoutes />
</NotificationProvider>
</Suspense>
</FeatureFlagsProvider>
</RelayEnvironmentProvider>
</EmotionThemeProvider>
</Provider>
Expand Down
107 changes: 107 additions & 0 deletions app/src/contexts/FeatureFlagsContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import React, { createContext, PropsWithChildren, useState } from "react";
import { useHotkeys } from "react-hotkeys-hook";

import { Dialog, DialogContainer, Switch, View } from "@arizeai/components";

type FeatureFlag = "evals";
export type FeatureFlagsContextType = {
featureFlags: Record<FeatureFlag, boolean>;
setFeatureFlags: (featureFlags: Record<FeatureFlag, boolean>) => void;
};

export const LOCAL_STORAGE_FEATURE_FLAGS_KEY = "arize-phoenix-feature-flags";

const DEFAULT_FEATURE_FLAGS: Record<FeatureFlag, boolean> = {
evals: false,
};

function getFeatureFlags(): Record<FeatureFlag, boolean> {
const featureFlagsFromLocalStorage = localStorage.getItem(
LOCAL_STORAGE_FEATURE_FLAGS_KEY
);
if (!featureFlagsFromLocalStorage) {
return DEFAULT_FEATURE_FLAGS;
}

try {
const parsedFeatureFlags = JSON.parse(featureFlagsFromLocalStorage);
return Object.assign({}, DEFAULT_FEATURE_FLAGS, parsedFeatureFlags);
} catch (e) {
return DEFAULT_FEATURE_FLAGS;
}
}

export const FeatureFlagsContext =
createContext<FeatureFlagsContextType | null>(null);

export function useFeatureFlags() {
const context = React.useContext(FeatureFlagsContext);
if (context === null) {
throw new Error(
"useFeatureFlags must be used within a FeatureFlagsProvider"
);
}
return context;
}

export function useFeatureFlag(featureFlag: FeatureFlag) {
const { featureFlags } = useFeatureFlags();
return featureFlags[featureFlag];
}

export function FeatureFlagsProvider(props: React.PropsWithChildren) {
const [featureFlags, _setFeatureFlags] = useState<
Record<FeatureFlag, boolean>
>(getFeatureFlags());
const setFeatureFlags = (featureFlags: Record<FeatureFlag, boolean>) => {
localStorage.setItem(
LOCAL_STORAGE_FEATURE_FLAGS_KEY,
JSON.stringify(featureFlags)
);
_setFeatureFlags(featureFlags);
};

return (
<FeatureFlagsContext.Provider value={{ featureFlags, setFeatureFlags }}>
<FeatureFlagsControls>{props.children}</FeatureFlagsControls>
</FeatureFlagsContext.Provider>
);
}

function FeatureFlagsControls(props: PropsWithChildren) {
const { children } = props;
const { featureFlags, setFeatureFlags } = useFeatureFlags();
const [showControls, setShowControls] = useState(false);
useHotkeys("ctrl+shift+f", () => setShowControls(true));
return (
<>
{children}
<DialogContainer
type="modal"
isDismissable
onDismiss={() => setShowControls(false)}
>
{showControls && (
<Dialog title="Feature Flags">
<View height="size-1000" padding="size-100">
{Object.keys(featureFlags).map((featureFlag) => (
<Switch
key={featureFlag}
isSelected={featureFlags[featureFlag as FeatureFlag]}
onChange={(isSelected) =>
setFeatureFlags({
...featureFlags,
[featureFlag]: isSelected,
})
}
>
{featureFlag}
</Switch>
))}
</View>
</Dialog>
)}
</DialogContainer>
</>
);
}
3 changes: 3 additions & 0 deletions app/src/pages/trace/TracePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import { SpanItem } from "@phoenix/components/trace/SpanItem";
import { SpanKindIcon } from "@phoenix/components/trace/SpanKindIcon";
import { TraceTree } from "@phoenix/components/trace/TraceTree";
import { useTheme } from "@phoenix/contexts";
import { useFeatureFlag } from "@phoenix/contexts/FeatureFlagsContext";
import {
DOCUMENT_CONTENT,
DOCUMENT_ID,
Expand Down Expand Up @@ -267,6 +268,7 @@ function SelectedSpanDetails({ selectedSpan }: { selectedSpan: Span }) {
const hasExceptions = useMemo<boolean>(() => {
return spanHasException(selectedSpan);
}, [selectedSpan]);
const evalsEnabled = useFeatureFlag("evals");
return (
<Flex direction="column" flex="1 1 auto" height="100%">
<View
Expand Down Expand Up @@ -303,6 +305,7 @@ function SelectedSpanDetails({ selectedSpan }: { selectedSpan: Span }) {
>
<SpanEventsList events={selectedSpan.events} />
</TabPane>
{evalsEnabled ? <TabPane name={"Evals"}>Evals Tab</TabPane> : null}
</Tabs>
</Flex>
);
Expand Down

0 comments on commit a2732cd

Please sign in to comment.