feat: resolve component update notification state persistence after dismissal (#6032)
* ✨ (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 <otavio2204@gmail.com>
This commit is contained in:
parent
369b8ec855
commit
6b17240d75
8 changed files with 78 additions and 5 deletions
|
|
@ -11,6 +11,7 @@ import { track } from "@/customization/utils/analytics";
|
||||||
import { useDarkStore } from "@/stores/darkStore";
|
import { useDarkStore } from "@/stores/darkStore";
|
||||||
import useFlowStore from "@/stores/flowStore";
|
import useFlowStore from "@/stores/flowStore";
|
||||||
import { useShortcutsStore } from "@/stores/shortcuts";
|
import { useShortcutsStore } from "@/stores/shortcuts";
|
||||||
|
import { useUtilityStore } from "@/stores/utilityStore";
|
||||||
import { VertexBuildTypeAPI } from "@/types/api";
|
import { VertexBuildTypeAPI } from "@/types/api";
|
||||||
import { NodeDataType } from "@/types/flow";
|
import { NodeDataType } from "@/types/flow";
|
||||||
import { findLastNode } from "@/utils/reactflowUtils";
|
import { findLastNode } from "@/utils/reactflowUtils";
|
||||||
|
|
@ -34,6 +35,7 @@ export default function NodeStatus({
|
||||||
isOutdated,
|
isOutdated,
|
||||||
isUserEdited,
|
isUserEdited,
|
||||||
getValidationStatus,
|
getValidationStatus,
|
||||||
|
handleUpdateComponent,
|
||||||
}: {
|
}: {
|
||||||
nodeId: string;
|
nodeId: string;
|
||||||
display_name: string;
|
display_name: string;
|
||||||
|
|
@ -46,6 +48,7 @@ export default function NodeStatus({
|
||||||
isOutdated: boolean;
|
isOutdated: boolean;
|
||||||
isUserEdited: boolean;
|
isUserEdited: boolean;
|
||||||
getValidationStatus: (data) => VertexBuildTypeAPI | null;
|
getValidationStatus: (data) => VertexBuildTypeAPI | null;
|
||||||
|
handleUpdateComponent: () => void;
|
||||||
}) {
|
}) {
|
||||||
const nodeId_ = data.node?.flow?.data
|
const nodeId_ = data.node?.flow?.data
|
||||||
? (findLastNode(data.node?.flow.data!)?.id ?? nodeId)
|
? (findLastNode(data.node?.flow.data!)?.id ?? nodeId)
|
||||||
|
|
@ -84,6 +87,8 @@ export default function NodeStatus({
|
||||||
getValidationStatus,
|
getValidationStatus,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const dismissAll = useUtilityStore((state) => state.dismissAll);
|
||||||
|
|
||||||
const getBaseBorderClass = (selected) => {
|
const getBaseBorderClass = (selected) => {
|
||||||
let className =
|
let className =
|
||||||
selected && !isBuilding
|
selected && !isBuilding
|
||||||
|
|
@ -91,7 +96,9 @@ export default function NodeStatus({
|
||||||
: "border ring-[0.5px] hover:shadow-node ring-border";
|
: "border ring-[0.5px] hover:shadow-node ring-border";
|
||||||
let frozenClass = selected ? "border-ring-frozen" : "border-frozen";
|
let frozenClass = selected ? "border-ring-frozen" : "border-frozen";
|
||||||
let updateClass =
|
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);
|
return cn(frozen ? frozenClass : className, updateClass);
|
||||||
};
|
};
|
||||||
const getNodeBorderClassName = (
|
const getNodeBorderClassName = (
|
||||||
|
|
@ -122,6 +129,7 @@ export default function NodeStatus({
|
||||||
isOutdated,
|
isOutdated,
|
||||||
isUserEdited,
|
isUserEdited,
|
||||||
frozen,
|
frozen,
|
||||||
|
dismissAll,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -248,6 +256,36 @@ export default function NodeStatus({
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</ShadTooltip>
|
</ShadTooltip>
|
||||||
|
{dismissAll && isOutdated && !isUserEdited && (
|
||||||
|
<ShadTooltip content="Update component">
|
||||||
|
<div
|
||||||
|
className="button-run-bg hit-area-icon ml-1 bg-warning hover:bg-warning/80"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
handleUpdateComponent();
|
||||||
|
e.stopPropagation();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{showNode && (
|
||||||
|
<Button
|
||||||
|
unstyled
|
||||||
|
type="button"
|
||||||
|
onClick={(e) => e.preventDefault()}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
data-testid={`button_update_` + display_name.toLowerCase()}
|
||||||
|
>
|
||||||
|
<IconComponent
|
||||||
|
name={"AlertTriangle"}
|
||||||
|
strokeWidth={ICON_STROKE_WIDTH}
|
||||||
|
className="icon-size text-black"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</ShadTooltip>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ import { NodeDataType } from "../../types/flow";
|
||||||
import { checkHasToolMode } from "../../utils/reactflowUtils";
|
import { checkHasToolMode } from "../../utils/reactflowUtils";
|
||||||
import { classNames, cn } from "../../utils/utils";
|
import { classNames, cn } from "../../utils/utils";
|
||||||
|
|
||||||
|
import { useUtilityStore } from "@/stores/utilityStore";
|
||||||
import { processNodeAdvancedFields } from "../helpers/process-node-advanced-fields";
|
import { processNodeAdvancedFields } from "../helpers/process-node-advanced-fields";
|
||||||
import useCheckCodeValidity from "../hooks/use-check-code-validity";
|
import useCheckCodeValidity from "../hooks/use-check-code-validity";
|
||||||
import useUpdateNodeCode from "../hooks/use-update-node-code";
|
import useUpdateNodeCode from "../hooks/use-update-node-code";
|
||||||
|
|
@ -87,6 +88,7 @@ function GenericNode({
|
||||||
const edges = useFlowStore((state) => state.edges);
|
const edges = useFlowStore((state) => state.edges);
|
||||||
const shortcuts = useShortcutsStore((state) => state.shortcuts);
|
const shortcuts = useShortcutsStore((state) => state.shortcuts);
|
||||||
const buildStatus = useBuildStatus(data, data.id);
|
const buildStatus = useBuildStatus(data, data.id);
|
||||||
|
const dismissAll = useUtilityStore((state) => state.dismissAll);
|
||||||
|
|
||||||
const showNode = data.showNode ?? true;
|
const showNode = data.showNode ?? true;
|
||||||
|
|
||||||
|
|
@ -322,6 +324,7 @@ function GenericNode({
|
||||||
isOutdated={isOutdated}
|
isOutdated={isOutdated}
|
||||||
isUserEdited={isUserEdited}
|
isUserEdited={isUserEdited}
|
||||||
getValidationStatus={getValidationStatus}
|
getValidationStatus={getValidationStatus}
|
||||||
|
handleUpdateComponent={handleUpdateCode}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}, [
|
}, [
|
||||||
|
|
@ -332,6 +335,8 @@ function GenericNode({
|
||||||
isOutdated,
|
isOutdated,
|
||||||
isUserEdited,
|
isUserEdited,
|
||||||
getValidationStatus,
|
getValidationStatus,
|
||||||
|
dismissAll,
|
||||||
|
handleUpdateCode,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const renderDescription = useCallback(() => {
|
const renderDescription = useCallback(() => {
|
||||||
|
|
@ -359,7 +364,11 @@ function GenericNode({
|
||||||
}, [data, types, isToolMode, showNode, shownOutputs, showHiddenOutputs]);
|
}, [data, types, isToolMode, showNode, shownOutputs, showHiddenOutputs]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cn(isOutdated && !isUserEdited ? "relative -mt-10" : "")}>
|
<div
|
||||||
|
className={cn(
|
||||||
|
isOutdated && !isUserEdited && !dismissAll ? "relative -mt-10" : "",
|
||||||
|
)}
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
borderColor,
|
borderColor,
|
||||||
|
|
@ -369,7 +378,7 @@ function GenericNode({
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{memoizedNodeToolbarComponent}
|
{memoizedNodeToolbarComponent}
|
||||||
{isOutdated && !isUserEdited && (
|
{isOutdated && !isUserEdited && !dismissAll && (
|
||||||
<div className="flex h-10 w-full items-center gap-4 rounded-t-[0.69rem] bg-warning p-2 px-4 text-warning-foreground">
|
<div className="flex h-10 w-full items-center gap-4 rounded-t-[0.69rem] bg-warning p-2 px-4 text-warning-foreground">
|
||||||
<ForwardedIconComponent
|
<ForwardedIconComponent
|
||||||
name="AlertTriangle"
|
name="AlertTriangle"
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ import {
|
||||||
} from "@/customization/feature-flags";
|
} from "@/customization/feature-flags";
|
||||||
import { useCustomNavigate } from "@/customization/hooks/use-custom-navigate";
|
import { useCustomNavigate } from "@/customization/hooks/use-custom-navigate";
|
||||||
import useTheme from "@/customization/hooks/use-custom-theme";
|
import useTheme from "@/customization/hooks/use-custom-theme";
|
||||||
|
import { useResetDismissUpdateAll } from "@/hooks/use-reset-dismiss-update-all";
|
||||||
import useAlertStore from "@/stores/alertStore";
|
import useAlertStore from "@/stores/alertStore";
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { AccountMenu } from "./components/AccountMenu";
|
import { AccountMenu } from "./components/AccountMenu";
|
||||||
|
|
@ -46,6 +47,8 @@ export default function AppHeader(): JSX.Element {
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useResetDismissUpdateAll();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-[62px] w-full items-center justify-between gap-2 border-b px-5 py-2.5 dark:bg-background">
|
<div className="flex h-[62px] w-full items-center justify-between gap-2 border-b px-5 py-2.5 dark:bg-background">
|
||||||
{/* Left Section */}
|
{/* Left Section */}
|
||||||
|
|
|
||||||
14
src/frontend/src/hooks/use-reset-dismiss-update-all.ts
Normal file
14
src/frontend/src/hooks/use-reset-dismiss-update-all.ts
Normal file
|
|
@ -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]);
|
||||||
|
};
|
||||||
|
|
@ -9,6 +9,7 @@ import useAlertStore from "@/stores/alertStore";
|
||||||
import useFlowsManagerStore from "@/stores/flowsManagerStore";
|
import useFlowsManagerStore from "@/stores/flowsManagerStore";
|
||||||
import useFlowStore from "@/stores/flowStore";
|
import useFlowStore from "@/stores/flowStore";
|
||||||
import { useTypesStore } from "@/stores/typesStore";
|
import { useTypesStore } from "@/stores/typesStore";
|
||||||
|
import { useUtilityStore } from "@/stores/utilityStore";
|
||||||
import { cn } from "@/utils/utils";
|
import { cn } from "@/utils/utils";
|
||||||
import { useUpdateNodeInternals } from "@xyflow/react";
|
import { useUpdateNodeInternals } from "@xyflow/react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
@ -27,6 +28,8 @@ export default function UpdateAllComponents() {
|
||||||
|
|
||||||
const [dismissed, setDismissed] = useState(false);
|
const [dismissed, setDismissed] = useState(false);
|
||||||
|
|
||||||
|
const setDismissAll = useUtilityStore((state) => state.setDismissAll);
|
||||||
|
|
||||||
const handleUpdateAllComponents = () => {
|
const handleUpdateAllComponents = () => {
|
||||||
setLoadingUpdate(true);
|
setLoadingUpdate(true);
|
||||||
takeSnapshot();
|
takeSnapshot();
|
||||||
|
|
@ -124,8 +127,10 @@ export default function UpdateAllComponents() {
|
||||||
variant="link"
|
variant="link"
|
||||||
size="icon"
|
size="icon"
|
||||||
className="shrink-0 text-sm text-warning-foreground"
|
className="shrink-0 text-sm text-warning-foreground"
|
||||||
onClick={() => {
|
onClick={(e) => {
|
||||||
setDismissed(true);
|
setDismissed(true);
|
||||||
|
setDismissAll(true);
|
||||||
|
e.stopPropagation();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Dismiss
|
Dismiss
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@ import { UtilityStoreType } from "@/types/zustand/utility";
|
||||||
import { create } from "zustand";
|
import { create } from "zustand";
|
||||||
|
|
||||||
export const useUtilityStore = create<UtilityStoreType>((set, get) => ({
|
export const useUtilityStore = create<UtilityStoreType>((set, get) => ({
|
||||||
|
dismissAll: false,
|
||||||
|
setDismissAll: (dismissAll: boolean) => set({ dismissAll }),
|
||||||
chatValueStore: "",
|
chatValueStore: "",
|
||||||
setChatValueStore: (value: string) => set({ chatValueStore: value }),
|
setChatValueStore: (value: string) => set({ chatValueStore: value }),
|
||||||
selectedItems: [],
|
selectedItems: [],
|
||||||
|
|
|
||||||
|
|
@ -307,7 +307,7 @@
|
||||||
|
|
||||||
--datatype-fuchsia: 291.1 93.1% 82.9%;
|
--datatype-fuchsia: 291.1 93.1% 82.9%;
|
||||||
--datatype-fuchsia-foreground: 293.4 69.5% 48.8%;
|
--datatype-fuchsia-foreground: 293.4 69.5% 48.8%;
|
||||||
|
|
||||||
--datatype-purple: 268.6 100% 91.8%;
|
--datatype-purple: 268.6 100% 91.8%;
|
||||||
--datatype-purple-foreground: 272.1 71.7% 47.1%;
|
--datatype-purple-foreground: 272.1 71.7% 47.1%;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,4 +17,6 @@ export type UtilityStoreType = {
|
||||||
setFeatureFlags: (featureFlags: Record<string, any>) => void;
|
setFeatureFlags: (featureFlags: Record<string, any>) => void;
|
||||||
chatValueStore: string;
|
chatValueStore: string;
|
||||||
setChatValueStore: (value: string) => void;
|
setChatValueStore: (value: string) => void;
|
||||||
|
dismissAll: boolean;
|
||||||
|
setDismissAll: (dismissAll: boolean) => void;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue