diff --git a/src/components/shared/ReactFlow/FlowCanvas/TaskNode/AnnotationsEditor/AnnotationsSection.tsx b/src/components/shared/ReactFlow/FlowCanvas/TaskNode/AnnotationsEditor/AnnotationsSection.tsx index 5650b964f..9c132ff39 100644 --- a/src/components/shared/ReactFlow/FlowCanvas/TaskNode/AnnotationsEditor/AnnotationsSection.tsx +++ b/src/components/shared/ReactFlow/FlowCanvas/TaskNode/AnnotationsEditor/AnnotationsSection.tsx @@ -4,6 +4,7 @@ import { BlockStack } from "@/components/ui/layout"; import { Separator } from "@/components/ui/separator"; import useToastNotification from "@/hooks/useToastNotification"; import type { AnnotationConfig, Annotations } from "@/types/annotations"; +import { HIDDEN_ANNOTATIONS } from "@/utils/annotations"; import type { TaskSpec } from "@/utils/componentSpec"; import { AnnotationsEditor } from "./AnnotationsEditor"; @@ -61,7 +62,7 @@ export const AnnotationsSection = ({ return Object.entries(annotations).reduce( (acc, [key, value]) => { - if (!managedAnnotationKeys.has(key)) { + if (!managedAnnotationKeys.has(key) && !HIDDEN_ANNOTATIONS.has(key)) { acc[key] = value; } return acc; diff --git a/src/components/shared/ReactFlow/FlowCanvas/TaskNode/TaskNodeCard/TaskNodeCard.tsx b/src/components/shared/ReactFlow/FlowCanvas/TaskNode/TaskNodeCard/TaskNodeCard.tsx index 7c3a8fb0b..4674f302e 100644 --- a/src/components/shared/ReactFlow/FlowCanvas/TaskNode/TaskNodeCard/TaskNodeCard.tsx +++ b/src/components/shared/ReactFlow/FlowCanvas/TaskNode/TaskNodeCard/TaskNodeCard.tsx @@ -51,20 +51,17 @@ const TaskNodeCard = () => { const executionState = executionData?.state; const nodeRef = useRef(null); - const contentRef = useRef(null); const [updateOverlayDialogOpen, setUpdateOverlayDialogOpen] = useState< UpdateOverlayMessage["data"] | undefined >(); const [highlightedState, setHighlighted] = useState(false); - const [scrollHeight, setScrollHeight] = useState(0); - const [condensed, setCondensed] = useState(false); const [expandedInputs, setExpandedInputs] = useState(false); const [expandedOutputs, setExpandedOutputs] = useState(false); const { name, displayName, state, nodeId, taskSpec, taskId } = taskNode; - const { dimensions, selected, highlighted, readOnly } = state; + const { dimensions, selected, highlighted, readOnly, isCollapsed } = state; const { isConnectedToSelectedEdge } = useEdgeSelectionHighlight(nodeId); @@ -160,23 +157,6 @@ const TaskNodeCard = () => { navigate, ]); - useEffect(() => { - if (nodeRef.current) { - setScrollHeight(nodeRef.current.scrollHeight); - } - }, []); - - useEffect(() => { - if (!dimensions.h) { - setCondensed(false); - return; - } - - if (contentRef.current && scrollHeight > 0) { - setCondensed(scrollHeight > dimensions.h); - } - }, [scrollHeight, dimensions.h]); - useEffect(() => { if (selected) { setContent(taskConfigMarkup); @@ -211,7 +191,7 @@ const TaskNodeCard = () => { return ( { )} style={{ width: dimensions.w + "px", - height: condensed || !dimensions.h ? "auto" : dimensions.h + "px", + height: isCollapsed || !dimensions.h ? "auto" : dimensions.h + "px", transition: "height 0.2s", }} ref={nodeRef} @@ -279,21 +259,22 @@ const TaskNodeCard = () => {
diff --git a/src/components/shared/ReactFlow/FlowCanvas/TaskNode/TaskNodeCard/TaskNodeInputs.test.tsx b/src/components/shared/ReactFlow/FlowCanvas/TaskNode/TaskNodeCard/TaskNodeInputs.test.tsx index 804e369ea..d5613a417 100644 --- a/src/components/shared/ReactFlow/FlowCanvas/TaskNode/TaskNodeCard/TaskNodeInputs.test.tsx +++ b/src/components/shared/ReactFlow/FlowCanvas/TaskNode/TaskNodeCard/TaskNodeInputs.test.tsx @@ -80,7 +80,7 @@ describe("", () => { useMockedUseTaskNode(inputs, taskSpec); - render(, { + render(, { wrapper: TestWrapper, }); @@ -108,7 +108,7 @@ describe("", () => { ); }); - it("should show hidden invalid arguments warning in condensed mode", () => { + it("should show hidden invalid arguments warning in collapsed mode", () => { const inputs = [ createMockInput("visibleInput", "String", false), createMockInput("hiddenInvalidInput1", "String", false), @@ -123,7 +123,7 @@ describe("", () => { useMockedUseTaskNode(inputs, taskSpec); - render(, { + render(, { wrapper: TestWrapper, }); @@ -147,7 +147,7 @@ describe("", () => { useMockedUseTaskNode(inputs, taskSpec); - render(, { + render(, { wrapper: TestWrapper, }); @@ -170,7 +170,7 @@ describe("", () => { useMockedUseTaskNode(inputs, taskSpec); - render(, { + render(, { wrapper: TestWrapper, }); @@ -183,7 +183,7 @@ describe("", () => { it("should handle edge case with no inputs", () => { useMockedUseTaskNode([], createMockTaskSpec()); - render(, { + render(, { wrapper: TestWrapper, }); @@ -214,7 +214,7 @@ describe("", () => { useMockedUseTaskNode(inputs, taskSpec); - render(, { + render(, { wrapper: TestWrapper, }); diff --git a/src/components/shared/ReactFlow/FlowCanvas/TaskNode/TaskNodeCard/TaskNodeInputs.tsx b/src/components/shared/ReactFlow/FlowCanvas/TaskNode/TaskNodeCard/TaskNodeInputs.tsx index 67c573afb..8f3144b05 100644 --- a/src/components/shared/ReactFlow/FlowCanvas/TaskNode/TaskNodeCard/TaskNodeInputs.tsx +++ b/src/components/shared/ReactFlow/FlowCanvas/TaskNode/TaskNodeCard/TaskNodeInputs.tsx @@ -21,13 +21,13 @@ import { InputHandle } from "./Handles"; import { getDisplayValue } from "./handleUtils"; interface TaskNodeInputsProps { - condensed: boolean; + collapsed: boolean; expanded: boolean; onBackgroundClick?: () => void; } export function TaskNodeInputs({ - condensed, + collapsed, expanded, onBackgroundClick, }: TaskNodeInputsProps) { @@ -76,12 +76,12 @@ export function TaskNodeInputs({ const handleBackgroundClick = useCallback( (e: MouseEvent) => { - if (condensed && onBackgroundClick) { + if (collapsed && onBackgroundClick) { e.stopPropagation(); onBackgroundClick(); } }, - [condensed, onBackgroundClick], + [collapsed, onBackgroundClick], ); const handleSelectionChange = useCallback( @@ -176,7 +176,7 @@ export function TaskNodeInputs({ const hiddenInputs = inputs.length - connectedInputs.length; if (hiddenInputs < 1) { - condensed = false; + collapsed = false; } const hiddenInvalidArguments = invalidArguments.filter( @@ -188,11 +188,11 @@ export function TaskNodeInputs({
- {condensed && !expanded ? ( + {collapsed && !expanded ? ( <> {connectedInputs.map((input, i) => ( ))} - {condensed && ( + {collapsed && ( (Click to collapse) diff --git a/src/components/shared/ReactFlow/FlowCanvas/TaskNode/TaskNodeCard/TaskNodeOutputs.tsx b/src/components/shared/ReactFlow/FlowCanvas/TaskNode/TaskNodeCard/TaskNodeOutputs.tsx index e3771554b..bafda1a0f 100644 --- a/src/components/shared/ReactFlow/FlowCanvas/TaskNode/TaskNodeCard/TaskNodeOutputs.tsx +++ b/src/components/shared/ReactFlow/FlowCanvas/TaskNode/TaskNodeCard/TaskNodeOutputs.tsx @@ -13,13 +13,13 @@ import { checkArtifactMatchesSearchFilters } from "@/utils/searchUtils"; import { OutputHandle } from "./Handles"; type TaskNodeOutputsProps = { - condensed: boolean; + collapsed: boolean; expanded: boolean; onBackgroundClick?: () => void; }; export function TaskNodeOutputs({ - condensed, + collapsed, expanded, onBackgroundClick, }: TaskNodeOutputsProps) { @@ -65,12 +65,12 @@ export function TaskNodeOutputs({ const handleBackgroundClick = useCallback( (e: MouseEvent) => { - if (condensed && onBackgroundClick) { + if (collapsed && onBackgroundClick) { e.stopPropagation(); onBackgroundClick(); } }, - [condensed, onBackgroundClick], + [collapsed, onBackgroundClick], ); const handleSelectionChange = useCallback( @@ -161,18 +161,18 @@ export function TaskNodeOutputs({ const hiddenOutputs = outputs.length - outputsWithTaskInput.length; if (hiddenOutputs < 1) { - condensed = false; + collapsed = false; } return (
- {condensed && !expanded ? ( + {collapsed && !expanded ? ( outputsWithTaskInput.map((output, i) => ( ))} - {condensed && ( + {collapsed && ( (Click to collapse) diff --git a/src/components/shared/ReactFlow/FlowCanvas/TaskNode/TaskOverview/TaskConfiguration.tsx b/src/components/shared/ReactFlow/FlowCanvas/TaskNode/TaskOverview/TaskConfiguration.tsx index d6e8493bc..e089d2eee 100644 --- a/src/components/shared/ReactFlow/FlowCanvas/TaskNode/TaskOverview/TaskConfiguration.tsx +++ b/src/components/shared/ReactFlow/FlowCanvas/TaskNode/TaskOverview/TaskConfiguration.tsx @@ -41,6 +41,15 @@ const TaskConfiguration = ({ taskNode }: TaskConfigurationProps) => { onCheckedChange={handleDisableCacheChange} /> + + + Collapse node + + + ); }; diff --git a/src/providers/TaskNodeProvider.tsx b/src/providers/TaskNodeProvider.tsx index bbdbddfb5..0002adf60 100644 --- a/src/providers/TaskNodeProvider.tsx +++ b/src/providers/TaskNodeProvider.tsx @@ -11,6 +11,12 @@ import { type TaskNodeData, type TaskNodeDimensions, } from "@/types/taskNode"; +import { + EDITOR_COLLAPSED_ANNOTATION, + getAnnotationValue, + removeAnnotation, + setAnnotation, +} from "@/utils/annotations"; import { type ArgumentType, type ComponentReference, @@ -39,6 +45,7 @@ type TaskNodeState = Readonly<{ connectable: boolean; status?: string; isCustomComponent: boolean; + isCollapsed: boolean; dimensions: TaskNodeDimensions; }>; @@ -46,6 +53,7 @@ type TaskNodeCallbacks = { setArguments: (args: Record) => void; setAnnotations: (annotations: Annotations) => void; setCacheStaleness: (cacheStaleness: string | undefined) => void; + setCollapsed: (collapsed: boolean) => void; onDelete?: () => void; onDuplicate?: () => void; onUpgrade?: () => void; @@ -139,6 +147,25 @@ export const TaskNodeProvider = ({ [setCacheStaleness, notify], ); + const isCollapsed = + getAnnotationValue(taskSpec?.annotations, EDITOR_COLLAPSED_ANNOTATION) === + "true"; + + const handleSetCollapsed = useCallback( + (collapsed: boolean) => { + const updatedAnnotations = collapsed + ? setAnnotation( + taskSpec?.annotations, + EDITOR_COLLAPSED_ANNOTATION, + "true", + ) + : removeAnnotation(taskSpec?.annotations, EDITOR_COLLAPSED_ANNOTATION); + + setAnnotations(updatedAnnotations as Annotations); + }, + [taskSpec?.annotations, setAnnotations], + ); + const handleDeleteTaskNode = useCallback(() => { onDelete(); }, [onDelete]); @@ -173,6 +200,7 @@ export const TaskNodeProvider = ({ status: data.isGhost ? undefined : status, disabled: data.isGhost ?? false, isCustomComponent, + isCollapsed, dimensions, }), [ @@ -182,6 +210,7 @@ export const TaskNodeProvider = ({ data.isGhost, status, isCustomComponent, + isCollapsed, dimensions, ], ); @@ -191,6 +220,7 @@ export const TaskNodeProvider = ({ setArguments: handleSetArguments, setAnnotations: handleSetAnnotations, setCacheStaleness: handleSetCacheStaleness, + setCollapsed: handleSetCollapsed, onDelete: handleDeleteTaskNode, onDuplicate: handleDuplicateTaskNode, onUpgrade: handleUpgradeTaskNode, @@ -198,6 +228,7 @@ export const TaskNodeProvider = ({ [ handleSetArguments, handleSetAnnotations, + handleSetCollapsed, handleDeleteTaskNode, handleDuplicateTaskNode, handleUpgradeTaskNode, diff --git a/src/utils/annotations.ts b/src/utils/annotations.ts index 049777d46..c03afad2f 100644 --- a/src/utils/annotations.ts +++ b/src/utils/annotations.ts @@ -11,6 +11,7 @@ export const PIPELINE_RUN_NOTES_ANNOTATION = "notes"; export const PIPELINE_CANONICAL_NAME_ANNOTATION = "canonical-pipeline-name"; export const RUN_NAME_TEMPLATE_ANNOTATION = "run-name-template"; export const EDITOR_POSITION_ANNOTATION = "editor.position"; +export const EDITOR_COLLAPSED_ANNOTATION = "editor.collapsed"; export const FLEX_NODES_ANNOTATION = "flex-nodes"; export const DEFAULT_COMMON_ANNOTATIONS: AnnotationConfig[] = [ @@ -27,6 +28,10 @@ export const DEFAULT_COMMON_ANNOTATIONS: AnnotationConfig[] = [ }, ]; +export const HIDDEN_ANNOTATIONS = new Set([ + EDITOR_COLLAPSED_ANNOTATION, +]); + type Annotations = | { [k: string]: unknown; @@ -69,7 +74,7 @@ export function getAnnotationValue( * @param value - The value to set * @returns */ -function setAnnotation( +export function setAnnotation( annotations: Annotations, key: string, value: string | undefined, @@ -207,3 +212,21 @@ export function ensureAnnotations( }, }; } + +/* + * Removes an annotation from the annotations object. + * @param annotations - The annotations object + * @param key - The key of the annotation to remove + * @returns Updated annotations object with the specified annotation removed + */ +export function removeAnnotation( + annotations: Annotations, + key: string, +): Annotations { + if (!annotations || !hasAnnotation(annotations, key)) { + return annotations; + } + + const { [key]: _, ...rest } = annotations; + return rest; +}