Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 18 additions & 24 deletions src/browser/components/Settings/sections/ExperimentsSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
type ExperimentId,
} from "@/common/constants/experiments";
import { Switch } from "@/browser/components/ui/switch";
import { useFeatureFlags, type StatsTabOverride } from "@/browser/contexts/FeatureFlagsContext";
import { useFeatureFlags } from "@/browser/contexts/FeatureFlagsContext";
import { useWorkspaceContext } from "@/browser/contexts/WorkspaceContext";
import { useTelemetry } from "@/browser/hooks/useTelemetry";

Expand Down Expand Up @@ -48,35 +48,29 @@ function ExperimentRow(props: ExperimentRowProps) {
);
}

function StatsTabOverrideRow() {
const { statsTabState, setStatsTabOverride } = useFeatureFlags();
function StatsTabRow() {
const { statsTabState, setStatsTabEnabled } = useFeatureFlags();

const onChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
const value = e.target.value as StatsTabOverride;
setStatsTabOverride(value).catch(() => {
// ignore
});
};
const handleToggle = useCallback(
(enabled: boolean) => {
setStatsTabEnabled(enabled).catch(() => {
// ignore
});
},
[setStatsTabEnabled]
);

return (
<div className="flex items-center justify-between py-3">
<div className="flex-1 pr-4">
<div className="text-foreground text-sm font-medium">Stats tab</div>
<div className="text-muted mt-0.5 text-xs">
PostHog experiment-gated timing stats sidebar. Experiment variant:{" "}
{statsTabState?.variant ?? "—"}.
</div>
<div className="text-muted mt-0.5 text-xs">Show timing statistics in the right sidebar</div>
</div>
<select
className="bg-background text-foreground border-border-light rounded-md border px-2 py-1 text-xs"
value={statsTabState?.override ?? "default"}
onChange={onChange}
aria-label="Stats tab override"
>
<option value="default">Default (experiment)</option>
<option value="on">Always on</option>
<option value="off">Always off</option>
</select>
<Switch
checked={statsTabState?.enabled ?? false}
onCheckedChange={handleToggle}
aria-label="Toggle Stats tab"
/>
</div>
);
}
Expand Down Expand Up @@ -105,7 +99,7 @@ export function ExperimentsSection() {
Experimental features that are still in development. Enable at your own risk.
</p>
<div className="divide-border-light divide-y">
<StatsTabOverrideRow />
<StatsTabRow />
{experiments.map((exp) => (
<ExperimentRow
key={exp.id}
Expand Down
27 changes: 11 additions & 16 deletions src/browser/contexts/FeatureFlagsContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,19 +26,14 @@ function isStorybook(): boolean {
return false;
}

export type StatsTabVariant = "control" | "stats";
export type StatsTabOverride = "default" | "on" | "off";

export interface StatsTabState {
enabled: boolean;
variant: StatsTabVariant;
override: StatsTabOverride;
}

interface FeatureFlagsContextValue {
statsTabState: StatsTabState | null;
refreshStatsTabState: () => Promise<void>;
setStatsTabOverride: (override: StatsTabOverride) => Promise<void>;
setStatsTabEnabled: (enabled: boolean) => Promise<void>;
}

const FeatureFlagsContext = createContext<FeatureFlagsContextValue | null>(null);
Expand All @@ -53,29 +48,29 @@ export function FeatureFlagsProvider(props: { children: ReactNode }) {
const { api } = useAPI();
const [statsTabState, setStatsTabState] = useState<StatsTabState | null>(() => {
if (isStorybook()) {
return { enabled: true, variant: "stats", override: "default" };
return { enabled: true };
}

return null;
});

const refreshStatsTabState = async (): Promise<void> => {
if (!api) {
setStatsTabState({ enabled: false, variant: "control", override: "default" });
setStatsTabState({ enabled: false });
return;
}

const state = await api.features.getStatsTabState();
setStatsTabState(state);
setStatsTabState({ enabled: state.enabled });
};

const setStatsTabOverride = async (override: StatsTabOverride): Promise<void> => {
const setStatsTabEnabled = async (enabled: boolean): Promise<void> => {
if (!api) {
throw new Error("ORPC client not initialized");
}

const state = await api.features.setStatsTabOverride({ override });
setStatsTabState(state);
const state = await api.features.setStatsTabOverride({ override: enabled ? "on" : "off" });
setStatsTabState({ enabled: state.enabled });
};

useEffect(() => {
Expand All @@ -86,22 +81,22 @@ export function FeatureFlagsProvider(props: { children: ReactNode }) {
(async () => {
try {
if (!api) {
setStatsTabState({ enabled: false, variant: "control", override: "default" });
setStatsTabState({ enabled: false });
return;
}

const state = await api.features.getStatsTabState();
setStatsTabState(state);
setStatsTabState({ enabled: state.enabled });
} catch {
// Treat as disabled if we can't fetch.
setStatsTabState({ enabled: false, variant: "control", override: "default" });
setStatsTabState({ enabled: false });
}
})();
}, [api]);

return (
<FeatureFlagsContext.Provider
value={{ statsTabState, refreshStatsTabState, setStatsTabOverride }}
value={{ statsTabState, refreshStatsTabState, setStatsTabEnabled }}
>
{props.children}
</FeatureFlagsContext.Provider>
Expand Down