diff --git a/packages/console/src/components/WorkflowGraph/WorkflowGraph.tsx b/packages/console/src/components/WorkflowGraph/WorkflowGraph.tsx index 6380fd2bc..b72b94acb 100644 --- a/packages/console/src/components/WorkflowGraph/WorkflowGraph.tsx +++ b/packages/console/src/components/WorkflowGraph/WorkflowGraph.tsx @@ -5,6 +5,7 @@ import { NonIdealState } from 'components/common/NonIdealState'; import { CompiledNode } from 'models/Node/types'; import { dNode } from 'models/Graph/types'; import { useDetailsPanel } from 'components/Executions/ExecutionDetails/DetailsPanelContext'; +import { ReactFlowBreadCrumbProvider } from 'components/flytegraph/ReactFlow/ReactFlowBreadCrumbProvider'; import t from './strings'; export interface DynamicWorkflowMapping { @@ -43,12 +44,14 @@ export const WorkflowGraph: React.FC = ({ } return ( - + + + ); }; diff --git a/packages/console/src/components/flytegraph/ReactFlow/BreadCrumb.tsx b/packages/console/src/components/flytegraph/ReactFlow/BreadCrumb.tsx new file mode 100644 index 000000000..619248d33 --- /dev/null +++ b/packages/console/src/components/flytegraph/ReactFlow/BreadCrumb.tsx @@ -0,0 +1,138 @@ +import { useNodeExecutionDynamicContext } from 'components/Executions/contextProvider/NodeExecutionDetails/NodeExecutionDynamicProvider'; +import { COLOR_SPECTRUM } from 'components/Theme/colorSpectrum'; +import { NodeExecutionPhase } from 'models'; +import React, { PropsWithChildren } from 'react'; +import { getNestedContainerStyle } from './utils'; + +const BREAD_FONT_SIZE = '9px'; +const BREAD_COLOR_ACTIVE = COLOR_SPECTRUM.purple60.color; +const BREAD_COLOR_INACTIVE = COLOR_SPECTRUM.black.color; + +export const BreadElement = ({ + nestedView, + index, + currentNestedDepth, + scopedId, + onClick, +}) => { + const liStyles: React.CSSProperties = { + cursor: 'pointer', + fontSize: BREAD_FONT_SIZE, + color: BREAD_COLOR_ACTIVE, + }; + + const liStyleInactive: React.CSSProperties = { ...liStyles }; + liStyleInactive['color'] = BREAD_COLOR_INACTIVE; + + const beforeStyle: React.CSSProperties = { + cursor: 'pointer', + color: BREAD_COLOR_ACTIVE, + padding: '0 .2rem', + fontSize: BREAD_FONT_SIZE, + }; + // const onClick = + // currentNestedDepth > index + 1 ? handleNestedViewClick : undefined; + return ( +
  • + {index === 0 ? {'>'} : null} + {nestedView} + {index < currentNestedDepth - 1 ? ( + {'>'} + ) : null} +
  • + ); +}; + +const BorderElement = ({ + nodeExecutionStatus, + children, +}: PropsWithChildren<{ + nodeExecutionStatus: NodeExecutionPhase; +}>) => { + const { componentProps } = useNodeExecutionDynamicContext(); + + const borderStyle = getNestedContainerStyle(nodeExecutionStatus); + + return ( +
    + {children} +
    + ); +}; + +export const BorderContainer = ({ + nodeExecutionStatus, + currentNestedDepth, + children, +}: PropsWithChildren<{ + currentNestedDepth: number; + nodeExecutionStatus: NodeExecutionPhase; +}>) => { + let borders = ( + + {children} + + ); + for (let i = 0; i < currentNestedDepth; i++) { + borders = ( + + {borders} + + ); + } + + return borders; +}; + +const breadContainerStyle: React.CSSProperties = { + position: 'absolute', + display: 'flex', + width: '100%', + marginTop: '-1rem', +}; +const olStyles: React.CSSProperties = { + margin: 0, + padding: 0, + display: 'flex', + listStyle: 'none', + listStyleImage: 'none', + minWidth: '1rem', +}; +const headerStyle: React.CSSProperties = { + color: BREAD_COLOR_ACTIVE, + fontSize: BREAD_FONT_SIZE, + margin: 0, + padding: 0, +}; + +export const BreadCrumbContainer = ({ + text, + currentNestedDepth, + handleRootClick, + children, +}: PropsWithChildren<{ + text: string; + currentNestedDepth: number; + handleRootClick: () => void; +}>) => { + const rootClick = currentNestedDepth > 0 ? handleRootClick : undefined; + return ( +
    +
    { + e.stopPropagation(); + rootClick?.(); + }} + > + {text} +
    +
      {children}
    +
    + ); +}; diff --git a/packages/console/src/components/flytegraph/ReactFlow/ReactFlowBreadCrumbProvider.tsx b/packages/console/src/components/flytegraph/ReactFlow/ReactFlowBreadCrumbProvider.tsx new file mode 100644 index 000000000..886202d88 --- /dev/null +++ b/packages/console/src/components/flytegraph/ReactFlow/ReactFlowBreadCrumbProvider.tsx @@ -0,0 +1,97 @@ +import { useNodeExecutionsById } from 'components/Executions/contextProvider/NodeExecutionDetails'; +import { fetchChildrenExecutions } from 'components/Executions/utils'; +import React, { + createContext, + PropsWithChildren, + useContext, + Ref, + useState, +} from 'react'; +import { useQueryClient } from 'react-query'; +import { isUnFetchedDynamicNode } from './utils'; + +export type RefType = Ref; +export interface IReactFlowBreadCrumbContext { + currentNestedDepth: number; + currentNestedView: BreadCrumbViews; + setCurrentNestedView: (newLevels: BreadCrumbViews) => void; + onAddNestedView: (view: any, sourceNode?: any) => Promise; + onRemoveNestedView: (viewParent: any, viewIndex: any) => void; +} + +export const ReactFlowBreadCrumbContext = + createContext({ + currentNestedDepth: 0, + currentNestedView: {}, + setCurrentNestedView: () => {}, + onAddNestedView: () => { + throw new Error('please use NodeExecutionDynamicProvider'); + }, + onRemoveNestedView: () => { + throw new Error('please use NodeExecutionDynamicProvider'); + }, + }); + +export interface BreadCrumbViews { + [key: string]: string[]; +} +/** Should wrap "top level" component in Execution view, will build a nodeExecutions tree for specific workflow */ +export const ReactFlowBreadCrumbProvider = ({ + children, +}: PropsWithChildren<{}>) => { + const queryClient = useQueryClient(); + const [currentNestedView, setCurrentNestedView] = useState( + {}, + ); + const currentNestedDepth = (currentNestedView?.length || 0) as any as number; + + const { nodeExecutionsById, setCurrentNodeExecutionsById } = + useNodeExecutionsById(); + + const onAddNestedView = async (view, sourceNode: any = null) => { + if (sourceNode && isUnFetchedDynamicNode(sourceNode)) { + await fetchChildrenExecutions( + queryClient, + sourceNode.scopedId, + nodeExecutionsById, + setCurrentNodeExecutionsById, + ); + } + + const currentView = currentNestedView[view.parent] || []; + const newView = { + [view.parent]: [...currentView, view.view], + }; + setCurrentNestedView(newView); + }; + + const onRemoveNestedView = (viewParent, viewIndex) => { + const newcurrentNestedView: any = { ...currentNestedView }; + newcurrentNestedView[viewParent] = newcurrentNestedView[viewParent]?.filter( + (_item, i) => i <= viewIndex, + ); + if (newcurrentNestedView[viewParent]?.length < 1) { + delete newcurrentNestedView[viewParent]; + } + setCurrentNestedView(newcurrentNestedView); + }; + + return ( + + {children} + + ); +}; + +export const useReactFlowBreadCrumbContext = + (): IReactFlowBreadCrumbContext => { + return useContext(ReactFlowBreadCrumbContext); + }; diff --git a/packages/console/src/components/flytegraph/ReactFlow/ReactFlowGraphComponent.tsx b/packages/console/src/components/flytegraph/ReactFlow/ReactFlowGraphComponent.tsx index 24a5a5667..c7c8a0605 100644 --- a/packages/console/src/components/flytegraph/ReactFlow/ReactFlowGraphComponent.tsx +++ b/packages/console/src/components/flytegraph/ReactFlow/ReactFlowGraphComponent.tsx @@ -5,19 +5,16 @@ import { useNodeExecutionsById, } from 'components/Executions/contextProvider/NodeExecutionDetails'; import { NodeExecutionPhase } from 'models/Execution/enums'; -import { - fetchChildrenExecutions, - isNodeGateNode, -} from 'components/Executions/utils'; +import { isNodeGateNode } from 'components/Executions/utils'; import { dNode } from 'models/Graph/types'; -import { useQueryClient } from 'react-query'; import { extractCompiledNodes } from 'components/hooks/utils'; import { useDetailsPanel } from 'components/Executions/ExecutionDetails/DetailsPanelContext'; import { RFWrapperProps, RFGraphTypes, ConvertDagProps } from './types'; -import { getRFBackground, isUnFetchedDynamicNode } from './utils'; +import { getRFBackground } from './utils'; import { ReactFlowWrapper } from './ReactFlowWrapper'; import { Legend } from './NodeStatusLegend'; import { PausedTasksComponent } from './PausedTasksComponent'; +import { useReactFlowBreadCrumbContext } from './ReactFlowBreadCrumbProvider'; const containerStyle: React.CSSProperties = { display: 'flex', @@ -35,42 +32,44 @@ export const ReactFlowGraphComponent = ({ selectedPhase, initialNodes, }) => { - const { nodeExecutionsById, shouldUpdate } = - useNodeExecutionsById(); + const { nodeExecutionsById, shouldUpdate } = useNodeExecutionsById(); const { isDetailsTabClosed } = useDetailsPanel(); const { compiledWorkflowClosure } = useNodeExecutionContext(); const [pausedNodes, setPausedNodes] = useState([]); - const [currentNestedView, setcurrentNestedView] = useState({}); - const onAddNestedView = async (view, sourceNode: any = null) => { - // if (sourceNode && isUnFetchedDynamicNode(sourceNode)) { - // await fetchChildrenExecutions( - // queryClient, - // sourceNode.scopedId, - // nodeExecutionsById, - // setCurrentNodeExecutionsById, - // ); - // } + const { currentNestedView, onAddNestedView, onRemoveNestedView } = + useReactFlowBreadCrumbContext(); + // const [currentNestedView, setcurrentNestedView] = useState({}); - const currentView = currentNestedView[view.parent] || []; - const newView = { - [view.parent]: [...currentView, view.view], - }; - setcurrentNestedView(newView); - }; + // const onAddNestedView = async (view, sourceNode: any = null) => { + // // if (sourceNode && isUnFetchedDynamicNode(sourceNode)) { + // // await fetchChildrenExecutions( + // // queryClient, + // // sourceNode.scopedId, + // // nodeExecutionsById, + // // setCurrentNodeExecutionsById, + // // ); + // // } - const onRemoveNestedView = (viewParent, viewIndex) => { - const newcurrentNestedView: any = { ...currentNestedView }; - newcurrentNestedView[viewParent] = newcurrentNestedView[viewParent]?.filter( - (_item, i) => i <= viewIndex, - ); - if (newcurrentNestedView[viewParent]?.length < 1) { - delete newcurrentNestedView[viewParent]; - } - setcurrentNestedView(newcurrentNestedView); - }; + // const currentView = currentNestedView[view.parent] || []; + // const newView = { + // [view.parent]: [...currentView, view.view], + // }; + // setcurrentNestedView(newView); + // }; + + // const onRemoveNestedView = (viewParent, viewIndex) => { + // const newcurrentNestedView: any = { ...currentNestedView }; + // newcurrentNestedView[viewParent] = newcurrentNestedView[viewParent]?.filter( + // (_item, i) => i <= viewIndex, + // ); + // if (newcurrentNestedView[viewParent]?.length < 1) { + // delete newcurrentNestedView[viewParent]; + // } + // setcurrentNestedView(newcurrentNestedView); + // }; const rfGraphJson = useMemo(() => { return ConvertFlyteDagToReactFlows({ diff --git a/packages/console/src/components/flytegraph/ReactFlow/customNodeComponents.tsx b/packages/console/src/components/flytegraph/ReactFlow/customNodeComponents.tsx index 0542e892c..8e6ccf02a 100644 --- a/packages/console/src/components/flytegraph/ReactFlow/customNodeComponents.tsx +++ b/packages/console/src/components/flytegraph/ReactFlow/customNodeComponents.tsx @@ -6,7 +6,6 @@ import { RENDER_ORDER } from 'components/Executions/TaskExecutionsList/constants import { whiteColor } from 'components/Theme/constants'; import { PlayCircleOutline } from '@material-ui/icons'; import { Tooltip } from '@material-ui/core'; -import { COLOR_SPECTRUM } from 'components/Theme/colorSpectrum'; import { getNodeFrontendPhase } from 'components/Executions/utils'; import { CacheStatus } from 'components/Executions/CacheStatus'; import { LaunchFormDialog } from 'components/Launch/LaunchForm/LaunchFormDialog'; @@ -20,11 +19,15 @@ import { COLOR_GRAPH_BACKGROUND, getGraphHandleStyle, getGraphNodeStyle, - getNestedContainerStyle, getStatusColor, } from './utils'; import { RFHandleProps, RFNode } from './types'; import t from './strings'; +import { + BorderContainer, + BreadCrumbContainer, + BreadElement, +} from './BreadCrumb'; const taskContainerStyle: React.CSSProperties = { position: 'absolute', @@ -155,16 +158,12 @@ export const ReactFlowCustomMaxNested = ({ data }: RFNode) => { const styles = getGraphNodeStyle(dTypes.nestedMaxDepth); const { componentProps } = useNodeExecutionDynamicContext(); - const onClick = () => { - onAddNestedView(); - }; - return renderBasicNode( taskType, text, scopedId, styles, - onClick, + onAddNestedView, componentProps, ); }; @@ -269,7 +268,8 @@ export const ReactFlowGateNode = ({ data }: RFNode) => { cursor: 'pointer', }; - const handleNodeClick = () => { + const handleNodeClick = e => { + e.stopPropagation(); onNodeSelectionChanged(true); }; @@ -374,7 +374,9 @@ export const ReactFlowCustomTaskNode = ( display: 'flex', }; - const handleNodeClick = _e => { + const handleNodeClick = e => { + e.stopPropagation(); + if (nodeExecutionStatus === NodeExecutionPhase.SKIPPED) { return; } @@ -452,19 +454,6 @@ export const ReactFlowSubWorkflowContainer = ({ data }: RFNode) => { currentNestedView, onRemoveNestedView, } = data; - const BREAD_FONT_SIZE = '9px'; - const BREAD_COLOR_ACTIVE = COLOR_SPECTRUM.purple60.color; - const BREAD_COLOR_INACTIVE = COLOR_SPECTRUM.black.color; - const borderStyle = getNestedContainerStyle(nodeExecutionStatus); - const { componentProps } = useNodeExecutionDynamicContext(); - - const handleNestedViewClick = e => { - const index = e.target.id.substr( - e.target.id.indexOf('_') + 1, - e.target.id.length, - ); - onRemoveNestedView(scopedId, index); - }; const handleRootClick = () => { onRemoveNestedView(scopedId, -1); @@ -472,102 +461,33 @@ export const ReactFlowSubWorkflowContainer = ({ data }: RFNode) => { const currentNestedDepth = currentNestedView?.length || 0; - const BreadElement = ({ nestedView, index }) => { - const liStyles: React.CSSProperties = { - cursor: 'pointer', - fontSize: BREAD_FONT_SIZE, - color: BREAD_COLOR_ACTIVE, - }; - - const liStyleInactive: React.CSSProperties = { ...liStyles }; - liStyleInactive['color'] = BREAD_COLOR_INACTIVE; - - const beforeStyle: React.CSSProperties = { - cursor: 'pointer', - color: BREAD_COLOR_ACTIVE, - padding: '0 .2rem', - fontSize: BREAD_FONT_SIZE, - }; - // const onClick = - // currentNestedDepth > index + 1 ? handleNestedViewClick : undefined; - return ( -
  • - {index === 0 ? {'>'} : null} - {nestedView} - {index < currentNestedDepth - 1 ? ( - {'>'} - ) : null} -
  • - ); - }; - - const BorderElement = props => { - return ( -
    - {props.children} -
    - ); - }; - - const BorderContainer = props => { - let output = BorderElement(props); - for (let i = 0; i < currentNestedDepth; i++) { - output = {output}; - } - return output; - }; - - const renderBreadCrumb = () => { - const breadContainerStyle: React.CSSProperties = { - position: 'absolute', - display: 'flex', - width: '100%', - marginTop: '-1rem', - }; - const olStyles: React.CSSProperties = { - margin: 0, - padding: 0, - display: 'flex', - listStyle: 'none', - listStyleImage: 'none', - minWidth: '1rem', - }; - const headerStyle: React.CSSProperties = { - color: BREAD_COLOR_ACTIVE, - fontSize: BREAD_FONT_SIZE, - margin: 0, - padding: 0, - }; - - const rootClick = currentNestedDepth > 0 ? handleRootClick : undefined; - return ( -
    -
    - {text} -
    -
      - {currentNestedView?.map((nestedView, i) => { - return ( - - ); - })} -
    -
    - ); - }; - return ( <> - {renderBreadCrumb()} - + + {currentNestedView?.map((nestedView, i) => { + return ( + { + e.stopPropagation(); + onRemoveNestedView(scopedId, i); + }} + /> + ); + })} + + {renderDefaultHandles( scopedId, getGraphHandleStyle('source'),