From 6b17240d75911b263e812639265114d77035c8e2 Mon Sep 17 00:00:00 2001 From: Cristhian Zanforlin Lousa Date: Wed, 5 Feb 2025 09:49:58 -0300 Subject: [PATCH] feat: resolve component update notification state persistence after dismissal (#6032) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ✨ (NodeStatus): Add support for utility store in NodeStatus component to manage dismissAll state 🔧 (GenericNode): Import and use utility store in GenericNode component to access dismissAll state 🔧 (use-reset-dismiss-update-all): Create hook to reset dismissAll state in utility store 🔧 (UpdateAllComponents): Import and use utility store in UpdateAllComponents component to access dismissAll state 🔧 (header): Import useResetDismissUpdateAll hook in header component to reset dismissAll state 🔧 (list): Import useResetDismissUpdateAll hook in list component to reset dismissAll state 🔧 (utilityStore): Add dismissAll state and setDismissAll method to utility store 🔧 (utility/index): Add dismissAll state and setDismissAll method to UtilityStoreType * ✨ (NodeStatus/index.tsx): add dismissAll prop to NodeStatus component to handle dismissing all notifications ✨ (GenericNode/index.tsx): add dismissAll prop to GenericNode component to handle dismissing all notifications ✨ (UpdateAllComponents/index.tsx): add e.stopPropagation() to onClick event handler to prevent event bubbling 🔧 (header/index.tsx): remove unused import useResetDismissUpdateAll from header component * ✨ (NodeStatus/index.tsx): Add functionality to handle updating a component when it is outdated and not user-edited 🔧 (GenericNode/index.tsx): Update handleUpdateComponent function to handleUpdateCode for consistency 🔧 (appHeaderComponent/index.tsx): Add useResetDismissUpdateAll hook to reset dismiss update all functionality 🔧 (use-reset-dismiss-update-all.ts): Update useResetDismissUpdateAll hook to only reset dismiss update all in flow location path 🔧 (list/index.tsx): Remove useResetDismissUpdateAll hook from ListComponent as it is no longer needed 🔧 (index.css): Remove extra whitespace in CSS file * 🔧 (GenericNode/index.tsx): improve conditional class logic to include dismissAll variable in className calculation --------- Co-authored-by: anovazzi1 --- .../components/NodeStatus/index.tsx | 40 ++++++++++++++++++- .../src/CustomNodes/GenericNode/index.tsx | 13 +++++- .../core/appHeaderComponent/index.tsx | 3 ++ .../src/hooks/use-reset-dismiss-update-all.ts | 14 +++++++ .../components/UpdateAllComponents/index.tsx | 7 +++- src/frontend/src/stores/utilityStore.ts | 2 + src/frontend/src/style/index.css | 2 +- .../src/types/zustand/utility/index.ts | 2 + 8 files changed, 78 insertions(+), 5 deletions(-) create mode 100644 src/frontend/src/hooks/use-reset-dismiss-update-all.ts diff --git a/src/frontend/src/CustomNodes/GenericNode/components/NodeStatus/index.tsx b/src/frontend/src/CustomNodes/GenericNode/components/NodeStatus/index.tsx index 34f47dde4..4b47856ae 100644 --- a/src/frontend/src/CustomNodes/GenericNode/components/NodeStatus/index.tsx +++ b/src/frontend/src/CustomNodes/GenericNode/components/NodeStatus/index.tsx @@ -11,6 +11,7 @@ import { track } from "@/customization/utils/analytics"; import { useDarkStore } from "@/stores/darkStore"; import useFlowStore from "@/stores/flowStore"; import { useShortcutsStore } from "@/stores/shortcuts"; +import { useUtilityStore } from "@/stores/utilityStore"; import { VertexBuildTypeAPI } from "@/types/api"; import { NodeDataType } from "@/types/flow"; import { findLastNode } from "@/utils/reactflowUtils"; @@ -34,6 +35,7 @@ export default function NodeStatus({ isOutdated, isUserEdited, getValidationStatus, + handleUpdateComponent, }: { nodeId: string; display_name: string; @@ -46,6 +48,7 @@ export default function NodeStatus({ isOutdated: boolean; isUserEdited: boolean; getValidationStatus: (data) => VertexBuildTypeAPI | null; + handleUpdateComponent: () => void; }) { const nodeId_ = data.node?.flow?.data ? (findLastNode(data.node?.flow.data!)?.id ?? nodeId) @@ -84,6 +87,8 @@ export default function NodeStatus({ getValidationStatus, ); + const dismissAll = useUtilityStore((state) => state.dismissAll); + const getBaseBorderClass = (selected) => { let className = selected && !isBuilding @@ -91,7 +96,9 @@ export default function NodeStatus({ : "border ring-[0.5px] hover:shadow-node ring-border"; let frozenClass = selected ? "border-ring-frozen" : "border-frozen"; let updateClass = - isOutdated && !isUserEdited ? "border-warning ring-2 ring-warning" : ""; + isOutdated && !isUserEdited && !dismissAll + ? "border-warning ring-2 ring-warning" + : ""; return cn(frozen ? frozenClass : className, updateClass); }; const getNodeBorderClassName = ( @@ -122,6 +129,7 @@ export default function NodeStatus({ isOutdated, isUserEdited, frozen, + dismissAll, ]); useEffect(() => { @@ -248,6 +256,36 @@ export default function NodeStatus({ )} + {dismissAll && isOutdated && !isUserEdited && ( + +
{ + e.stopPropagation(); + handleUpdateComponent(); + e.stopPropagation(); + }} + > + {showNode && ( + + )} +
+
+ )} ) : ( diff --git a/src/frontend/src/CustomNodes/GenericNode/index.tsx b/src/frontend/src/CustomNodes/GenericNode/index.tsx index 0e149f633..1d4be5b2e 100644 --- a/src/frontend/src/CustomNodes/GenericNode/index.tsx +++ b/src/frontend/src/CustomNodes/GenericNode/index.tsx @@ -20,6 +20,7 @@ import { NodeDataType } from "../../types/flow"; import { checkHasToolMode } from "../../utils/reactflowUtils"; import { classNames, cn } from "../../utils/utils"; +import { useUtilityStore } from "@/stores/utilityStore"; import { processNodeAdvancedFields } from "../helpers/process-node-advanced-fields"; import useCheckCodeValidity from "../hooks/use-check-code-validity"; import useUpdateNodeCode from "../hooks/use-update-node-code"; @@ -87,6 +88,7 @@ function GenericNode({ const edges = useFlowStore((state) => state.edges); const shortcuts = useShortcutsStore((state) => state.shortcuts); const buildStatus = useBuildStatus(data, data.id); + const dismissAll = useUtilityStore((state) => state.dismissAll); const showNode = data.showNode ?? true; @@ -322,6 +324,7 @@ function GenericNode({ isOutdated={isOutdated} isUserEdited={isUserEdited} getValidationStatus={getValidationStatus} + handleUpdateComponent={handleUpdateCode} /> ); }, [ @@ -332,6 +335,8 @@ function GenericNode({ isOutdated, isUserEdited, getValidationStatus, + dismissAll, + handleUpdateCode, ]); const renderDescription = useCallback(() => { @@ -359,7 +364,11 @@ function GenericNode({ }, [data, types, isToolMode, showNode, shownOutputs, showHiddenOutputs]); return ( -
+
{memoizedNodeToolbarComponent} - {isOutdated && !isUserEdited && ( + {isOutdated && !isUserEdited && !dismissAll && (
{/* Left Section */} diff --git a/src/frontend/src/hooks/use-reset-dismiss-update-all.ts b/src/frontend/src/hooks/use-reset-dismiss-update-all.ts new file mode 100644 index 000000000..a49f3e773 --- /dev/null +++ b/src/frontend/src/hooks/use-reset-dismiss-update-all.ts @@ -0,0 +1,14 @@ +import { useEffect } from "react"; +import { useLocation } from "react-router-dom"; +import { useUtilityStore } from "../stores/utilityStore"; +export const useResetDismissUpdateAll = () => { + const location = useLocation(); + const flowLocationPath = location.pathname.includes("flow"); + const setDismissAll = useUtilityStore((state) => state.setDismissAll); + + useEffect(() => { + if (flowLocationPath) { + setDismissAll(false); + } + }, [location]); +}; diff --git a/src/frontend/src/pages/FlowPage/components/UpdateAllComponents/index.tsx b/src/frontend/src/pages/FlowPage/components/UpdateAllComponents/index.tsx index 3928367aa..9e06919c4 100644 --- a/src/frontend/src/pages/FlowPage/components/UpdateAllComponents/index.tsx +++ b/src/frontend/src/pages/FlowPage/components/UpdateAllComponents/index.tsx @@ -9,6 +9,7 @@ import useAlertStore from "@/stores/alertStore"; import useFlowsManagerStore from "@/stores/flowsManagerStore"; import useFlowStore from "@/stores/flowStore"; import { useTypesStore } from "@/stores/typesStore"; +import { useUtilityStore } from "@/stores/utilityStore"; import { cn } from "@/utils/utils"; import { useUpdateNodeInternals } from "@xyflow/react"; import { useState } from "react"; @@ -27,6 +28,8 @@ export default function UpdateAllComponents() { const [dismissed, setDismissed] = useState(false); + const setDismissAll = useUtilityStore((state) => state.setDismissAll); + const handleUpdateAllComponents = () => { setLoadingUpdate(true); takeSnapshot(); @@ -124,8 +127,10 @@ export default function UpdateAllComponents() { variant="link" size="icon" className="shrink-0 text-sm text-warning-foreground" - onClick={() => { + onClick={(e) => { setDismissed(true); + setDismissAll(true); + e.stopPropagation(); }} > Dismiss diff --git a/src/frontend/src/stores/utilityStore.ts b/src/frontend/src/stores/utilityStore.ts index fc302836a..4b4308597 100644 --- a/src/frontend/src/stores/utilityStore.ts +++ b/src/frontend/src/stores/utilityStore.ts @@ -3,6 +3,8 @@ import { UtilityStoreType } from "@/types/zustand/utility"; import { create } from "zustand"; export const useUtilityStore = create((set, get) => ({ + dismissAll: false, + setDismissAll: (dismissAll: boolean) => set({ dismissAll }), chatValueStore: "", setChatValueStore: (value: string) => set({ chatValueStore: value }), selectedItems: [], diff --git a/src/frontend/src/style/index.css b/src/frontend/src/style/index.css index 217337401..914d1f287 100644 --- a/src/frontend/src/style/index.css +++ b/src/frontend/src/style/index.css @@ -307,7 +307,7 @@ --datatype-fuchsia: 291.1 93.1% 82.9%; --datatype-fuchsia-foreground: 293.4 69.5% 48.8%; - + --datatype-purple: 268.6 100% 91.8%; --datatype-purple-foreground: 272.1 71.7% 47.1%; diff --git a/src/frontend/src/types/zustand/utility/index.ts b/src/frontend/src/types/zustand/utility/index.ts index 333f30f17..fda8add9d 100644 --- a/src/frontend/src/types/zustand/utility/index.ts +++ b/src/frontend/src/types/zustand/utility/index.ts @@ -17,4 +17,6 @@ export type UtilityStoreType = { setFeatureFlags: (featureFlags: Record) => void; chatValueStore: string; setChatValueStore: (value: string) => void; + dismissAll: boolean; + setDismissAll: (dismissAll: boolean) => void; };