From e95faa89bf53c9b20ad34a89c7aededabe8e724e Mon Sep 17 00:00:00 2001 From: Carina Ursu Date: Thu, 4 May 2023 18:05:19 -0700 Subject: [PATCH 01/47] 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: From e28a142e966cf689fd8b51ba9049763626dc2eba Mon Sep 17 00:00:00 2001 From: Kevin Su Date: Mon, 8 May 2023 09:06:18 -0700 Subject: [PATCH 02/47] Add support fetching description entity (#735) * Add Description entity Signed-off-by: Kevin Su * Add Description entity Signed-off-by: Kevin Su * nit Signed-off-by: Kevin Su * nit Signed-off-by: Kevin Su * nit Signed-off-by: Kevin Su * address comment Signed-off-by: Kevin Su * test ci Signed-off-by: Kevin Su * test ci Signed-off-by: Kevin Su * test ci Signed-off-by: Kevin Su * test ci Signed-off-by: Kevin Su * test ci Signed-off-by: Kevin Su --------- Signed-off-by: Kevin Su Co-authored-by: Jason Porter <84735036+jsonporter@users.noreply.github.com> --- .../components/Entities/EntityDescription.tsx | 45 +++++++++++++++---- .../src/components/Entities/strings.ts | 2 + .../components/Task/SimpleTaskInterface.tsx | 7 ++- .../Workflow/SearchableWorkflowNameList.tsx | 10 +++++ .../Workflow/useWorkflowInfoItem.ts | 4 +- .../__stories__/WorkflowGraph.stories.tsx | 2 + .../console/src/components/data/apiContext.ts | 5 ++- .../src/components/hooks/useDescription.ts | 31 +++++++++++++ .../console/src/models/Common/constants.ts | 1 + packages/console/src/models/Common/utils.ts | 14 ++++++ .../src/models/DescriptionEntity/api.ts | 30 +++++++++++++ .../src/models/DescriptionEntity/types.ts | 28 ++++++++++++ .../src/models/DescriptionEntity/utils.ts | 19 ++++++++ packages/console/src/models/Task/types.ts | 2 + packages/console/src/models/Workflow/types.ts | 3 +- 15 files changed, 191 insertions(+), 12 deletions(-) create mode 100644 packages/console/src/components/hooks/useDescription.ts create mode 100644 packages/console/src/models/DescriptionEntity/api.ts create mode 100644 packages/console/src/models/DescriptionEntity/types.ts create mode 100644 packages/console/src/models/DescriptionEntity/utils.ts diff --git a/packages/console/src/components/Entities/EntityDescription.tsx b/packages/console/src/components/Entities/EntityDescription.tsx index d8d0253ac..a76734f7a 100644 --- a/packages/console/src/components/Entities/EntityDescription.tsx +++ b/packages/console/src/components/Entities/EntityDescription.tsx @@ -3,10 +3,8 @@ import { makeStyles, Theme } from '@material-ui/core/styles'; import classnames from 'classnames'; import { useCommonStyles } from 'components/common/styles'; import { WaitForData } from 'components/common/WaitForData'; -import { useNamedEntity } from 'components/hooks/useNamedEntity'; import { IdentifierScope, - NamedEntityMetadata, ResourceIdentifier, Variable, } from 'models/Common/types'; @@ -20,7 +18,8 @@ import { TaskClosure } from 'models/Task/types'; import { executionFilterGenerator } from './generators'; import { Row } from './Row'; import t, { patternKey } from './strings'; -import { entityStrings, entitySections } from './constants'; +import {entityStrings, entitySections} from './constants'; +import {useDescriptionEntityList} from "../hooks/useDescription"; const Skeleton = reactLoadingSkeleton; @@ -96,9 +95,30 @@ export const EntityDescription: React.FC<{ }> = ({ id }) => { const commonStyles = useCommonStyles(); const styles = useStyles(); - const namedEntity = useNamedEntity(id); - const { metadata = {} as NamedEntityMetadata } = namedEntity.value; - const hasDescription = !!metadata.description; + + const { resourceType } = id; + const sort = { + key: executionSortFields.createdAt, + direction: SortDirection.DESCENDING, + }; + + const baseFilters = React.useMemo( + () => executionFilterGenerator[resourceType](id), + [id, resourceType], + ); + + const descriptionEntities = useDescriptionEntityList( + { ...id, version: '' }, + { + sort, + filter: baseFilters, + limit: 1, + }, + ); + + const descriptionEntity = descriptionEntities?.value?.[0] + const hasDescription = descriptionEntity?.longDescription.value.length !== 0; + const hasLink = !!descriptionEntity?.sourceCode?.link; const sections = entitySections[id.resourceType]; return ( @@ -113,7 +133,7 @@ export const EntityDescription: React.FC<{ className={styles.description} > @@ -124,12 +144,21 @@ export const EntityDescription: React.FC<{ })} > {hasDescription - ? metadata.description + ? descriptionEntity?.longDescription?.value : t( patternKey('noDescription', entityStrings[id.resourceType]), )} + {hasLink && ( + + {hasLink + ?{descriptionEntity?.sourceCode?.link} + : t( + patternKey('noGithubLink', entityStrings[id.resourceType]), + )} + + )} {sections?.descriptionInputsAndOutputs && } diff --git a/packages/console/src/components/Entities/strings.ts b/packages/console/src/components/Entities/strings.ts index 3ceeb18a8..9c7e676be 100644 --- a/packages/console/src/components/Entities/strings.ts +++ b/packages/console/src/components/Entities/strings.ts @@ -10,6 +10,7 @@ const str = { noDescription_task: 'This task has no description.', noSchedules_workflow: 'This workflow has no schedules.', noSchedules_task: 'This task has no schedules.', + noDescription: 'No description found.', allExecutionsChartTitle_workflow: 'All Executions in the Workflow', allExecutionsChartTitle_task: 'All Executions in the Task', allExecutionsChartTitle_launch_plan: 'All Executions Using Launch Plan', @@ -22,6 +23,7 @@ const str = { details_task: 'Task Details', inputsFieldName: 'Inputs', outputsFieldName: 'Outputs', + githubLink: 'Git', imageFieldName: 'Image', envVarsFieldName: 'Env Vars', commandsFieldName: 'Commands', diff --git a/packages/console/src/components/Task/SimpleTaskInterface.tsx b/packages/console/src/components/Task/SimpleTaskInterface.tsx index f58182616..28416e1d9 100644 --- a/packages/console/src/components/Task/SimpleTaskInterface.tsx +++ b/packages/console/src/components/Task/SimpleTaskInterface.tsx @@ -66,10 +66,11 @@ const VariablesList: React.FC<{ variables: Record }> = ({ export const SimpleTaskInterface: React.FC<{ task: Task }> = ({ task }) => { const { inputs = emptyVariables, outputs = emptyVariables } = task.closure.compiledTask.template.interface || {}; + const description = task.shortDescription || "No description found."; return (
= ({ task }) => { name: 'outputs', content: , }, + { + name: 'description', + content: {description} + } ]} />
diff --git a/packages/console/src/components/Workflow/SearchableWorkflowNameList.tsx b/packages/console/src/components/Workflow/SearchableWorkflowNameList.tsx index 187b785ee..3215c887a 100644 --- a/packages/console/src/components/Workflow/SearchableWorkflowNameList.tsx +++ b/packages/console/src/components/Workflow/SearchableWorkflowNameList.tsx @@ -331,6 +331,16 @@ const SearchableWorkflowNameItem: React.FC = )} +
+
Description
+
+ {isLoading ? ( + + ) : ( + workflow?.description ?? No description found. + )} +
+
0 ? parsedOutputs.join(', ') : undefined; - return { id, inputs, outputs }; + const description = workflow?.shortDescription && workflow?.shortDescription.length > 0 ? workflow.shortDescription : undefined + return { id, inputs, outputs, description}; }, { staleTime: 1000 * 60 * 5, diff --git a/packages/console/src/components/WorkflowGraph/__stories__/WorkflowGraph.stories.tsx b/packages/console/src/components/WorkflowGraph/__stories__/WorkflowGraph.stories.tsx index 6c0295983..319983255 100644 --- a/packages/console/src/components/WorkflowGraph/__stories__/WorkflowGraph.stories.tsx +++ b/packages/console/src/components/WorkflowGraph/__stories__/WorkflowGraph.stories.tsx @@ -10,6 +10,7 @@ import { WorkflowGraph } from '../WorkflowGraph'; import graphData from './rich.json'; const graphDataClosure = graphData as unknown as CompiledWorkflowClosure; +const shortDescription = "" const workflow: Workflow = { closure: { compiledWorkflow: graphDataClosure }, @@ -19,6 +20,7 @@ const workflow: Workflow = { name: 'test', version: '1', }, + shortDescription: shortDescription, }; const onNodeSelectionChanged = action('nodeSelected'); diff --git a/packages/console/src/components/data/apiContext.ts b/packages/console/src/components/data/apiContext.ts index a9f41eb91..6f2295ab3 100644 --- a/packages/console/src/components/data/apiContext.ts +++ b/packages/console/src/components/data/apiContext.ts @@ -4,6 +4,7 @@ import * as LaunchAPI from 'models/Launch/api'; import * as ProjectAPI from 'models/Project/api'; import * as TaskAPI from 'models/Task/api'; import * as WorkflowAPI from 'models/Workflow/api'; +import * as DescriptionEntityAPI from 'models/DescriptionEntity/api' import * as React from 'react'; type APIFunctions = typeof CommonAPI & @@ -11,7 +12,8 @@ type APIFunctions = typeof CommonAPI & typeof LaunchAPI & typeof ProjectAPI & typeof TaskAPI & - typeof WorkflowAPI; + typeof WorkflowAPI & + typeof DescriptionEntityAPI; export interface APIContextValue extends APIFunctions { // use API functions only, for now @@ -24,6 +26,7 @@ export const defaultAPIContextValue: APIContextValue = { ...ProjectAPI, ...TaskAPI, ...WorkflowAPI, + ...DescriptionEntityAPI }; /** Exposes all of the model layer api functions for use by data fetching diff --git a/packages/console/src/components/hooks/useDescription.ts b/packages/console/src/components/hooks/useDescription.ts new file mode 100644 index 000000000..cc74e737d --- /dev/null +++ b/packages/console/src/components/hooks/useDescription.ts @@ -0,0 +1,31 @@ +import {Identifier, IdentifierScope, RequestConfig} from "../../models"; +import {useFetchableData} from "./useFetchableData"; +import {getDescriptionEntity, listDescriptionEntities} from "../../models/DescriptionEntity/api"; +import {DescriptionEntity} from "../../models/DescriptionEntity/types"; +import {FetchableData} from "./types"; +import {useAPIContext} from "../data/apiContext"; +import {usePagination} from "./usePagination"; + + +/** A hook for fetching a description entity */ +export function useDescriptionEntity(id: Identifier): FetchableData { + const { getDescriptionEntity } = useAPIContext(); + return useFetchableData( + { + useCache: true, + debugName: 'DescriptionEntity', + defaultValue: {} as DescriptionEntity, + doFetch: async descriptionEntityId => (await getDescriptionEntity(descriptionEntityId)) as DescriptionEntity, + }, + id, + ); +} + +/** A hook for fetching a paginated list of description entities */ +export function useDescriptionEntityList(scope: IdentifierScope, config: RequestConfig) { + const { listDescriptionEntities } = useAPIContext(); + return usePagination( + { ...config, cacheItems: true, fetchArg: scope }, + listDescriptionEntities, + ); +} diff --git a/packages/console/src/models/Common/constants.ts b/packages/console/src/models/Common/constants.ts index 198e7a59b..df7b72725 100644 --- a/packages/console/src/models/Common/constants.ts +++ b/packages/console/src/models/Common/constants.ts @@ -13,6 +13,7 @@ export const endpointPrefixes = { recoverExecution: '/executions/recover', setSignal: '/signals', task: '/tasks', + descriptionEntity: 'description_entities', taskExecution: '/task_executions', taskExecutionChildren: '/children/task_executions', workflow: '/workflows', diff --git a/packages/console/src/models/Common/utils.ts b/packages/console/src/models/Common/utils.ts index 9c005bfd2..d435009e7 100644 --- a/packages/console/src/models/Common/utils.ts +++ b/packages/console/src/models/Common/utils.ts @@ -50,3 +50,17 @@ export function makeNamedEntityPath({ name, ]).join('/'); } + +export function makeDescriptionEntityPath( + prefix: string, + { resourceType, project, domain, name, version }: Partial, +) { + const path = takeWhile([ + resourceType, + project, + domain, + name, + decodeURIComponent(version || ''), + ]).join('/'); + return `${prefix}/${path}`; +} diff --git a/packages/console/src/models/DescriptionEntity/api.ts b/packages/console/src/models/DescriptionEntity/api.ts new file mode 100644 index 000000000..115753e71 --- /dev/null +++ b/packages/console/src/models/DescriptionEntity/api.ts @@ -0,0 +1,30 @@ +import { Admin } from '@flyteorg/flyteidl-types'; +import { + getAdminEntity, +} from 'models/AdminEntity/AdminEntity'; +import { defaultPaginationConfig } from 'models/AdminEntity/constants'; +import { RequestConfig } from 'models/AdminEntity/types'; +import { Identifier, IdentifierScope } from 'models/Common/types'; +import { DescriptionEntity } from './types'; +import { makeDescriptionPath, descriptionEntityListTransformer } from './utils'; + +/** Fetches a list of `DescriptionEntity` records matching the provided `scope` */ +export const listDescriptionEntities = (scope: IdentifierScope, config?: RequestConfig) => + getAdminEntity( + { + path: makeDescriptionPath(scope), + messageType: Admin.DescriptionEntityList, + transform: descriptionEntityListTransformer, + }, + { ...defaultPaginationConfig, ...config }, + ); + +/** Fetches an individual `DescriptionEntity` record */ +export const getDescriptionEntity = (id: Identifier, config?: RequestConfig) => + getAdminEntity( + { + path: makeDescriptionPath(id), + messageType: Admin.DescriptionEntity, + }, + config, + ); diff --git a/packages/console/src/models/DescriptionEntity/types.ts b/packages/console/src/models/DescriptionEntity/types.ts new file mode 100644 index 000000000..14f013ec0 --- /dev/null +++ b/packages/console/src/models/DescriptionEntity/types.ts @@ -0,0 +1,28 @@ +import { Admin } from '@flyteorg/flyteidl-types'; +import { Identifier } from 'models/Common/types'; + + +/** Optional link to source code used to define this entity */ +export interface SourceCode extends Admin.ISourceCode { + link?: string +} + +/** Full user description with formatting preserved */ +export interface LongDescription extends Admin.IDescription { + value: string; + uri: string; + format: Admin.DescriptionFormat + iconLink?: string +} + +/* +DescriptionEntity contains detailed description for the task/workflow. +Documentation could provide insight into the algorithms, business use case, etc +*/ +export interface DescriptionEntity extends Admin.IDescriptionEntity { + id: Identifier; + shortDescription: string; + longDescription: LongDescription + sourceCode?: SourceCode + tags?: string[] +} diff --git a/packages/console/src/models/DescriptionEntity/utils.ts b/packages/console/src/models/DescriptionEntity/utils.ts new file mode 100644 index 000000000..889a13cb0 --- /dev/null +++ b/packages/console/src/models/DescriptionEntity/utils.ts @@ -0,0 +1,19 @@ +import { Admin } from '@flyteorg/flyteidl-types'; +import { createPaginationTransformer } from 'models/AdminEntity/utils'; +import { endpointPrefixes } from 'models/Common/constants'; +import { IdentifierScope } from 'models/Common/types'; +import { makeDescriptionEntityPath } from 'models/Common/utils'; +import { DescriptionEntity } from './types'; + +/** Generate the correct path for retrieving a DescriptionEntity or list of DescriptionEntities based on the + * given scope. + */ +export function makeDescriptionPath(scope: IdentifierScope) { + return makeDescriptionEntityPath(endpointPrefixes.descriptionEntity, scope); +} + +/** Transformer to coerce an `Admin.DescriptionEntityList` into a standard shape */ +export const descriptionEntityListTransformer = createPaginationTransformer< + DescriptionEntity, + Admin.DescriptionEntityList +>('descriptionEntities'); diff --git a/packages/console/src/models/Task/types.ts b/packages/console/src/models/Task/types.ts index 47e29a675..067c42e4c 100644 --- a/packages/console/src/models/Task/types.ts +++ b/packages/console/src/models/Task/types.ts @@ -28,6 +28,7 @@ export interface TaskTemplate extends Core.ITaskTemplate { metadata?: TaskMetadata; closure?: TaskClosure; type: string; + shortDescription?: string; } /** An instance of a task which has been serialized into a `TaskClosure` */ @@ -44,4 +45,5 @@ export interface TaskClosure extends Admin.ITaskClosure { export interface Task extends Admin.ITask { id: Identifier; closure: TaskClosure; + shortDescription?: string; } diff --git a/packages/console/src/models/Workflow/types.ts b/packages/console/src/models/Workflow/types.ts index 0b880dd9d..240f1dab1 100644 --- a/packages/console/src/models/Workflow/types.ts +++ b/packages/console/src/models/Workflow/types.ts @@ -27,7 +27,7 @@ export interface CompiledWorkflowClosure extends Core.ICompiledWorkflowClosure { tasks: CompiledTask[]; } -/** A serialized representation of all inforamtion about a specific workflow +/** A serialized representation of all information about a specific workflow * version. */ export interface WorkflowClosure extends Admin.IWorkflowClosure { @@ -37,6 +37,7 @@ export interface WorkflowClosure extends Admin.IWorkflowClosure { export interface Workflow extends Admin.IWorkflow { closure?: WorkflowClosure; id: Identifier; + shortDescription?: string; } export type WorkflowId = Identifier; From 5caa0abc12acad8e12acd58f039c97ff0cd9e3e7 Mon Sep 17 00:00:00 2001 From: Carina Ursu Date: Wed, 10 May 2023 14:07:13 -0700 Subject: [PATCH 03/47] fix: content panel captures focus (#754) * fix: content panel captures focus Signed-off-by: Carina Ursu * chore: lint Signed-off-by: Carina Ursu --------- Signed-off-by: Carina Ursu --- packages/console/package.json | 2 +- .../components/Entities/EntityDescription.tsx | 30 ++++++++++++------- .../components/Task/SimpleTaskInterface.tsx | 6 ++-- .../Workflow/useWorkflowInfoItem.ts | 7 +++-- .../__stories__/WorkflowGraph.stories.tsx | 2 +- .../src/components/common/DetailsPanel.tsx | 3 ++ .../console/src/components/data/apiContext.ts | 4 +-- .../src/components/hooks/useDescription.ts | 26 +++++++++------- .../src/models/DescriptionEntity/api.ts | 9 +++--- .../src/models/DescriptionEntity/types.ts | 13 ++++---- website/package.json | 2 +- yarn.lock | 4 +-- 12 files changed, 63 insertions(+), 45 deletions(-) diff --git a/packages/console/package.json b/packages/console/package.json index 8980ff2c3..c90846828 100644 --- a/packages/console/package.json +++ b/packages/console/package.json @@ -1,6 +1,6 @@ { "name": "@flyteorg/console", - "version": "0.0.27", + "version": "0.0.28", "description": "Flyteconsole main app module", "main": "./dist/index.js", "module": "./lib/index.js", diff --git a/packages/console/src/components/Entities/EntityDescription.tsx b/packages/console/src/components/Entities/EntityDescription.tsx index a76734f7a..245b2aaff 100644 --- a/packages/console/src/components/Entities/EntityDescription.tsx +++ b/packages/console/src/components/Entities/EntityDescription.tsx @@ -18,8 +18,8 @@ import { TaskClosure } from 'models/Task/types'; import { executionFilterGenerator } from './generators'; import { Row } from './Row'; import t, { patternKey } from './strings'; -import {entityStrings, entitySections} from './constants'; -import {useDescriptionEntityList} from "../hooks/useDescription"; +import { entityStrings, entitySections } from './constants'; +import { useDescriptionEntityList } from '../hooks/useDescription'; const Skeleton = reactLoadingSkeleton; @@ -116,7 +116,7 @@ export const EntityDescription: React.FC<{ }, ); - const descriptionEntity = descriptionEntities?.value?.[0] + const descriptionEntity = descriptionEntities?.value?.[0]; const hasDescription = descriptionEntity?.longDescription.value.length !== 0; const hasLink = !!descriptionEntity?.sourceCode?.link; const sections = entitySections[id.resourceType]; @@ -150,15 +150,23 @@ export const EntityDescription: React.FC<{ )} - {hasLink && ( - - {hasLink - ?{descriptionEntity?.sourceCode?.link} - : t( - patternKey('noGithubLink', entityStrings[id.resourceType]), + {hasLink && ( + + + {hasLink ? ( + + {descriptionEntity?.sourceCode?.link} + + ) : ( + t(patternKey('noGithubLink', entityStrings[id.resourceType])) )} - - )} + + + )} {sections?.descriptionInputsAndOutputs && } diff --git a/packages/console/src/components/Task/SimpleTaskInterface.tsx b/packages/console/src/components/Task/SimpleTaskInterface.tsx index 28416e1d9..aa2d3e6a7 100644 --- a/packages/console/src/components/Task/SimpleTaskInterface.tsx +++ b/packages/console/src/components/Task/SimpleTaskInterface.tsx @@ -66,7 +66,7 @@ const VariablesList: React.FC<{ variables: Record }> = ({ export const SimpleTaskInterface: React.FC<{ task: Task }> = ({ task }) => { const { inputs = emptyVariables, outputs = emptyVariables } = task.closure.compiledTask.template.interface || {}; - const description = task.shortDescription || "No description found."; + const description = task.shortDescription || 'No description found.'; return (
= ({ task }) => { }, { name: 'description', - content: {description} - } + content: {description}, + }, ]} />
diff --git a/packages/console/src/components/Workflow/useWorkflowInfoItem.ts b/packages/console/src/components/Workflow/useWorkflowInfoItem.ts index 57de75719..0f6e03df4 100644 --- a/packages/console/src/components/Workflow/useWorkflowInfoItem.ts +++ b/packages/console/src/components/Workflow/useWorkflowInfoItem.ts @@ -117,8 +117,11 @@ export const useWorkflowInfoItem = ({ const parsedOutputs = getOutputsForWorkflow(launchPlan); const outputs = parsedOutputs.length > 0 ? parsedOutputs.join(', ') : undefined; - const description = workflow?.shortDescription && workflow?.shortDescription.length > 0 ? workflow.shortDescription : undefined - return { id, inputs, outputs, description}; + const description = + workflow?.shortDescription && workflow?.shortDescription.length > 0 + ? workflow.shortDescription + : undefined; + return { id, inputs, outputs, description }; }, { staleTime: 1000 * 60 * 5, diff --git a/packages/console/src/components/WorkflowGraph/__stories__/WorkflowGraph.stories.tsx b/packages/console/src/components/WorkflowGraph/__stories__/WorkflowGraph.stories.tsx index 319983255..180247553 100644 --- a/packages/console/src/components/WorkflowGraph/__stories__/WorkflowGraph.stories.tsx +++ b/packages/console/src/components/WorkflowGraph/__stories__/WorkflowGraph.stories.tsx @@ -10,7 +10,7 @@ import { WorkflowGraph } from '../WorkflowGraph'; import graphData from './rich.json'; const graphDataClosure = graphData as unknown as CompiledWorkflowClosure; -const shortDescription = "" +const shortDescription = ''; const workflow: Workflow = { closure: { compiledWorkflow: graphDataClosure }, diff --git a/packages/console/src/components/common/DetailsPanel.tsx b/packages/console/src/components/common/DetailsPanel.tsx index 81d15e17f..398c9284b 100644 --- a/packages/console/src/components/common/DetailsPanel.tsx +++ b/packages/console/src/components/common/DetailsPanel.tsx @@ -43,6 +43,9 @@ export const DetailsPanel: React.FC = ({ ModalProps={{ className: styles.modal, hideBackdrop: true, + // This is needed to prevent the modal from stealing focus + // from other modals in the app + disableEnforceFocus: true, // Modal uses inline styling for the zIndex, so we have to // override it style: { zIndex: theme.zIndex.appBar - 2 }, diff --git a/packages/console/src/components/data/apiContext.ts b/packages/console/src/components/data/apiContext.ts index 6f2295ab3..c9a070a81 100644 --- a/packages/console/src/components/data/apiContext.ts +++ b/packages/console/src/components/data/apiContext.ts @@ -4,7 +4,7 @@ import * as LaunchAPI from 'models/Launch/api'; import * as ProjectAPI from 'models/Project/api'; import * as TaskAPI from 'models/Task/api'; import * as WorkflowAPI from 'models/Workflow/api'; -import * as DescriptionEntityAPI from 'models/DescriptionEntity/api' +import * as DescriptionEntityAPI from 'models/DescriptionEntity/api'; import * as React from 'react'; type APIFunctions = typeof CommonAPI & @@ -26,7 +26,7 @@ export const defaultAPIContextValue: APIContextValue = { ...ProjectAPI, ...TaskAPI, ...WorkflowAPI, - ...DescriptionEntityAPI + ...DescriptionEntityAPI, }; /** Exposes all of the model layer api functions for use by data fetching diff --git a/packages/console/src/components/hooks/useDescription.ts b/packages/console/src/components/hooks/useDescription.ts index cc74e737d..b23028c63 100644 --- a/packages/console/src/components/hooks/useDescription.ts +++ b/packages/console/src/components/hooks/useDescription.ts @@ -1,28 +1,32 @@ -import {Identifier, IdentifierScope, RequestConfig} from "../../models"; -import {useFetchableData} from "./useFetchableData"; -import {getDescriptionEntity, listDescriptionEntities} from "../../models/DescriptionEntity/api"; -import {DescriptionEntity} from "../../models/DescriptionEntity/types"; -import {FetchableData} from "./types"; -import {useAPIContext} from "../data/apiContext"; -import {usePagination} from "./usePagination"; - +import { Identifier, IdentifierScope, RequestConfig } from '../../models'; +import { useFetchableData } from './useFetchableData'; +import { DescriptionEntity } from '../../models/DescriptionEntity/types'; +import { FetchableData } from './types'; +import { useAPIContext } from '../data/apiContext'; +import { usePagination } from './usePagination'; /** A hook for fetching a description entity */ -export function useDescriptionEntity(id: Identifier): FetchableData { +export function useDescriptionEntity( + id: Identifier, +): FetchableData { const { getDescriptionEntity } = useAPIContext(); return useFetchableData( { useCache: true, debugName: 'DescriptionEntity', defaultValue: {} as DescriptionEntity, - doFetch: async descriptionEntityId => (await getDescriptionEntity(descriptionEntityId)) as DescriptionEntity, + doFetch: async descriptionEntityId => + (await getDescriptionEntity(descriptionEntityId)) as DescriptionEntity, }, id, ); } /** A hook for fetching a paginated list of description entities */ -export function useDescriptionEntityList(scope: IdentifierScope, config: RequestConfig) { +export function useDescriptionEntityList( + scope: IdentifierScope, + config: RequestConfig, +) { const { listDescriptionEntities } = useAPIContext(); return usePagination( { ...config, cacheItems: true, fetchArg: scope }, diff --git a/packages/console/src/models/DescriptionEntity/api.ts b/packages/console/src/models/DescriptionEntity/api.ts index 115753e71..23c3d1400 100644 --- a/packages/console/src/models/DescriptionEntity/api.ts +++ b/packages/console/src/models/DescriptionEntity/api.ts @@ -1,7 +1,5 @@ import { Admin } from '@flyteorg/flyteidl-types'; -import { - getAdminEntity, -} from 'models/AdminEntity/AdminEntity'; +import { getAdminEntity } from 'models/AdminEntity/AdminEntity'; import { defaultPaginationConfig } from 'models/AdminEntity/constants'; import { RequestConfig } from 'models/AdminEntity/types'; import { Identifier, IdentifierScope } from 'models/Common/types'; @@ -9,7 +7,10 @@ import { DescriptionEntity } from './types'; import { makeDescriptionPath, descriptionEntityListTransformer } from './utils'; /** Fetches a list of `DescriptionEntity` records matching the provided `scope` */ -export const listDescriptionEntities = (scope: IdentifierScope, config?: RequestConfig) => +export const listDescriptionEntities = ( + scope: IdentifierScope, + config?: RequestConfig, +) => getAdminEntity( { path: makeDescriptionPath(scope), diff --git a/packages/console/src/models/DescriptionEntity/types.ts b/packages/console/src/models/DescriptionEntity/types.ts index 14f013ec0..62a09a86c 100644 --- a/packages/console/src/models/DescriptionEntity/types.ts +++ b/packages/console/src/models/DescriptionEntity/types.ts @@ -1,18 +1,17 @@ import { Admin } from '@flyteorg/flyteidl-types'; import { Identifier } from 'models/Common/types'; - /** Optional link to source code used to define this entity */ export interface SourceCode extends Admin.ISourceCode { - link?: string + link?: string; } /** Full user description with formatting preserved */ export interface LongDescription extends Admin.IDescription { value: string; uri: string; - format: Admin.DescriptionFormat - iconLink?: string + format: Admin.DescriptionFormat; + iconLink?: string; } /* @@ -22,7 +21,7 @@ Documentation could provide insight into the algorithms, business use case, etc export interface DescriptionEntity extends Admin.IDescriptionEntity { id: Identifier; shortDescription: string; - longDescription: LongDescription - sourceCode?: SourceCode - tags?: string[] + longDescription: LongDescription; + sourceCode?: SourceCode; + tags?: string[]; } diff --git a/website/package.json b/website/package.json index a688fa1fc..71c65a075 100644 --- a/website/package.json +++ b/website/package.json @@ -37,7 +37,7 @@ }, "dependencies": { "@flyteorg/common": "^0.0.4", - "@flyteorg/console": "^0.0.27", + "@flyteorg/console": "^0.0.28", "long": "^4.0.0", "protobufjs": "~6.11.3", "react-ga4": "^1.4.1", diff --git a/yarn.lock b/yarn.lock index d4740dbe4..1359e8180 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.27 + "@flyteorg/console": ^0.0.28 "@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.27, @flyteorg/console@workspace:packages/console": +"@flyteorg/console@^0.0.28, @flyteorg/console@workspace:packages/console": version: 0.0.0-use.local resolution: "@flyteorg/console@workspace:packages/console" dependencies: From 29babaa4d9a814f130b055da8bdc2ebdd101a667 Mon Sep 17 00:00:00 2001 From: 4nalog <88684372+4nalog@users.noreply.github.com> Date: Wed, 10 May 2023 17:39:19 -0400 Subject: [PATCH 04/47] feat: runtime metrics UI (#747) * feat: runtime metrics * feat: add mock data * fix: chart distance spacing breaking * chore: use traversal logic to get operation id from nodes * chore: minor fixes Signed-off-by: Soham <4nalog@protonmail.com> * fix: phase color * chore: threshold on milliseconds * chore: add borders * feat: runtime metrics * chore: minor fixes * chore: remove tooltip datasets, sanitize tooltip design * Added parsing function for span data Signed-off-by: Jason Porter * chore: minor fixes * chore: minor fixes * chore: minor fixes * chore: minor fixes * fix: width * chore: remove highlight, fix fonts * fixed parsing algo to include node operations Signed-off-by: Jason Porter * chore: support ms * fix: formatting * chore: cleanup * Minor fixes Signed-off-by: Jason Porter * Fixed formatting issue with displayed duration Signed-off-by: Jason Porter * chore: fix yarn.lock Signed-off-by: Carina Ursu * chore: up package version Signed-off-by: Carina Ursu * fix: add space Signed-off-by: Soham <4nalog@protonmail.com> --------- Signed-off-by: Soham <4nalog@protonmail.com> Signed-off-by: Jason Porter Signed-off-by: Carina Ursu Co-authored-by: Jason Porter Co-authored-by: Carina Ursu --- package.json | 4 +- packages/console/package.json | 2 +- .../Timeline/ExecutionTimeline.tsx | 25 +++- .../Timeline/TimelineChart/barOptions.ts | 47 +++++++- .../Timeline/TimelineChart/index.tsx | 109 ++++++++++++++++-- .../Timeline/TimelineChart/utils.ts | 102 +++++++++++++++- .../Executions/useExecutionMetrics.tsx | 37 ++++++ packages/console/src/components/data/types.ts | 1 + .../console/src/models/Common/constants.ts | 1 + packages/console/src/models/Execution/api.ts | 16 +++ .../console/src/models/Execution/utils.ts | 10 +- website/package.json | 2 +- yarn.lock | 20 +++- 13 files changed, 357 insertions(+), 19 deletions(-) create mode 100644 packages/console/src/components/Executions/useExecutionMetrics.tsx diff --git a/package.json b/package.json index 3c36ca13f..7a7e1078f 100644 --- a/package.json +++ b/package.json @@ -104,6 +104,7 @@ "react-dom": "^16.13.1", "serve-static": "^1.12.3", "source-map-loader": "^4.0.1", + "traverse": "^0.6.7", "ts-jest": "^26.3.0", "ts-loader": "^9.2.6", "ts-node": "^8.0.2", @@ -126,7 +127,8 @@ "@storybook/builder-webpack5": "^6.4.19", "@storybook/manager-webpack5": "^6.4.19", "@storybook/react": "^6.4.19", - "@storybook/testing-library": "^0.0.9" + "@storybook/testing-library": "^0.0.9", + "@types/traverse": "^0.6.32" }, "resolutions": { "@babel/cli": "~7.16.0", diff --git a/packages/console/package.json b/packages/console/package.json index c90846828..2646c5c20 100644 --- a/packages/console/package.json +++ b/packages/console/package.json @@ -1,6 +1,6 @@ { "name": "@flyteorg/console", - "version": "0.0.28", + "version": "0.0.29", "description": "Flyteconsole main app module", "main": "./dist/index.js", "module": "./lib/index.js", diff --git a/packages/console/src/components/Executions/ExecutionDetails/Timeline/ExecutionTimeline.tsx b/packages/console/src/components/Executions/ExecutionDetails/Timeline/ExecutionTimeline.tsx index c7423da39..3fd9a32d3 100644 --- a/packages/console/src/components/Executions/ExecutionDetails/Timeline/ExecutionTimeline.tsx +++ b/packages/console/src/components/Executions/ExecutionDetails/Timeline/ExecutionTimeline.tsx @@ -1,4 +1,10 @@ -import React, { createRef, useEffect, useRef, useState } from 'react'; +import React, { + createRef, + useContext, + useEffect, + useRef, + useState, +} from 'react'; import { makeStyles, Typography } from '@material-ui/core'; import { tableHeaderColor } from 'components/Theme/constants'; import { timestampToDate } from 'common/utils'; @@ -10,6 +16,8 @@ import { import { useQueryClient } from 'react-query'; import { eq, merge } from 'lodash'; import { useNodeExecutionsById } from 'components/Executions/contextProvider/NodeExecutionDetails'; +import { ExecutionContext } from 'components/Executions/contexts'; +import { useExecutionMetrics } from 'components/Executions/useExecutionMetrics'; import { convertToPlainNodes } from './helpers'; import { ChartHeader } from './ChartHeader'; import { useScaleContext } from './scaleContext'; @@ -17,6 +25,10 @@ import { TaskNames } from './TaskNames'; import { getChartDurationData } from './TimelineChart/chartData'; import { TimelineChart } from './TimelineChart'; import t from '../strings'; +import { + getExecutionMetricsOperationIds, + parseSpanData, +} from './TimelineChart/utils'; interface StyleProps { chartWidth: number; @@ -91,6 +103,8 @@ export const ExecutionTimeline: React.FC = ({ const { nodeExecutionsById, setCurrentNodeExecutionsById } = useNodeExecutionsById(); const { chartInterval: chartTimeInterval } = useScaleContext(); + const { execution } = useContext(ExecutionContext); + const executionMetricsData = useExecutionMetrics(execution.id, 10); useEffect(() => { setOriginalNodes(ogn => { @@ -178,6 +192,12 @@ export const ExecutionTimeline: React.FC = ({ setOriginalNodes([...originalNodes]); }; + const operationIds = getExecutionMetricsOperationIds( + executionMetricsData.value, + ); + + const parsedExecutionMetricsData = parseSpanData(executionMetricsData.value); + return ( <>
@@ -213,6 +233,9 @@ export const ExecutionTimeline: React.FC = ({
diff --git a/packages/console/src/components/Executions/ExecutionDetails/Timeline/TimelineChart/barOptions.ts b/packages/console/src/components/Executions/ExecutionDetails/Timeline/TimelineChart/barOptions.ts index 7ec613a58..5f3b07eec 100644 --- a/packages/console/src/components/Executions/ExecutionDetails/Timeline/TimelineChart/barOptions.ts +++ b/packages/console/src/components/Executions/ExecutionDetails/Timeline/TimelineChart/barOptions.ts @@ -1,5 +1,6 @@ import { Chart as ChartJS, registerables, Tooltip } from 'chart.js'; import ChartDataLabels from 'chartjs-plugin-datalabels'; +import { isEqual, isNil } from 'lodash'; ChartJS.register(...registerables, ChartDataLabels); @@ -11,6 +12,9 @@ Tooltip.positioners.cursor = function (_chartElements, coordinates) { export const getBarOptions = ( chartTimeIntervalSec: number, tooltipLabels: string[][], + chartRef: React.MutableRefObject, + tooltip: any, + setTooltip: any, ) => { return { animation: false as const, @@ -31,17 +35,56 @@ export const getBarOptions = ( }, tooltip: { // Setting up tooltip: https://www.chartjs.org/docs/latest/configuration/tooltip.html + enabled: false, position: 'cursor', filter: function (tooltipItem) { // no tooltip for offsets - return tooltipItem.datasetIndex === 1; + return tooltipItem.datasetIndex !== 0; }, callbacks: { label: function (context) { const index = context.dataIndex; - return tooltipLabels[index] ?? ''; + + return tooltipLabels ? [`${tooltipLabels[index]}`] : ''; + }, + labelColor: function () { + return { + fontColor: 'white', + }; }, }, + external: context => { + const tooltipModel = context.tooltip; + + if (!chartRef || !chartRef.current) { + return; + } + + if (tooltipModel.opacity === 0) { + if (tooltip.opacity !== 0) + setTooltip(prev => ({ ...prev, opacity: 0 })); + return; + } + + const position = context.chart.canvas.getBoundingClientRect(); + + const dataIndex = tooltipModel.dataPoints[0]?.dataIndex; + + if (isNil(dataIndex)) { + return; + } + + const newTooltipData = { + opacity: 1, + left: position.left + tooltipModel.caretX, + top: position.top + tooltipModel.caretY, + dataIndex: dataIndex, + }; + + if (!isEqual(tooltip, newTooltipData)) { + setTooltip(newTooltipData); + } + }, }, }, scales: { diff --git a/packages/console/src/components/Executions/ExecutionDetails/Timeline/TimelineChart/index.tsx b/packages/console/src/components/Executions/ExecutionDetails/Timeline/TimelineChart/index.tsx index 08a898af6..23b747a85 100644 --- a/packages/console/src/components/Executions/ExecutionDetails/Timeline/TimelineChart/index.tsx +++ b/packages/console/src/components/Executions/ExecutionDetails/Timeline/TimelineChart/index.tsx @@ -1,22 +1,117 @@ import * as React from 'react'; import { Bar } from 'react-chartjs-2'; +import { dNode } from 'models/Graph/types'; +import { Box, Theme, makeStyles } from '@material-ui/core'; + +import { NodeExecutionPhase } from 'models'; +import { getNodeExecutionPhaseConstants } from 'components/Executions/utils'; +import { + BarItemData, + formatSecondsToHmsFormat, + generateChartData, + getChartData, + getDuration, + parseSpanData, +} from './utils'; import { getBarOptions } from './barOptions'; -import { BarItemData, generateChartData, getChartData } from './utils'; interface TimelineChartProps { items: BarItemData[]; + nodes: dNode[]; chartTimeIntervalSec: number; + operationIds: string[]; + parsedExecutionMetricsData: ReturnType; +} + +interface StyleProps { + opacity: number; + top: number; + left: number; + phaseColor: string; } +const useStyles = makeStyles(theme => ({ + tooltipContainer: { + position: 'absolute', + background: theme.palette.grey[100], + color: theme.palette.common.black, + padding: theme.spacing(2), + borderRadius: 8, + width: 'fit-content', + maxContent: 'fit-content', + top: ({ top }) => top + 10, + left: ({ left }) => left + 10, + display: ({ opacity }) => (opacity ? 'block' : 'none'), + }, + phaseText: { + width: 'fit-content', + marginBlockEnd: theme.spacing(1), + }, + tooltipText: { + minWidth: '50px', + }, + tooltipTextContainer: { + display: 'flex', + gap: 1, + color: theme.palette.grey[700], + }, + operationIdContainer: { + textAlign: 'left', + flex: 1, + }, +})); + export const TimelineChart = (props: TimelineChartProps) => { + const [tooltip, setTooltip] = React.useState({ + opacity: 0, + top: 0, + left: 0, + dataIndex: -1, + }); + const chartRef = React.useRef(null); const phaseData = generateChartData(props.items); + const options = getBarOptions( + props.chartTimeIntervalSec, + phaseData.tooltipLabel, + chartRef, + tooltip, + setTooltip, + ) as any; + + const data = getChartData(phaseData); + const node = props.nodes[tooltip.dataIndex]; + const phase = node?.execution?.closure.phase ?? NodeExecutionPhase.UNDEFINED; + const phaseConstant = getNodeExecutionPhaseConstants(phase); + const spans = (node && props.parsedExecutionMetricsData[node.scopedId]) || []; + + const styles = useStyles({ + opacity: tooltip.opacity, + top: tooltip.top, + left: tooltip.left, + phaseColor: phaseConstant.badgeColor, + }); + return ( - + <> + + + {phase && {phaseConstant.text}} + {spans?.map(span => ( + + + {formatSecondsToHmsFormat( + Math.round( + (getDuration(span.startTime, span.endTime) / 1000) * 100, + ) / 100, + )} + + + {span.operationId} + + + ))} + + ); }; diff --git a/packages/console/src/components/Executions/ExecutionDetails/Timeline/TimelineChart/utils.ts b/packages/console/src/components/Executions/ExecutionDetails/Timeline/TimelineChart/utils.ts index 8afceb910..388b009a4 100644 --- a/packages/console/src/components/Executions/ExecutionDetails/Timeline/TimelineChart/utils.ts +++ b/packages/console/src/components/Executions/ExecutionDetails/Timeline/TimelineChart/utils.ts @@ -2,6 +2,10 @@ import { getNodeExecutionPhaseConstants } from 'components/Executions/utils'; import { primaryTextColor } from 'components/Theme/constants'; import { NodeExecutionPhase } from 'models/Execution/enums'; import t from 'components/Executions/strings'; +import { Admin, Core, Protobuf } from '@flyteorg/flyteidl-types'; +import { get, uniq } from 'lodash'; +import { timestampToDate } from 'common'; +import traverse from 'traverse'; export const CASHED_GREEN = 'rgba(74,227,174,0.25)'; // statusColors.SUCCESS (Mint20) with 25% opacity export const TRANSPARENT = 'rgba(0, 0, 0, 0)'; @@ -24,6 +28,58 @@ interface ChartDataInput { barColor: string[]; } +/** + * Recursively traverses span data and returns a map of nodeId/taskId to span data. + * Example return: + * { + * "n0": [span, span, span], + * "n1": [span, span] + * } + */ +export const parseSpanData = ( + data: Admin.WorkflowExecutionGetMetricsResponse, +) => { + const results: Record = {}; + const workflowSpans = data?.span ?? {}; + + const traverseSpanData = (rootSpan: Core.Span) => { + const spanNodeId = + rootSpan.nodeId?.nodeId || + rootSpan.taskId?.nodeExecutionId?.nodeId || + rootSpan.workflowId?.name || + ''; + + if (!results[spanNodeId]) { + results[spanNodeId] = []; + } + + if (rootSpan.spans?.length > 0) { + rootSpan.spans.forEach(span => { + /* Recurse if taskId/nodeId; else add to record */ + if (span.nodeId?.nodeId || span.taskId?.nodeExecutionId?.nodeId) { + traverseSpanData(span as Core.Span); + } else { + results[spanNodeId].push(span); + } + }); + } + }; + traverseSpanData(workflowSpans as Core.Span); + return results; +}; + +export const getOperationsFromWorkflowExecutionMetrics = ( + data: Admin.WorkflowExecutionGetMetricsResponse, +): string[] => { + const operationIds = uniq( + traverse(data) + .paths() + .filter(path => path.at(-1) === 'operationId') + .map(path => get(data, path)), + ); + + return operationIds; +}; /** * Depending on amounf of second provided shows data in * XhXmXs or XmXs or Xs format @@ -33,12 +89,23 @@ export const formatSecondsToHmsFormat = (seconds: number) => { seconds %= 3600; const minutes = Math.floor(seconds / 60); seconds = seconds % 60; + /** + * Note: + * if we're showing hours or minutes, round seconds + * if we're (only) showing seconds, round to nearest 1/100 + */ if (hours > 0) { - return `${hours}h ${minutes}m ${seconds}s`; + return `${hours}h ${minutes}m ${Math.round(seconds)}s`; } else if (minutes > 0) { - return `${minutes}m ${seconds}s`; + return `${minutes}m ${Math.round(seconds)}s`; + } else { + seconds = Math.round(seconds * 100) / 100; + return `${seconds}s`; } - return `${seconds}s`; +}; + +export const getDurationString = (element: BarItemData): string => { + return formatSecondsToHmsFormat(element.durationSec); }; /** @@ -94,6 +161,28 @@ export const generateChartData = (data: BarItemData[]): ChartDataInput => { }; }; +export const getDuration = ( + startTime: Protobuf.ITimestamp, + endTime?: Protobuf.ITimestamp, +) => { + const endTimeInMS = endTime ? timestampToDate(endTime).getTime() : Date.now(); + const duration = endTimeInMS - timestampToDate(startTime).getTime(); + return duration; +}; + +export const getExecutionMetricsOperationIds = ( + data: Admin.WorkflowExecutionGetMetricsResponse, +): string[] => { + const operationIds = uniq( + traverse(data) + .paths() + .filter(path => path.at(-1) === 'operationId') + .map(path => get(data, path)), + ); + + return operationIds; +}; + /** * Generates chart data format suitable for Chart.js Bar. Each bar consists of two data items: * |-----------|XXXXXXXXXXXXXXXX| @@ -127,6 +216,13 @@ export const getChartData = (data: ChartDataInput) => { ...defaultStyle, data: data.durations, backgroundColor: data.barColor, + borderColor: 'rgba(0, 0, 0, 0.55)', + borderWidth: { + top: 0, + left: 0, + right: 1, + bottom: 0, + }, datalabels: { // Positioning info - https://chartjs-plugin-datalabels.netlify.app/guide/positioning.html color: primaryTextColor, diff --git a/packages/console/src/components/Executions/useExecutionMetrics.tsx b/packages/console/src/components/Executions/useExecutionMetrics.tsx new file mode 100644 index 000000000..4c1cfb93c --- /dev/null +++ b/packages/console/src/components/Executions/useExecutionMetrics.tsx @@ -0,0 +1,37 @@ +import { Admin } from '@flyteorg/flyteidl-types'; +import { APIContextValue, useAPIContext } from 'components/data/apiContext'; +import { useFetchableData } from 'components/hooks/useFetchableData'; +import { WorkflowExecutionIdentifier } from 'models'; + +export const fetchExecutionMetrics = async ( + id: WorkflowExecutionIdentifier, + depth: number, + apiContext: APIContextValue, +) => { + const { getExecutionMetrics } = apiContext; + const metrics = await getExecutionMetrics(id, { + params: { + depth, + }, + }); + return metrics; +}; + +export function useExecutionMetrics( + id: WorkflowExecutionIdentifier, + depth = 0, +) { + const apiContext = useAPIContext(); + + return useFetchableData< + Admin.WorkflowExecutionGetMetricsResponse, + WorkflowExecutionIdentifier + >( + { + debugName: 'ExecutionMetrics', + defaultValue: [] as Admin.WorkflowExecutionGetMetricsResponse, + doFetch: id => fetchExecutionMetrics(id, depth, apiContext), + }, + id, + ); +} diff --git a/packages/console/src/components/data/types.ts b/packages/console/src/components/data/types.ts index d751dca1a..040328487 100644 --- a/packages/console/src/components/data/types.ts +++ b/packages/console/src/components/data/types.ts @@ -4,6 +4,7 @@ import { } from 'react-query'; export enum QueryType { + ExecutionMetrics = 'executionMetrics', NodeExecutionDetails = 'NodeExecutionDetails', DynamicWorkflowFromNodeExecution = 'DynamicWorkflowFromNodeExecution', NodeExecution = 'nodeExecution', diff --git a/packages/console/src/models/Common/constants.ts b/packages/console/src/models/Common/constants.ts index df7b72725..1216444bf 100644 --- a/packages/console/src/models/Common/constants.ts +++ b/packages/console/src/models/Common/constants.ts @@ -6,6 +6,7 @@ export const endpointPrefixes = { namedEntity: '/named_entities', nodeExecution: '/node_executions', dynamicWorkflowExecution: '/data/node_executions', + executionMetrics: '/metrics/executions', project: '/projects', projectAttributes: '/project_attributes', projectDomainAtributes: '/project_domain_attributes', diff --git a/packages/console/src/models/Execution/api.ts b/packages/console/src/models/Execution/api.ts index 9d7839905..58058c29f 100644 --- a/packages/console/src/models/Execution/api.ts +++ b/packages/console/src/models/Execution/api.ts @@ -29,6 +29,7 @@ import { } from './types'; import { executionListTransformer, + makeExecutionMetricsPath, makeExecutionPath, makeNodeExecutionListPath, makeNodeExecutionPath, @@ -427,3 +428,18 @@ export const updateExecution = ( config, ); }; + +export const getExecutionMetrics = ( + id: WorkflowExecutionIdentifier, + config?: RequestConfig, +): Promise => + getAdminEntity< + Admin.WorkflowExecutionGetMetricsResponse, + Admin.WorkflowExecutionGetMetricsResponse + >( + { + path: makeExecutionMetricsPath(id), + messageType: Admin.WorkflowExecutionGetMetricsResponse, + }, + config, + ); diff --git a/packages/console/src/models/Execution/utils.ts b/packages/console/src/models/Execution/utils.ts index 3a7980932..77aeb9c3f 100644 --- a/packages/console/src/models/Execution/utils.ts +++ b/packages/console/src/models/Execution/utils.ts @@ -1,3 +1,4 @@ +import { WorkflowExecutionIdentifier } from 'models/Execution/types'; import { Admin } from '@flyteorg/flyteidl-types'; import { createPaginationTransformer } from 'models/AdminEntity/utils'; import { endpointPrefixes } from 'models/Common/constants'; @@ -9,9 +10,16 @@ import { NodeExecutionIdentifier, TaskExecution, TaskExecutionIdentifier, - WorkflowExecutionIdentifier, } from './types'; +/** Generates the API endpoint for getting execution metrics for a given workflow */ +export const makeExecutionMetricsPath = ({ + project, + domain, + name, +}: WorkflowExecutionIdentifier) => + [endpointPrefixes.executionMetrics, project, domain, name].join('/'); + /** Generates the API endpoint for a given `WorkflowExecutionIdentifier` */ export const makeExecutionPath = ({ project, diff --git a/website/package.json b/website/package.json index 71c65a075..be189ae0d 100644 --- a/website/package.json +++ b/website/package.json @@ -37,7 +37,7 @@ }, "dependencies": { "@flyteorg/common": "^0.0.4", - "@flyteorg/console": "^0.0.28", + "@flyteorg/console": "^0.0.29", "long": "^4.0.0", "protobufjs": "~6.11.3", "react-ga4": "^1.4.1", diff --git a/yarn.lock b/yarn.lock index 1359e8180..a87818160 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.28 + "@flyteorg/console": ^0.0.29 "@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.28, @flyteorg/console@workspace:packages/console": +"@flyteorg/console@^0.0.29, @flyteorg/console@workspace:packages/console": version: 0.0.0-use.local resolution: "@flyteorg/console@workspace:packages/console" dependencies: @@ -5620,6 +5620,13 @@ __metadata: languageName: node linkType: hard +"@types/traverse@npm:^0.6.32": + version: 0.6.32 + resolution: "@types/traverse@npm:0.6.32" + checksum: 0d27f4758e1da79463c42cdd91de018095048e5c3963ad108b97044d5f43ea48b071da87062fdd4ff46407461802cc47367dd90b8f4a03a3dcf2ff94840d4909 + languageName: node + linkType: hard + "@types/uglify-js@npm:*": version: 3.17.1 resolution: "@types/uglify-js@npm:3.17.1" @@ -11663,6 +11670,7 @@ __metadata: "@types/morgan": ^1.9.4 "@types/react": ^16.14.35 "@types/react-dom": ^16.9.7 + "@types/traverse": ^0.6.32 "@typescript-eslint/eslint-plugin": ^5.48.2 "@typescript-eslint/parser": ^5.48.2 babel-loader: ^8.2.5 @@ -11695,6 +11703,7 @@ __metadata: react-dom: ^16.13.1 serve-static: ^1.12.3 source-map-loader: ^4.0.1 + traverse: ^0.6.7 ts-jest: ^26.3.0 ts-loader: ^9.2.6 ts-node: ^8.0.2 @@ -21258,6 +21267,13 @@ __metadata: languageName: node linkType: hard +"traverse@npm:^0.6.7": + version: 0.6.7 + resolution: "traverse@npm:0.6.7" + checksum: 21018085ab72f717991597e12e2b52446962ed59df591502e4d7e1a709bc0a989f7c3d451aa7d882666ad0634f1546d696c5edecda1f2fc228777df7bb529a1e + languageName: node + linkType: hard + "treeverse@npm:*, treeverse@npm:^3.0.0": version: 3.0.0 resolution: "treeverse@npm:3.0.0" From a7d7e1c2c351fc6d99106f9a2b2a1d0c64f639e2 Mon Sep 17 00:00:00 2001 From: Carina Ursu Date: Wed, 10 May 2023 15:03:48 -0700 Subject: [PATCH 05/47] chore: remove disable_auth from docs (#753) * chore: remove disable_auth from docs Signed-off-by: Carina Ursu * chore: lint Signed-off-by: Carina Ursu --------- Signed-off-by: Carina Ursu --- CONTRIBUTING.md | 2 +- README.md | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index df1a7463c..e0a68fb71 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -210,4 +210,4 @@ yarn install Your new localhost is [localhost.different.admin.service.com](http://localhost.different.admin.service.com) -> Ensure you don't have `ADMIN_API_URL` or `DISABLE_AUTH` set (e.g., in your `/.profile`.) +> Ensure you don't have `ADMIN_API_URL` set (e.g., in your `/.profile`.) diff --git a/README.md b/README.md index 6eaf3801a..497a2bc11 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ For help with installing dependencies look into ``` 2. Now, export the following env variables: - `export ADMIN_API_URL=http://localhost:30080 export DISABLE_AUTH=1` + `export ADMIN_API_URL=http://localhost:30080` > You can persist these environment variables either in the current shell or in a `.env` file at the root > of the repository. A `.env` file will persist the settings across multiple terminal @@ -116,12 +116,10 @@ few environment variables in your run command to setup the appliation. `CONFIG_DIR="/etc/flyte/config"` (required) -`DISABLE_AUTH="1"` (optional) - This example assumes building from `v1.0.0` on port `8080` ```bash -docker run -p 8080:8080 -e BASE_URL="/console" -e CONFIG_DIR="/etc/flyte/config" -e DISABLE_AUTH="1" ghcr.io/flyteorg/flyteconsole:v1.0.0 +docker run -p 8080:8080 -e BASE_URL="/console" -e CONFIG_DIR="/etc/flyte/config" ghcr.io/flyteorg/flyteconsole:v1.0.0 ``` ### Run the server From d985dfd158b513f057dc2ae7a9f20a387d716f7f Mon Sep 17 00:00:00 2001 From: Carina Ursu Date: Wed, 10 May 2023 15:29:34 -0700 Subject: [PATCH 06/47] fix: missing dependency (#755) Signed-off-by: Carina Ursu --- package.json | 1 - packages/console/package.json | 3 ++- website/package.json | 2 +- yarn.lock | 6 +++--- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 7a7e1078f..d8e60716d 100644 --- a/package.json +++ b/package.json @@ -104,7 +104,6 @@ "react-dom": "^16.13.1", "serve-static": "^1.12.3", "source-map-loader": "^4.0.1", - "traverse": "^0.6.7", "ts-jest": "^26.3.0", "ts-loader": "^9.2.6", "ts-node": "^8.0.2", diff --git a/packages/console/package.json b/packages/console/package.json index 2646c5c20..718717d97 100644 --- a/packages/console/package.json +++ b/packages/console/package.json @@ -1,6 +1,6 @@ { "name": "@flyteorg/console", - "version": "0.0.29", + "version": "0.0.30", "description": "Flyteconsole main app module", "main": "./dist/index.js", "module": "./lib/index.js", @@ -107,6 +107,7 @@ "react-query-devtools": "3.0.0-beta.1", "react-virtualized": "^9.21.1", "shallowequal": "^1.1.0", + "traverse": "^0.6.7", "url-search-params": "^0.10.0", "xstate": "4.33.6" }, diff --git a/website/package.json b/website/package.json index be189ae0d..0830de38a 100644 --- a/website/package.json +++ b/website/package.json @@ -37,7 +37,7 @@ }, "dependencies": { "@flyteorg/common": "^0.0.4", - "@flyteorg/console": "^0.0.29", + "@flyteorg/console": "^0.0.30", "long": "^4.0.0", "protobufjs": "~6.11.3", "react-ga4": "^1.4.1", diff --git a/yarn.lock b/yarn.lock index a87818160..f063ba482 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.29 + "@flyteorg/console": ^0.0.30 "@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.29, @flyteorg/console@workspace:packages/console": +"@flyteorg/console@^0.0.30, @flyteorg/console@workspace:packages/console": version: 0.0.0-use.local resolution: "@flyteorg/console@workspace:packages/console" dependencies: @@ -2134,6 +2134,7 @@ __metadata: react-query-devtools: 3.0.0-beta.1 react-virtualized: ^9.21.1 shallowequal: ^1.1.0 + traverse: ^0.6.7 url-search-params: ^0.10.0 xstate: 4.33.6 peerDependencies: @@ -11703,7 +11704,6 @@ __metadata: react-dom: ^16.13.1 serve-static: ^1.12.3 source-map-loader: ^4.0.1 - traverse: ^0.6.7 ts-jest: ^26.3.0 ts-loader: ^9.2.6 ts-node: ^8.0.2 From b7c4b80319f9c7bb1287ecebdf7a984f35557bb6 Mon Sep 17 00:00:00 2001 From: Jason Porter <84735036+jsonporter@users.noreply.github.com> Date: Thu, 11 May 2023 16:45:55 -0700 Subject: [PATCH 07/47] feat: added support for mapped task parent retryAttempt (#756) * feat: added support for mapped task parent retryAttempt Signed-off-by: Jason Porter * chore: upgrade pkg Signed-off-by: Carina Ursu * chore: yarn.lock Signed-off-by: Carina Ursu --------- Signed-off-by: Jason Porter Signed-off-by: Carina Ursu Co-authored-by: Carina Ursu --- packages/console/package.json | 2 +- .../TaskExecutionsList/MapTaskExecutionListItem.tsx | 2 -- .../Executions/TaskExecutionsList/TaskExecutionLogsCard.tsx | 4 ++-- .../components/common/MapTaskExecutionsList/TaskNameList.tsx | 1 + packages/console/src/models/Execution/types.ts | 1 + website/package.json | 2 +- yarn.lock | 4 ++-- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/console/package.json b/packages/console/package.json index 718717d97..861404e81 100644 --- a/packages/console/package.json +++ b/packages/console/package.json @@ -1,6 +1,6 @@ { "name": "@flyteorg/console", - "version": "0.0.30", + "version": "0.0.31", "description": "Flyteconsole main app module", "main": "./dist/index.js", "module": "./lib/index.js", diff --git a/packages/console/src/components/Executions/TaskExecutionsList/MapTaskExecutionListItem.tsx b/packages/console/src/components/Executions/TaskExecutionsList/MapTaskExecutionListItem.tsx index 4519df89a..1bcf2db56 100644 --- a/packages/console/src/components/Executions/TaskExecutionsList/MapTaskExecutionListItem.tsx +++ b/packages/console/src/components/Executions/TaskExecutionsList/MapTaskExecutionListItem.tsx @@ -74,7 +74,6 @@ export const MapTaskExecutionsListItem: React.FC< ) : null} - {/* If main map task has log attached - show it here */} {logs && logs.length > 0 ? (
@@ -99,7 +98,6 @@ export const MapTaskExecutionsListItem: React.FC< /> ); })} - {/* If map task is actively started - show 'started' and 'run time' details */} {taskHasStarted && (
diff --git a/packages/console/src/components/Executions/TaskExecutionsList/TaskExecutionLogsCard.tsx b/packages/console/src/components/Executions/TaskExecutionsList/TaskExecutionLogsCard.tsx index 45107e940..76f7b881d 100644 --- a/packages/console/src/components/Executions/TaskExecutionsList/TaskExecutionLogsCard.tsx +++ b/packages/console/src/components/Executions/TaskExecutionsList/TaskExecutionLogsCard.tsx @@ -4,7 +4,7 @@ import Typography from '@material-ui/core/Typography'; import classnames from 'classnames'; import { useCommonStyles } from 'components/common/styles'; import { TaskExecutionPhase } from 'models/Execution/enums'; -import { TaskExecution } from 'models/Execution/types'; +import { MapTaskExecution, TaskExecution } from 'models/Execution/types'; import { Core } from '@flyteorg/flyteidl-types'; import { ExternalConfigHoc } from 'basics/ExternalConfigHoc'; import { useExternalConfigurationContext } from 'basics/ExternalConfigurationProvider'; @@ -36,7 +36,7 @@ const useStyles = makeStyles((theme: Theme) => ({ })); interface TaskExecutionLogsCardProps { - taskExecution: TaskExecution; + taskExecution: TaskExecution | MapTaskExecution; headerText: string; phase: TaskExecutionPhase; logs: Core.ITaskLog[]; diff --git a/packages/console/src/components/common/MapTaskExecutionsList/TaskNameList.tsx b/packages/console/src/components/common/MapTaskExecutionsList/TaskNameList.tsx index 7b378a20a..02582abef 100644 --- a/packages/console/src/components/common/MapTaskExecutionsList/TaskNameList.tsx +++ b/packages/console/src/components/common/MapTaskExecutionsList/TaskNameList.tsx @@ -64,6 +64,7 @@ export const TaskNameList = ({ onTaskSelected({ ...taskExecution, taskIndex: (log as any).index, + parentRetryAttempt: taskExecution.id.retryAttempt, }); }; diff --git a/packages/console/src/models/Execution/types.ts b/packages/console/src/models/Execution/types.ts index 3ebaaca9d..ddc691838 100644 --- a/packages/console/src/models/Execution/types.ts +++ b/packages/console/src/models/Execution/types.ts @@ -118,6 +118,7 @@ export interface TaskExecutionIdentifier extends Core.ITaskExecutionIdentifier { } export interface MapTaskExecution extends TaskExecution { taskIndex: number | null; + parentRetryAttempt?: number; } export interface TaskExecution extends Admin.ITaskExecution { diff --git a/website/package.json b/website/package.json index 0830de38a..6f64315b8 100644 --- a/website/package.json +++ b/website/package.json @@ -37,7 +37,7 @@ }, "dependencies": { "@flyteorg/common": "^0.0.4", - "@flyteorg/console": "^0.0.30", + "@flyteorg/console": "^0.0.31", "long": "^4.0.0", "protobufjs": "~6.11.3", "react-ga4": "^1.4.1", diff --git a/yarn.lock b/yarn.lock index f063ba482..214bcc0c7 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.30 + "@flyteorg/console": ^0.0.31 "@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.30, @flyteorg/console@workspace:packages/console": +"@flyteorg/console@^0.0.31, @flyteorg/console@workspace:packages/console": version: 0.0.0-use.local resolution: "@flyteorg/console@workspace:packages/console" dependencies: From 22ed97fc892d1fa2575746106dfc2f9bdcd68ba2 Mon Sep 17 00:00:00 2001 From: james-union <105876962+james-union@users.noreply.github.com> Date: Mon, 15 May 2023 11:02:41 -0400 Subject: [PATCH 08/47] Export Flytedecks support for TLRO (#757) * fix: export ExecutionNodeDeck with few css changes Signed-off-by: James * fix: upgrade version Signed-off-by: James * chore: yarn.lock Signed-off-by: Carina Ursu --------- Signed-off-by: James Signed-off-by: Carina Ursu Co-authored-by: Carina Ursu --- packages/console/package.json | 2 +- .../Executions/ExecutionDetails/ExecutionDetailsActions.tsx | 3 --- .../Executions/ExecutionDetails/ExecutionNodeDeck.tsx | 6 +++++- .../src/components/Executions/ExecutionDetails/index.ts | 1 + website/package.json | 2 +- yarn.lock | 4 ++-- 6 files changed, 10 insertions(+), 8 deletions(-) diff --git a/packages/console/package.json b/packages/console/package.json index 861404e81..0ce2731f2 100644 --- a/packages/console/package.json +++ b/packages/console/package.json @@ -1,6 +1,6 @@ { "name": "@flyteorg/console", - "version": "0.0.31", + "version": "0.0.32", "description": "Flyteconsole main app module", "main": "./dist/index.js", "module": "./lib/index.js", diff --git a/packages/console/src/components/Executions/ExecutionDetails/ExecutionDetailsActions.tsx b/packages/console/src/components/Executions/ExecutionDetails/ExecutionDetailsActions.tsx index 2752c4dbf..ea5202b99 100644 --- a/packages/console/src/components/Executions/ExecutionDetails/ExecutionDetailsActions.tsx +++ b/packages/console/src/components/Executions/ExecutionDetails/ExecutionDetailsActions.tsx @@ -38,9 +38,6 @@ const useStyles = makeStyles((theme: Theme) => { maxHeight: `calc(100% - ${theme.spacing(12)}px)`, height: theme.spacing(90), width: theme.spacing(110), - '& iframe': { - border: 'none', - }, }, dialogTitle: { display: 'flex', diff --git a/packages/console/src/components/Executions/ExecutionDetails/ExecutionNodeDeck.tsx b/packages/console/src/components/Executions/ExecutionDetails/ExecutionNodeDeck.tsx index cb0b1177f..2a389fd2c 100644 --- a/packages/console/src/components/Executions/ExecutionDetails/ExecutionNodeDeck.tsx +++ b/packages/console/src/components/Executions/ExecutionDetails/ExecutionNodeDeck.tsx @@ -6,15 +6,19 @@ import { Core } from '@flyteorg/flyteidl-types'; /** Fetches and renders the deck data for a given `nodeExecutionId` */ export const ExecutionNodeDeck: React.FC<{ nodeExecutionId: Core.NodeExecutionIdentifier; -}> = ({ nodeExecutionId }) => { + className?: string; +}> = ({ nodeExecutionId, className = '' }) => { const downloadLink = useDownloadLink(nodeExecutionId); return (