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 { Transition } from "@headlessui/react";
import { useMemo, useRef, useState } from "react"; import { useMemo, useRef, useState } from "react";
import { useHotkeys } from "react-hotkeys-hook"; import { useHotkeys } from "react-hotkeys-hook";
@ -140,30 +141,34 @@ export default function FlowToolbar(): JSX.Element {
<div> <div>
<Separator orientation="vertical" /> <Separator orientation="vertical" />
</div> </div>
<div className="flex cursor-pointer items-center gap-2"> {FeatureFlags.ENABLE_API && (
{currentFlow && currentFlow.data && ( <>
<ApiModal <div className="flex cursor-pointer items-center gap-2">
flow={currentFlow} {currentFlow && currentFlow.data && (
open={openCodeModal} <ApiModal
setOpen={setOpenCodeModal} flow={currentFlow}
> open={openCodeModal}
<div setOpen={setOpenCodeModal}
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", <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"} <ForwardedIconComponent
/> name="Code2"
API className={"h-5 w-5"}
</div> />
</ApiModal> API
)} </div>
</div> </ApiModal>
<div> )}
<Separator orientation="vertical" /> </div>
</div> <div>
<Separator orientation="vertical" />
</div>
</>
)}
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<div <div
className={`side-bar-button ${ className={`side-bar-button ${

View file

@ -11,6 +11,7 @@ import {
} from "../../constants/constants"; } from "../../constants/constants";
import { AuthContext } from "../../contexts/authContext"; import { AuthContext } from "../../contexts/authContext";
import FeatureFlags from "@/../feature-config.json";
import { useLogout } from "@/controllers/API/queries/auth"; import { useLogout } from "@/controllers/API/queries/auth";
import useAuthStore from "@/stores/authStore"; import useAuthStore from "@/stores/authStore";
import useAlertStore from "../../stores/alertStore"; import useAlertStore from "../../stores/alertStore";
@ -182,18 +183,26 @@ export default function Header(): JSX.Element {
</a> </a>
<Separator orientation="vertical" /> <Separator orientation="vertical" />
<button {FeatureFlags.ENABLE_DARK_MODE && (
className="extra-side-bar-save-disable" <button
onClick={() => { className="extra-side-bar-save-disable"
setDark(!dark); onClick={() => {
}} setDark(!dark);
> }}
{dark ? ( >
<IconComponent name="SunIcon" className="side-bar-button-size" /> {dark ? (
) : ( <IconComponent
<IconComponent name="MoonIcon" className="side-bar-button-size" /> name="SunIcon"
)} className="side-bar-button-size"
</button> />
) : (
<IconComponent
name="MoonIcon"
className="side-bar-button-size"
/>
)}
</button>
)}
<AlertDropdown> <AlertDropdown>
<div className="extra-side-bar-save-disable relative"> <div className="extra-side-bar-save-disable relative">
{notificationCenter && ( {notificationCenter && (
@ -216,10 +225,17 @@ export default function Header(): JSX.Element {
data-testid="user-profile-settings" data-testid="user-profile-settings"
className="shrink-0" className="shrink-0"
> >
<img {FeatureFlags.ENABLE_PROFILE_ICONS ? (
src={profileImageUrl} <img
className="h-7 w-7 shrink-0 focus-visible:outline-0" src={profileImageUrl}
/> className="h-7 w-7 shrink-0 focus-visible:outline-0"
/>
) : (
<IconComponent
name="Settings"
className="side-bar-button-size"
/>
)}
</Button> </Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent className="mr-1 mt-1 min-w-40"> <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 { useEffect } from "react";
import { Outlet } from "react-router-dom"; import { Outlet } from "react-router-dom";
import ForwardedIconComponent from "../../components/genericIconComponent"; import ForwardedIconComponent from "../../components/genericIconComponent";
@ -10,12 +13,26 @@ export default function SettingsPage(): JSX.Element {
const setCurrentFlowId = useFlowsManagerStore( const setCurrentFlowId = useFlowsManagerStore(
(state) => state.setCurrentFlowId, (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(() => { useEffect(() => {
setCurrentFlowId(""); setCurrentFlowId("");
}, [pathname]); }, [pathname]);
const sidebarNavItems = [ const sidebarNavItems: {
{ href?: string;
title: string;
icon: React.ReactNode;
}[] = [];
if (showGeneralSettings) {
sidebarNavItems.push({
title: "General", title: "General",
href: "/settings/general", href: "/settings/general",
icon: ( icon: (
@ -24,7 +41,10 @@ export default function SettingsPage(): JSX.Element {
className="w-4 flex-shrink-0 justify-start stroke-[1.5]" className="w-4 flex-shrink-0 justify-start stroke-[1.5]"
/> />
), ),
}, });
}
sidebarNavItems.push(
{ {
title: "Global Variables", title: "Global Variables",
href: "/settings/global-variables", href: "/settings/global-variables",
@ -65,7 +85,7 @@ export default function SettingsPage(): JSX.Element {
/> />
), ),
}, },
]; );
return ( return (
<PageLayout <PageLayout
title="Settings" title="Settings"

View file

@ -1,3 +1,4 @@
import FeatureFlags from "@/../feature-config.json";
import { usePostAddApiKey } from "@/controllers/API/queries/api-keys"; import { usePostAddApiKey } from "@/controllers/API/queries/api-keys";
import { useGetProfilePicturesQuery } from "@/controllers/API/queries/files"; import { useGetProfilePicturesQuery } from "@/controllers/API/queries/files";
import useAuthStore from "@/stores/authStore"; import useAuthStore from "@/stores/authStore";
@ -101,13 +102,15 @@ export const GeneralPage = () => {
<GeneralPageHeaderComponent /> <GeneralPageHeaderComponent />
<div className="grid gap-6"> <div className="grid gap-6">
<ProfilePictureFormComponent {FeatureFlags.ENABLE_PROFILE_ICONS && (
profilePicture={profilePicture} <ProfilePictureFormComponent
handleInput={handleInput} profilePicture={profilePicture}
handlePatchProfilePicture={handlePatchProfilePicture} handleInput={handleInput}
handleGetProfilePictures={handleGetProfilePictures} handlePatchProfilePicture={handlePatchProfilePicture}
userData={userData} handleGetProfilePictures={handleGetProfilePictures}
/> userData={userData}
/>
)}
{!autoLogin && ( {!autoLogin && (
<PasswordFormComponent <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 { Suspense, lazy } from "react";
import { Navigate, Route, Routes } from "react-router-dom"; import { Navigate, Route, Routes } from "react-router-dom";
import { ProtectedAdminRoute } from "./components/authAdminGuard"; import { ProtectedAdminRoute } from "./components/authAdminGuard";
@ -36,6 +39,13 @@ const StorePage = lazy(() => import("./pages/StorePage"));
const ViewPage = lazy(() => import("./pages/ViewPage")); const ViewPage = lazy(() => import("./pages/ViewPage"));
const Router = () => { 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 ( return (
<Suspense <Suspense
fallback={ fallback={
@ -77,10 +87,20 @@ const Router = () => {
</ProtectedRoute> </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="global-variables" element={<GlobalVariablesPage />} />
<Route path="api-keys" element={<ApiKeysPage />} /> <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="shortcuts" element={<ShortcutsPage />} />
<Route path="messages" element={<MessagesPage />} /> <Route path="messages" element={<MessagesPage />} />
</Route> </Route>

View file

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