feat: create Frontend feature flags (#3029)

* Add UI feature flag config

* [autofix.ci] apply automated fixes

* hide general settings if there is nothing to show

* make sure to handle !autoLogin case

* [autofix.ci] apply automated fixes

* missed commit

* [autofix.ci] apply automated fixes

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: anovazzi1 <otavio2204@gmail.com>
This commit is contained in:
Mike Fortman 2024-07-30 16:19:05 -05:00 committed by GitHub
commit 72ff6d3a58
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 127 additions and 54 deletions

View file

@ -0,0 +1,6 @@
{
"ENABLE_DARK_MODE": true,
"ENABLE_API": true,
"ENABLE_LANGFLOW_STORE": true,
"ENABLE_PROFILE_ICONS": true
}

View file

@ -1,3 +1,4 @@
import FeatureFlags from "@/../feature-config.json";
import { Transition } from "@headlessui/react";
import { useMemo, useRef, useState } from "react";
import { useHotkeys } from "react-hotkeys-hook";
@ -140,30 +141,34 @@ export default function FlowToolbar(): JSX.Element {
<div>
<Separator orientation="vertical" />
</div>
<div className="flex cursor-pointer items-center gap-2">
{currentFlow && currentFlow.data && (
<ApiModal
flow={currentFlow}
open={openCodeModal}
setOpen={setOpenCodeModal}
>
<div
className={classNames(
"relative inline-flex w-full items-center justify-center gap-1 px-5 py-3 text-sm font-semibold text-foreground transition-all duration-150 ease-in-out hover:bg-hover",
)}
>
<ForwardedIconComponent
name="Code2"
className={"h-5 w-5"}
/>
API
</div>
</ApiModal>
)}
</div>
<div>
<Separator orientation="vertical" />
</div>
{FeatureFlags.ENABLE_API && (
<>
<div className="flex cursor-pointer items-center gap-2">
{currentFlow && currentFlow.data && (
<ApiModal
flow={currentFlow}
open={openCodeModal}
setOpen={setOpenCodeModal}
>
<div
className={classNames(
"relative inline-flex w-full items-center justify-center gap-1 px-5 py-3 text-sm font-semibold text-foreground transition-all duration-150 ease-in-out hover:bg-hover",
)}
>
<ForwardedIconComponent
name="Code2"
className={"h-5 w-5"}
/>
API
</div>
</ApiModal>
)}
</div>
<div>
<Separator orientation="vertical" />
</div>
</>
)}
<div className="flex items-center gap-2">
<div
className={`side-bar-button ${

View file

@ -11,6 +11,7 @@ import {
} from "../../constants/constants";
import { AuthContext } from "../../contexts/authContext";
import FeatureFlags from "@/../feature-config.json";
import { useLogout } from "@/controllers/API/queries/auth";
import useAuthStore from "@/stores/authStore";
import useAlertStore from "../../stores/alertStore";
@ -182,18 +183,26 @@ export default function Header(): JSX.Element {
</a>
<Separator orientation="vertical" />
<button
className="extra-side-bar-save-disable"
onClick={() => {
setDark(!dark);
}}
>
{dark ? (
<IconComponent name="SunIcon" className="side-bar-button-size" />
) : (
<IconComponent name="MoonIcon" className="side-bar-button-size" />
)}
</button>
{FeatureFlags.ENABLE_DARK_MODE && (
<button
className="extra-side-bar-save-disable"
onClick={() => {
setDark(!dark);
}}
>
{dark ? (
<IconComponent
name="SunIcon"
className="side-bar-button-size"
/>
) : (
<IconComponent
name="MoonIcon"
className="side-bar-button-size"
/>
)}
</button>
)}
<AlertDropdown>
<div className="extra-side-bar-save-disable relative">
{notificationCenter && (
@ -216,10 +225,17 @@ export default function Header(): JSX.Element {
data-testid="user-profile-settings"
className="shrink-0"
>
<img
src={profileImageUrl}
className="h-7 w-7 shrink-0 focus-visible:outline-0"
/>
{FeatureFlags.ENABLE_PROFILE_ICONS ? (
<img
src={profileImageUrl}
className="h-7 w-7 shrink-0 focus-visible:outline-0"
/>
) : (
<IconComponent
name="Settings"
className="side-bar-button-size"
/>
)}
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="mr-1 mt-1 min-w-40">

View file

@ -1,3 +1,6 @@
import FeatureFlags from "@/../feature-config.json";
import useAuthStore from "@/stores/authStore";
import { useStoreStore } from "@/stores/storeStore";
import { useEffect } from "react";
import { Outlet } from "react-router-dom";
import ForwardedIconComponent from "../../components/genericIconComponent";
@ -10,12 +13,26 @@ export default function SettingsPage(): JSX.Element {
const setCurrentFlowId = useFlowsManagerStore(
(state) => state.setCurrentFlowId,
);
const autoLogin = useAuthStore((state) => state.autoLogin);
const hasStore = useStoreStore((state) => state.hasStore);
// Hides the General settings if there is nothing to show
const showGeneralSettings =
FeatureFlags.ENABLE_PROFILE_ICONS || hasStore || !autoLogin;
useEffect(() => {
setCurrentFlowId("");
}, [pathname]);
const sidebarNavItems = [
{
const sidebarNavItems: {
href?: string;
title: string;
icon: React.ReactNode;
}[] = [];
if (showGeneralSettings) {
sidebarNavItems.push({
title: "General",
href: "/settings/general",
icon: (
@ -24,7 +41,10 @@ export default function SettingsPage(): JSX.Element {
className="w-4 flex-shrink-0 justify-start stroke-[1.5]"
/>
),
},
});
}
sidebarNavItems.push(
{
title: "Global Variables",
href: "/settings/global-variables",
@ -65,7 +85,7 @@ export default function SettingsPage(): JSX.Element {
/>
),
},
];
);
return (
<PageLayout
title="Settings"

View file

@ -1,3 +1,4 @@
import FeatureFlags from "@/../feature-config.json";
import { usePostAddApiKey } from "@/controllers/API/queries/api-keys";
import { useGetProfilePicturesQuery } from "@/controllers/API/queries/files";
import useAuthStore from "@/stores/authStore";
@ -101,13 +102,15 @@ export const GeneralPage = () => {
<GeneralPageHeaderComponent />
<div className="grid gap-6">
<ProfilePictureFormComponent
profilePicture={profilePicture}
handleInput={handleInput}
handlePatchProfilePicture={handlePatchProfilePicture}
handleGetProfilePictures={handleGetProfilePictures}
userData={userData}
/>
{FeatureFlags.ENABLE_PROFILE_ICONS && (
<ProfilePictureFormComponent
profilePicture={profilePicture}
handleInput={handleInput}
handlePatchProfilePicture={handlePatchProfilePicture}
handleGetProfilePictures={handleGetProfilePictures}
userData={userData}
/>
)}
{!autoLogin && (
<PasswordFormComponent

View file

@ -1,3 +1,6 @@
import FeatureFlags from "@/../feature-config.json";
import useAuthStore from "@/stores/authStore";
import { useStoreStore } from "@/stores/storeStore";
import { Suspense, lazy } from "react";
import { Navigate, Route, Routes } from "react-router-dom";
import { ProtectedAdminRoute } from "./components/authAdminGuard";
@ -36,6 +39,13 @@ const StorePage = lazy(() => import("./pages/StorePage"));
const ViewPage = lazy(() => import("./pages/ViewPage"));
const Router = () => {
const autoLogin = useAuthStore((state) => state.autoLogin);
const hasStore = useStoreStore((state) => state.hasStore);
// Hides the General settings if there is nothing to show
const showGeneralSettings =
FeatureFlags.ENABLE_PROFILE_ICONS || hasStore || !autoLogin;
return (
<Suspense
fallback={
@ -77,10 +87,20 @@ const Router = () => {
</ProtectedRoute>
}
>
<Route index element={<Navigate replace to={"general"} />} />
<Route
index
element={
<Navigate
replace
to={showGeneralSettings ? "general" : "global-variables"}
/>
}
/>
<Route path="global-variables" element={<GlobalVariablesPage />} />
<Route path="api-keys" element={<ApiKeysPage />} />
<Route path="general/:scrollId?" element={<GeneralPage />} />
{showGeneralSettings && (
<Route path="general/:scrollId?" element={<GeneralPage />} />
)}
<Route path="shortcuts" element={<ShortcutsPage />} />
<Route path="messages" element={<MessagesPage />} />
</Route>

View file

@ -1,3 +1,4 @@
import FeatureFlags from "@/../feature-config.json";
import { create } from "zustand";
import { checkHasApiKey, checkHasStore } from "../controllers/API";
import { StoreStoreType } from "../types/zustand/store";
@ -9,7 +10,9 @@ export const useStoreStore = create<StoreStoreType>((set) => ({
loadingApiKey: true,
checkHasStore: () => {
checkHasStore().then((res) => {
set({ hasStore: res?.enabled ?? false });
set({
hasStore: FeatureFlags.ENABLE_LANGFLOW_STORE && (res?.enabled ?? false),
});
});
},
updateValidApiKey: (validApiKey) => set(() => ({ validApiKey: validApiKey })),