From e95faa89bf53c9b20ad34a89c7aededabe8e724e Mon Sep 17 00:00:00 2001 From: Carina Ursu Date: Thu, 4 May 2023 18:05:19 -0700 Subject: [PATCH] chore: propagate dynamic parent id (#751) * chore: propagate dynamic parent id Signed-off-by: Carina Ursu * chore: use app state execution in context panel Signed-off-by: Carina Ursu * chore: fix build err Signed-off-by: Carina Ursu * chore: taskexecutionslist.test.tsx Signed-off-by: Carina Ursu * chore: nodeexecutiondetailspanelcontent.tsx Signed-off-by: Carina Ursu * chore: cleanup Signed-off-by: Carina Ursu --------- Signed-off-by: Carina Ursu --- packages/console/package.json | 2 +- .../components/Entities/EntitySchedules.tsx | 1 - .../ExecutionDetails/ExecutionMetadata.tsx | 1 - .../NodeExecutionDetailsPanelContent.tsx | 179 +++++++----------- .../Timeline/NodeExecutionName.tsx | 4 +- .../Executions/ExecutionDetails/utils.ts | 18 +- .../TaskExecutionsList/TaskExecutionsList.tsx | 41 ++-- .../test/TaskExecutionsList.test.tsx | 27 ++- .../NodeExecutionDetailsContextProvider.tsx | 2 +- .../NodeExecutionDetails/utils.ts | 17 +- .../Executions/nodeExecutionQueries.ts | 64 ++++--- .../src/components/Executions/types.ts | 1 + .../src/components/Executions/utils.ts | 6 +- .../src/components/Launch/LaunchForm/utils.ts | 1 - .../components/Tables/PaginatedDataList.tsx | 6 +- .../ReactFlow/customNodeComponents.tsx | 2 +- .../src/components/hooks/useNodeExecution.ts | 2 +- packages/console/src/models/Common/types.ts | 3 + .../__mocks__/mockNodeExecutionsData.ts | 1 + .../console/src/models/Execution/types.ts | 10 +- website/package.json | 2 +- yarn.lock | 4 +- 22 files changed, 181 insertions(+), 213 deletions(-) diff --git a/packages/console/package.json b/packages/console/package.json index 2bee7a2a1..8980ff2c3 100644 --- a/packages/console/package.json +++ b/packages/console/package.json @@ -1,6 +1,6 @@ { "name": "@flyteorg/console", - "version": "0.0.26", + "version": "0.0.27", "description": "Flyteconsole main app module", "main": "./dist/index.js", "module": "./lib/index.js", diff --git a/packages/console/src/components/Entities/EntitySchedules.tsx b/packages/console/src/components/Entities/EntitySchedules.tsx index 6c16cca4a..847735672 100644 --- a/packages/console/src/components/Entities/EntitySchedules.tsx +++ b/packages/console/src/components/Entities/EntitySchedules.tsx @@ -17,7 +17,6 @@ import { useCommonStyles } from 'components/common/styles'; import { WaitForData } from 'components/common/WaitForData'; import { useWorkflowSchedules } from 'components/hooks/useWorkflowSchedules'; import { ResourceIdentifier } from 'models/Common/types'; -import { identifierToString } from 'models/Common/utils'; import { LaunchPlan } from 'models/Launch/types'; import * as React from 'react'; import { LaunchPlanLink } from 'components/LaunchPlan/LaunchPlanLink'; diff --git a/packages/console/src/components/Executions/ExecutionDetails/ExecutionMetadata.tsx b/packages/console/src/components/Executions/ExecutionDetails/ExecutionMetadata.tsx index a5a4428a8..651cb5625 100644 --- a/packages/console/src/components/Executions/ExecutionDetails/ExecutionMetadata.tsx +++ b/packages/console/src/components/Executions/ExecutionDetails/ExecutionMetadata.tsx @@ -7,7 +7,6 @@ import { formatDateUTC, protobufDurationToHMS } from 'common/formatters'; import { timestampToDate } from 'common/utils'; import { useCommonStyles } from 'components/common/styles'; import { secondaryBackgroundColor } from 'components/Theme/constants'; -import { Execution } from 'models/Execution/types'; import { Link as RouterLink } from 'react-router-dom'; import { Routes } from 'routes/routes'; import { ExecutionContext } from '../contexts'; diff --git a/packages/console/src/components/Executions/ExecutionDetails/NodeExecutionDetailsPanelContent.tsx b/packages/console/src/components/Executions/ExecutionDetails/NodeExecutionDetailsPanelContent.tsx index b812d8722..3b575e204 100644 --- a/packages/console/src/components/Executions/ExecutionDetails/NodeExecutionDetailsPanelContent.tsx +++ b/packages/console/src/components/Executions/ExecutionDetails/NodeExecutionDetailsPanelContent.tsx @@ -1,4 +1,4 @@ -import React, { useContext, useEffect, useMemo, useRef, useState } from 'react'; +import React, { useEffect, useMemo, useRef, useState } from 'react'; import { IconButton, Typography, Tab, Tabs } from '@material-ui/core'; import { makeStyles, Theme } from '@material-ui/core/styles'; import { ArrowBackIos, Close } from '@material-ui/icons'; @@ -10,18 +10,14 @@ import { ExecutionStatusBadge } from 'components/Executions/ExecutionStatusBadge import { LocationState } from 'components/hooks/useLocationState'; import { useTabState } from 'components/hooks/useTabState'; import { LocationDescriptor } from 'history'; -import { PaginatedEntityResponse } from 'models/AdminEntity/types'; import { Workflow } from 'models/Workflow/types'; import { - ExternalResource, - LogsByPhase, MapTaskExecution, NodeExecution, NodeExecutionIdentifier, - TaskExecution, } from 'models/Execution/types'; import Skeleton from 'react-loading-skeleton'; -import { useQuery, useQueryClient } from 'react-query'; +import { useQueryClient } from 'react-query'; import { Link as RouterLink } from 'react-router-dom'; import { Routes } from 'routes/routes'; import { NoDataIsAvailable } from 'components/Literals/LiteralMapViewer'; @@ -37,24 +33,21 @@ import { } from 'components/WorkflowGraph/utils'; import { TaskVersionDetailsLink } from 'components/Entities/VersionDetails/VersionDetailsLink'; import { Identifier } from 'models/Common/types'; -import { isMapTaskV1 } from 'models/Task/utils'; -import { merge } from 'lodash'; +import { isEqual, values } from 'lodash'; import { extractCompiledNodes } from 'components/hooks/utils'; import { NodeExecutionCacheStatus } from '../NodeExecutionCacheStatus'; -import { - makeListTaskExecutionsQuery, - makeNodeExecutionQuery, -} from '../nodeExecutionQueries'; +import { getTaskExecutions } from '../nodeExecutionQueries'; import { NodeExecutionDetails } from '../types'; -import { useNodeExecutionContext } from '../contextProvider/NodeExecutionDetails'; +import { + useNodeExecutionContext, + useNodeExecutionsById, +} from '../contextProvider/NodeExecutionDetails'; import { getTaskExecutionDetailReasons } from './utils'; import { fetchWorkflowExecution } from '../useWorkflowExecution'; import { NodeExecutionTabs } from './NodeExecutionTabs'; import { ExecutionDetailsActions } from './ExecutionDetailsActions'; import { getNodeFrontendPhase, isNodeGateNode } from '../utils'; -import { fetchTaskExecutionList } from '../taskExecutionQueries'; -import { getGroupedLogs } from '../TaskExecutionsList/utils'; -import { WorkflowNodeExecutionsContext } from '../contexts'; +import { WorkflowNodeExecution } from '../contexts'; const useStyles = makeStyles((theme: Theme) => { const paddingVertical = `${theme.spacing(2)}px`; @@ -259,11 +252,28 @@ export const NodeExecutionDetailsPanelContent: React.FC< const commonStyles = useCommonStyles(); const styles = useStyles(); const queryClient = useQueryClient(); + + const { nodeExecutionsById, setCurrentNodeExecutionsById } = + useNodeExecutionsById(); + + const nodeExecution = useMemo(() => { + return values(nodeExecutionsById).find(node => + isEqual(node.id, nodeExecutionId), + ); + }, [nodeExecutionId, nodeExecutionsById]); + + const [isReasonsVisible, setReasonsVisible] = useState(false); + const [dag, setDag] = useState(null); + const [details, setDetails] = useState(); + const [selectedTaskExecution, setSelectedTaskExecution] = + useState(null); + // const [nodePhase, setNodePhase] = useState( + // nodeExecution?.closure.phase ?? NodeExecutionPhase.UNDEFINED, + // ); + const { getNodeExecutionDetails, compiledWorkflowClosure } = useNodeExecutionContext(); - const { nodeExecutionsById, setCurrentNodeExecutionsById } = useContext( - WorkflowNodeExecutionsContext, - ); + const isGateNode = isNodeGateNode( extractCompiledNodes(compiledWorkflowClosure), nodeExecutionsById[nodeExecutionId.nodeId]?.metadata?.specNodeId || @@ -273,61 +283,50 @@ export const NodeExecutionDetailsPanelContent: React.FC< const [nodeExecutionLoading, setNodeExecutionLoading] = useState(false); - const { data: nodeExecution } = useQuery({ - ...makeNodeExecutionQuery(nodeExecutionId), - // The selected NodeExecution has been fetched at this point, we don't want to - // issue an additional fetch. - staleTime: Infinity, + const isMounted = useRef(false); + useEffect(() => { + isMounted.current = true; + return () => { + isMounted.current = false; + }; + }, []); + + useEffect(() => { + let isCurrent = true; + getNodeExecutionDetails(nodeExecution).then(res => { + if (isCurrent) { + setDetails(res); + } + }); + + return () => { + isCurrent = false; + }; }); useEffect(() => { let isCurrent = true; - async function fetchTasksData(exe, queryClient) { + async function fetchTasksData(queryClient) { setNodeExecutionLoading(true); - const taskExecutions = await fetchTaskExecutionList(queryClient, exe.id); + const newNode = await getTaskExecutions(queryClient, nodeExecution!); - const useNewMapTaskView = taskExecutions.every(taskExecution => { + if (isCurrent && newNode) { const { - closure: { taskType, metadata, eventVersion = 0 }, - } = taskExecution; - return isMapTaskV1( - eventVersion, - metadata?.externalResources?.length ?? 0, - taskType ?? undefined, - ); - }); - const externalResources: ExternalResource[] = taskExecutions - .map(taskExecution => taskExecution.closure.metadata?.externalResources) - .flat() - .filter((resource): resource is ExternalResource => !!resource); - - const logsByPhase: LogsByPhase = getGroupedLogs(externalResources); - - const exeWithResources = { - [exe.scopedId]: { - ...exe, - ...(useNewMapTaskView && logsByPhase.size > 0 && { logsByPhase }), - tasksFetched: true, - }, - }; - - if (isCurrent) { - const newNodeExecutionsById = merge( - nodeExecutionsById, - exeWithResources, - ); - setCurrentNodeExecutionsById(newNodeExecutionsById); + closure: _, + metadata: __, + ...parentLight + } = newNode || ({} as WorkflowNodeExecution); + + setCurrentNodeExecutionsById({ + [newNode.scopedId!]: parentLight as WorkflowNodeExecution, + }); setNodeExecutionLoading(false); } } - if (nodeExecution) { - if ( - nodeExecution.scopedId && - !nodeExecutionsById?.[nodeExecution.scopedId]?.tasksFetched - ) - fetchTasksData(nodeExecution, queryClient); + if (nodeExecution && !nodeExecution?.tasksFetched) { + fetchTasksData(queryClient); } else { if (isCurrent) { setNodeExecutionLoading(false); @@ -338,45 +337,15 @@ export const NodeExecutionDetailsPanelContent: React.FC< }; }, [nodeExecution]); - const [isReasonsVisible, setReasonsVisible] = useState(false); - const [dag, setDag] = useState(null); - const [details, setDetails] = useState(); - const [selectedTaskExecution, setSelectedTaskExecution] = - useState(null); - const [nodePhase, setNodePhase] = useState( - nodeExecution?.closure.phase ?? NodeExecutionPhase.UNDEFINED, - ); - - const isMounted = useRef(false); - useEffect(() => { - isMounted.current = true; - return () => { - isMounted.current = false; - }; - }, []); - - useEffect(() => { - let isCurrent = true; - getNodeExecutionDetails(nodeExecution).then(res => { - if (isCurrent) { - setDetails(res); - } - }); - - return () => { - isCurrent = false; - }; - }); - useEffect(() => { setReasonsVisible(false); - setNodePhase(nodeExecution?.closure.phase ?? NodeExecutionPhase.UNDEFINED); }, [nodeExecutionId]); useEffect(() => { setSelectedTaskExecution(null); }, [nodeExecutionId, taskPhase]); + // TODO: needs to be removed const getWorkflowDag = async () => { const workflowExecution = await fetchWorkflowExecution( queryClient, @@ -397,15 +366,10 @@ export const NodeExecutionDetailsPanelContent: React.FC< } else { if (dag) setDag(null); } - const listTaskExecutionsQuery = useQuery< - PaginatedEntityResponse, - Error - >({ - ...makeListTaskExecutionsQuery(nodeExecutionId), - staleTime: Infinity, - }); - const reasons = getTaskExecutionDetailReasons(listTaskExecutionsQuery.data); + const reasons = getTaskExecutionDetailReasons( + nodeExecution?.taskExecutions ?? [], + ); const onBackClick = () => { setSelectedTaskExecution(null); @@ -441,16 +405,19 @@ export const NodeExecutionDetailsPanelContent: React.FC< ); }, [nodeExecutionId, selectedTaskExecution]); - const frontendPhase = useMemo( - () => getNodeFrontendPhase(nodePhase, isGateNode), - [nodePhase, isGateNode], - ); + const frontendPhase = useMemo(() => { + const computedPhase = getNodeFrontendPhase( + nodeExecution?.closure.phase ?? NodeExecutionPhase.UNDEFINED, + isGateNode, + ); + return computedPhase; + }, [nodeExecution?.closure.phase, isGateNode]); const isRunningPhase = useMemo( () => frontendPhase === NodeExecutionPhase.QUEUED || frontendPhase === NodeExecutionPhase.RUNNING, - [nodePhase], + [frontendPhase], ); const handleReasonsVisibility = () => { diff --git a/packages/console/src/components/Executions/ExecutionDetails/Timeline/NodeExecutionName.tsx b/packages/console/src/components/Executions/ExecutionDetails/Timeline/NodeExecutionName.tsx index 475870913..f3cfc30e4 100644 --- a/packages/console/src/components/Executions/ExecutionDetails/Timeline/NodeExecutionName.tsx +++ b/packages/console/src/components/Executions/ExecutionDetails/Timeline/NodeExecutionName.tsx @@ -7,8 +7,8 @@ import { SelectNodeExecutionLink } from 'components/Executions/Tables/SelectNode import { isEqual } from 'lodash'; import { NodeExecutionPhase } from 'models/Execution/enums'; import { NodeExecution } from 'models/Execution/types'; -import React, { useContext, useEffect, useState } from 'react'; -import { DetailsPanelContext, useDetailsPanel } from '../DetailsPanelContext'; +import React, { useEffect, useState } from 'react'; +import { useDetailsPanel } from '../DetailsPanelContext'; interface NodeExecutionTimelineNameData { name: string; diff --git a/packages/console/src/components/Executions/ExecutionDetails/utils.ts b/packages/console/src/components/Executions/ExecutionDetails/utils.ts index 044f62284..6820f1bb2 100644 --- a/packages/console/src/components/Executions/ExecutionDetails/utils.ts +++ b/packages/console/src/components/Executions/ExecutionDetails/utils.ts @@ -5,7 +5,6 @@ import { TaskExecution, } from 'models/Execution/types'; import { Routes } from 'routes/routes'; -import { PaginatedEntityResponse } from 'models/AdminEntity/types'; import { timestampToDate } from 'common'; import { formatDateUTC } from 'common/formatters'; @@ -27,21 +26,26 @@ export function getExecutionBackLink(execution: Execution): string { } export function getTaskExecutionDetailReasons( - taskExecutionDetails?: PaginatedEntityResponse, + taskExecutionDetails?: TaskExecution[], ): (string | null | undefined)[] { let reasons: string[] = []; - taskExecutionDetails?.entities.forEach(taskExecution => { - if (taskExecution.closure.reasons) + taskExecutionDetails?.forEach?.(taskExecution => { + const finalReasons = ( + taskExecution.closure.reasons?.length + ? taskExecution.closure.reasons + : [{ message: taskExecution.closure.reason }] + ).filter(r => !!r); + + if (finalReasons) { reasons = reasons.concat( - taskExecution.closure.reasons.map( + finalReasons.map( reason => (reason.occurredAt ? formatDateUTC(timestampToDate(reason.occurredAt)) + ' ' : '') + reason.message, ), ); - else if (taskExecution.closure.reason) - reasons.push(taskExecution.closure.reason); + } }); return reasons; } diff --git a/packages/console/src/components/Executions/TaskExecutionsList/TaskExecutionsList.tsx b/packages/console/src/components/Executions/TaskExecutionsList/TaskExecutionsList.tsx index 11de3a35b..fadc20a4a 100644 --- a/packages/console/src/components/Executions/TaskExecutionsList/TaskExecutionsList.tsx +++ b/packages/console/src/components/Executions/TaskExecutionsList/TaskExecutionsList.tsx @@ -2,21 +2,13 @@ import * as React from 'react'; import { makeStyles, Theme } from '@material-ui/core/styles'; import { noExecutionsFoundString } from 'common/constants'; import { NonIdealState } from 'components/common/NonIdealState'; -import { WaitForData } from 'components/common/WaitForData'; -import { - MapTaskExecution, - NodeExecution, - TaskExecution, -} from 'models/Execution/types'; +import { MapTaskExecution, TaskExecution } from 'models/Execution/types'; import { isMapTaskV1 } from 'models/Task/utils'; import { TaskExecutionPhase } from 'models/Execution/enums'; -import { - useTaskExecutions, - useTaskExecutionsRefresher, -} from '../useTaskExecutions'; import { MapTaskExecutionsListItem } from './MapTaskExecutionListItem'; import { TaskExecutionsListItem } from './TaskExecutionsListItem'; import { getUniqueTaskExecutionName } from './utils'; +import { WorkflowNodeExecution } from '../contexts'; const useStyles = makeStyles((theme: Theme) => ({ noExecutionsMessage: { @@ -24,19 +16,13 @@ const useStyles = makeStyles((theme: Theme) => ({ }, })); -interface TaskExecutionsListProps { - nodeExecution: NodeExecution; - onTaskSelected: (val: MapTaskExecution) => void; - phase?: TaskExecutionPhase; -} - export const TaskExecutionsListContent: React.FC<{ taskExecutions: TaskExecution[]; onTaskSelected: (val: MapTaskExecution) => void; phase?: TaskExecutionPhase; }> = ({ taskExecutions, onTaskSelected, phase }) => { const styles = useStyles(); - if (!taskExecutions.length) { + if (!taskExecutions?.length) { return ( void; + phase?: TaskExecutionPhase; +} + /** Renders a vertical list of task execution records with horizontal separators */ export const TaskExecutionsList: React.FC = ({ @@ -83,16 +75,11 @@ export const TaskExecutionsList: React.FC = ({ onTaskSelected, phase, }) => { - const taskExecutions = useTaskExecutions(nodeExecution.id); - useTaskExecutionsRefresher(nodeExecution, taskExecutions); - return ( - - - + ); }; diff --git a/packages/console/src/components/Executions/TaskExecutionsList/test/TaskExecutionsList.test.tsx b/packages/console/src/components/Executions/TaskExecutionsList/test/TaskExecutionsList.test.tsx index a48a806a4..fcd206f41 100644 --- a/packages/console/src/components/Executions/TaskExecutionsList/test/TaskExecutionsList.test.tsx +++ b/packages/console/src/components/Executions/TaskExecutionsList/test/TaskExecutionsList.test.tsx @@ -1,14 +1,13 @@ +import * as React from 'react'; import { render, waitFor } from '@testing-library/react'; import { noExecutionsFoundString } from 'common/constants'; import { APIContext } from 'components/data/apiContext'; import { mockAPIContextValue } from 'components/data/__mocks__/apiContext'; -import { SortDirection } from 'models/AdminEntity/types'; import { listTaskExecutions } from 'models/Execution/api'; import { NodeExecution } from 'models/Execution/types'; import { mockNodeExecutionResponse } from 'models/Execution/__mocks__/mockNodeExecutionsData'; -import { taskSortFields } from 'models/Task/constants'; -import * as React from 'react'; import { TaskExecutionsList } from '../TaskExecutionsList'; +import { MockPythonTaskExecution } from '../TaskExecutions.mocks'; describe('TaskExecutionsList', () => { let nodeExecution: NodeExecution; @@ -35,21 +34,19 @@ describe('TaskExecutionsList', () => { it('Renders message when no task executions exist', async () => { const { queryByText } = renderList(); await waitFor(() => {}); - expect(mockListTaskExecutions).toHaveBeenCalled(); expect(queryByText(noExecutionsFoundString)).toBeInTheDocument(); }); - it('Requests items in correct order', async () => { - renderList(); + it('Renders tasks when task executions exist', async () => { + nodeExecution = { + ...mockNodeExecutionResponse, + startedAt: '2021-01-01T00:00:00Z', + taskExecutions: [MockPythonTaskExecution], + } as NodeExecution; + + const { queryByText } = renderList(); await waitFor(() => {}); - expect(mockListTaskExecutions).toHaveBeenCalledWith( - expect.anything(), - expect.objectContaining({ - sort: { - key: taskSortFields.createdAt, - direction: SortDirection.ASCENDING, - }, - }), - ); + expect(queryByText('Attempt 01')).toBeInTheDocument(); + expect(queryByText('Succeeded')).toBeInTheDocument(); }); }); diff --git a/packages/console/src/components/Executions/contextProvider/NodeExecutionDetails/NodeExecutionDetailsContextProvider.tsx b/packages/console/src/components/Executions/contextProvider/NodeExecutionDetails/NodeExecutionDetailsContextProvider.tsx index 0721cd434..8424c95b4 100644 --- a/packages/console/src/components/Executions/contextProvider/NodeExecutionDetails/NodeExecutionDetailsContextProvider.tsx +++ b/packages/console/src/components/Executions/contextProvider/NodeExecutionDetails/NodeExecutionDetailsContextProvider.tsx @@ -9,7 +9,7 @@ import React, { import { log } from 'common/log'; import { Identifier } from 'models/Common/types'; import { NodeExecution } from 'models/Execution/types'; -import { CompiledWorkflowClosure, Workflow } from 'models/Workflow/types'; +import { CompiledWorkflowClosure } from 'models/Workflow/types'; import { useQueryClient } from 'react-query'; import { fetchWorkflow } from 'components/Workflow/workflowQueries'; import { NodeExecutionDetails } from '../../types'; diff --git a/packages/console/src/components/Executions/contextProvider/NodeExecutionDetails/utils.ts b/packages/console/src/components/Executions/contextProvider/NodeExecutionDetails/utils.ts index 7f71deba4..112de41dd 100644 --- a/packages/console/src/components/Executions/contextProvider/NodeExecutionDetails/utils.ts +++ b/packages/console/src/components/Executions/contextProvider/NodeExecutionDetails/utils.ts @@ -1,4 +1,4 @@ -import { merge, mergeWith } from 'lodash'; +import { cloneDeep, merge, mergeWith } from 'lodash'; export const mapStringifyReplacer = (key: string, value: any) => { if (value instanceof Map) { @@ -19,12 +19,19 @@ export const stringifyIsEqual = (a: any, b: any) => { }; export const mergeNodeExecutions = (val, srcVal, _key) => { - const retVal = mergeWith(val, srcVal, (val, srcVal, _key) => { - if (srcVal instanceof Map) { - return srcVal; + const retVal = mergeWith(val, srcVal, (target, src, _key) => { + if (!target) { + return src; + } + const clonedTarget = cloneDeep(target); + const clonedSrc = cloneDeep(src); + if (clonedSrc instanceof Map) { + return clonedSrc; } const finaVal = - typeof srcVal === 'object' ? merge({ ...val }, { ...srcVal }) : srcVal; + typeof clonedSrc === 'object' + ? merge(clonedTarget, clonedSrc) + : clonedSrc; return finaVal; }); return retVal; diff --git a/packages/console/src/components/Executions/nodeExecutionQueries.ts b/packages/console/src/components/Executions/nodeExecutionQueries.ts index a768fd246..88d99fd88 100644 --- a/packages/console/src/components/Executions/nodeExecutionQueries.ts +++ b/packages/console/src/components/Executions/nodeExecutionQueries.ts @@ -1,6 +1,6 @@ import { QueryInput, QueryType } from 'components/data/types'; import { retriesToZero } from 'components/flytegraph/ReactFlow/utils'; -import { isEqual } from 'lodash'; +import { cloneDeep, isEqual } from 'lodash'; import { PaginatedEntityResponse, RequestConfig, @@ -63,6 +63,7 @@ export function makeNodeExecutionQuery( export function makeNodeExecutionAndTasksQuery( id: NodeExecutionIdentifier, queryClient: QueryClient, + dynamicParentNodeId?: string, ): QueryInput { return { queryKey: [QueryType.NodeExecutionAndTasks, id], @@ -72,8 +73,8 @@ export function makeNodeExecutionAndTasksQuery( // step 2: Fetch the task executions and attach them to the node execution const workflowNodeExecution = (await getTaskExecutions( - nodeExecutionPure, queryClient, + nodeExecutionPure, )) as WorkflowNodeExecution; if (!workflowNodeExecution) { @@ -88,15 +89,11 @@ export function makeNodeExecutionAndTasksQuery( // -- only one request is made as it is constant across all attempts const taskExecutions = workflowNodeExecution?.taskExecutions || []; const taskId = taskExecutions?.[0]?.id?.taskId; - const compiledTaskClosure = await (taskId - ? getTask(taskId!).catch(e => { - debug( - '\t failed to get compiled task closure for taskId: ', - taskId, - ' Error message:', - e, - ); - }) + + // don't issue a task compiled closure request if the node has a dynamic parent + // TODO: fetch dynamic parent to get the compiled closure + const compiledTaskClosure = await (taskId && !dynamicParentNodeId + ? getTask(taskId!).catch(() => null) : Promise.resolve(null)); // step 5: get each task's executions data @@ -123,9 +120,9 @@ export function makeNodeExecutionAndTasksQuery( }; } -const getTaskExecutions = async ( - nodeExecution: WorkflowNodeExecution, +export const getTaskExecutions = async ( queryClient: QueryClient, + nodeExecution: WorkflowNodeExecution, ): Promise => { const isTerminal = nodeExecutionIsTerminal(nodeExecution); const tasksFetched = !!nodeExecution.tasksFetched; @@ -138,7 +135,15 @@ const getTaskExecutions = async ( queryClient, nodeExecution.id as any, ).then(taskExecutions => { - const useNewMapTaskView = taskExecutions.every(taskExecution => { + const finalTaskExecutions = cloneDeep(taskExecutions)?.map( + taskExecution => + ({ + ...taskExecution, + dynamicParentNodeId: nodeExecution.dynamicParentNodeId, + } as WorkflowTaskExecution), + ); + + const useNewMapTaskView = finalTaskExecutions?.every(taskExecution => { const { closure: { taskType, metadata, eventVersion = 0 }, } = taskExecution; @@ -149,7 +154,7 @@ const getTaskExecutions = async ( ); }); - const externalResources: ExternalResource[] = taskExecutions + const externalResources: ExternalResource[] = finalTaskExecutions .map(taskExecution => taskExecution.closure.metadata?.externalResources) .flat() .filter((resource): resource is ExternalResource => !!resource); @@ -160,7 +165,7 @@ const getTaskExecutions = async ( return { ...nodeExecution, - taskExecutions, + taskExecutions: finalTaskExecutions, ...(useNewMapTaskView && logsByPhase.size > 0 && { logsByPhase }), ...((appendTasksFetched && { tasksFetched: true }) || {}), } as any as WorkflowNodeExecution; @@ -175,18 +180,22 @@ export function makeNodeExecutionQueryEnhanced( const { id } = nodeExecution || {}; return { - enabled: !!nodeExecution, + enabled: !!id, queryKey: [QueryType.NodeExecutionEnhanced, id], queryFn: async () => { // complexity: // +1 for parent node tasks // +1 for node execution list // +n= executionList.length - const isParent = isParentNode(nodeExecution); - const parentNodeID = nodeExecution.id.nodeId; + const parentExecution = cloneDeep(nodeExecution); + const isParent = isParentNode(parentExecution); + const fromUniqueParentId = parentExecution.id.nodeId; const parentScopeId = - nodeExecution.scopedId ?? nodeExecution.metadata?.specNodeId; - nodeExecution.scopedId = parentScopeId; + parentExecution.scopedId ?? parentExecution.metadata?.specNodeId; + parentExecution.scopedId = parentScopeId; + const dynamicParentNodeId = isDynamicNode(parentExecution) + ? fromUniqueParentId + : parentExecution.dynamicParentNodeId; // if the node is a parent, force refetch its children // called by NodeExecutionDynamicProvider @@ -198,7 +207,7 @@ export function makeNodeExecutionQueryEnhanced( id.executionId, { params: { - [nodeExecutionQueryParams.parentNodeId]: parentNodeID, + [nodeExecutionQueryParams.parentNodeId]: fromUniqueParentId, }, }, ).then(childExecutions => { @@ -206,17 +215,20 @@ export function makeNodeExecutionQueryEnhanced( const scopedId = e.metadata?.specNodeId ? retriesToZero(e?.metadata?.specNodeId) : retriesToZero(e?.id?.nodeId); - e['scopedId'] = `${parentScopeId}-0-${scopedId}`; - e['fromUniqueParentId'] = parentNodeID; - return e; + return { + ...e, + scopedId: `${parentScopeId}-0-${scopedId}`, + fromUniqueParentId, + ...(dynamicParentNodeId ? { dynamicParentNodeId } : {}), + }; }); return children; }) : () => Promise.resolve([]); const parentNodeAndTaskExecutions = await Promise.all([ - getTaskExecutions(nodeExecution, queryClient), + getTaskExecutions(queryClient, parentExecution), parentNodeExecutions(), ]).then(([parent, children]) => { // strip closure and metadata to avoid overwriting data from queries that handle status updates diff --git a/packages/console/src/components/Executions/types.ts b/packages/console/src/components/Executions/types.ts index fa61b44f9..14d5f3561 100644 --- a/packages/console/src/components/Executions/types.ts +++ b/packages/console/src/components/Executions/types.ts @@ -54,6 +54,7 @@ export interface NodeExecutionDetails { displayId?: string; displayName?: string; displayType: string; + scopedId?: string; taskTemplate?: TaskTemplate; } diff --git a/packages/console/src/components/Executions/utils.ts b/packages/console/src/components/Executions/utils.ts index 7282377b0..0d49fac16 100644 --- a/packages/console/src/components/Executions/utils.ts +++ b/packages/console/src/components/Executions/utils.ts @@ -28,11 +28,7 @@ import { taskTypeToNodeExecutionDisplayType, workflowExecutionPhaseConstants, } from './constants'; -import { - NodeExecutionsById, - SetCurrentNodeExecutionsById, - WorkflowNodeExecution, -} from './contexts'; +import { NodeExecutionsById, SetCurrentNodeExecutionsById } from './contexts'; import { isChildGroupsFetched } from './ExecutionDetails/utils'; import { fetchChildNodeExecutionGroups } from './nodeExecutionQueries'; import { diff --git a/packages/console/src/components/Launch/LaunchForm/utils.ts b/packages/console/src/components/Launch/LaunchForm/utils.ts index fb672b958..6e106215e 100644 --- a/packages/console/src/components/Launch/LaunchForm/utils.ts +++ b/packages/console/src/components/Launch/LaunchForm/utils.ts @@ -20,7 +20,6 @@ import { InputTypeDefinition, ParsedInput, SearchableVersion, - InputValue, } from './types'; /** Creates a unique cache key for an input based on its name and type. diff --git a/packages/console/src/components/Tables/PaginatedDataList.tsx b/packages/console/src/components/Tables/PaginatedDataList.tsx index 50bd00368..92a4b9ed0 100644 --- a/packages/console/src/components/Tables/PaginatedDataList.tsx +++ b/packages/console/src/components/Tables/PaginatedDataList.tsx @@ -18,7 +18,7 @@ import { workflowVersionsTableColumnWidths } from '../Executions/Tables/constant type Order = 'asc' | 'desc'; -interface PaginatedDataListHeaderProps { +interface PaginatedDataListHeaderProps { classes: ReturnType; columns: ColumnDefinition[]; onRequestSort: (event: React.MouseEvent, property: string) => void; @@ -83,8 +83,8 @@ const useStyles = makeStyles((theme: Theme) => * @param props * @constructor */ -const PaginatedDataListHeader = ( - props: PropsWithChildren>, +const PaginatedDataListHeader = ( + props: PropsWithChildren, ) => { const { classes, order, orderBy, onRequestSort, columns, showRadioButton } = props; diff --git a/packages/console/src/components/flytegraph/ReactFlow/customNodeComponents.tsx b/packages/console/src/components/flytegraph/ReactFlow/customNodeComponents.tsx index 7e0b38be3..9688b7c84 100644 --- a/packages/console/src/components/flytegraph/ReactFlow/customNodeComponents.tsx +++ b/packages/console/src/components/flytegraph/ReactFlow/customNodeComponents.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useMemo } from 'react'; +import React, { useState, useEffect } from 'react'; import { Handle, Position, ReactFlowProps } from 'react-flow-renderer'; import { dTypes } from 'models/Graph/types'; import { NodeExecutionPhase, TaskExecutionPhase } from 'models/Execution/enums'; diff --git a/packages/console/src/components/hooks/useNodeExecution.ts b/packages/console/src/components/hooks/useNodeExecution.ts index 21e33470e..1db7d9081 100644 --- a/packages/console/src/components/hooks/useNodeExecution.ts +++ b/packages/console/src/components/hooks/useNodeExecution.ts @@ -32,7 +32,7 @@ export function useNodeExecutionData( debugName: 'NodeExecutionData', defaultValue: {} as ExecutionData, doFetch: id => - getNodeExecutionData(id).catch(e => { + getNodeExecutionData(id).catch(() => { return {} as ExecutionData; }), }, diff --git a/packages/console/src/models/Common/types.ts b/packages/console/src/models/Common/types.ts index 153189bad..e0c3a1823 100644 --- a/packages/console/src/models/Common/types.ts +++ b/packages/console/src/models/Common/types.ts @@ -95,8 +95,11 @@ export interface BlobLiteral extends Core.ILiteral { export type LiteralCollection = RequiredNonNullable; +/* eslint-disable @typescript-eslint/no-redeclare */ export type LiteralMap = RequiredNonNullable; export const LiteralMap = Core.LiteralMap; +/* eslint-enable @typescript-eslint/no-redeclare */ + export interface LiteralMapBlob extends Admin.ILiteralMapBlob { values: LiteralMap; } diff --git a/packages/console/src/models/Execution/__mocks__/mockNodeExecutionsData.ts b/packages/console/src/models/Execution/__mocks__/mockNodeExecutionsData.ts index 095502540..eba1997d9 100644 --- a/packages/console/src/models/Execution/__mocks__/mockNodeExecutionsData.ts +++ b/packages/console/src/models/Execution/__mocks__/mockNodeExecutionsData.ts @@ -20,6 +20,7 @@ export const mockNodeExecutionResponse: Admin.INodeExecution = { duration: millisecondsToDuration(1000 * 60 * 60 * 1.251), outputUri: 's3://path/to/my/outputs.pb', }, + taskExecutions: [], }; export const mockExecution = mockNodeExecutionResponse as NodeExecution; diff --git a/packages/console/src/models/Execution/types.ts b/packages/console/src/models/Execution/types.ts index 28b650a01..3ebaaca9d 100644 --- a/packages/console/src/models/Execution/types.ts +++ b/packages/console/src/models/Execution/types.ts @@ -1,10 +1,4 @@ -import { - Admin, - Core, - Event, - Protobuf, - Service, -} from '@flyteorg/flyteidl-types'; +import { Admin, Core, Event, Protobuf } from '@flyteorg/flyteidl-types'; import { Identifier, LiteralMap, @@ -100,6 +94,7 @@ export interface NodeExecution extends Admin.INodeExecution { metadata?: NodeExecutionMetadata; scopedId?: string; fromUniqueParentId?: string; + dynamicParentNodeId?: string; } export interface NodeExecutionClosure extends Admin.INodeExecutionClosure { @@ -130,6 +125,7 @@ export interface TaskExecution extends Admin.ITaskExecution { inputUri: string; isParent?: boolean; closure: TaskExecutionClosure; + dynamicParentNodeId?: string; } export interface TaskExecutionClosure extends Admin.ITaskExecutionClosure { createdAt: Protobuf.ITimestamp; diff --git a/website/package.json b/website/package.json index fda9a70d9..a688fa1fc 100644 --- a/website/package.json +++ b/website/package.json @@ -37,7 +37,7 @@ }, "dependencies": { "@flyteorg/common": "^0.0.4", - "@flyteorg/console": "^0.0.26", + "@flyteorg/console": "^0.0.27", "long": "^4.0.0", "protobufjs": "~6.11.3", "react-ga4": "^1.4.1", diff --git a/yarn.lock b/yarn.lock index 910e383ff..d4740dbe4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2020,7 +2020,7 @@ __metadata: resolution: "@flyteconsole/client-app@workspace:website" dependencies: "@flyteorg/common": ^0.0.4 - "@flyteorg/console": ^0.0.26 + "@flyteorg/console": ^0.0.27 "@types/long": ^3.0.32 long: ^4.0.0 protobufjs: ~6.11.3 @@ -2059,7 +2059,7 @@ __metadata: languageName: unknown linkType: soft -"@flyteorg/console@^0.0.26, @flyteorg/console@workspace:packages/console": +"@flyteorg/console@^0.0.27, @flyteorg/console@workspace:packages/console": version: 0.0.0-use.local resolution: "@flyteorg/console@workspace:packages/console" dependencies: