diff --git a/frontend/src/concepts/pipelines/apiHooks/mlmd/types.ts b/frontend/src/concepts/pipelines/apiHooks/mlmd/types.ts new file mode 100644 index 0000000000..254eb6fde3 --- /dev/null +++ b/frontend/src/concepts/pipelines/apiHooks/mlmd/types.ts @@ -0,0 +1,3 @@ +import { Context } from '~/third_party/mlmd'; + +export type MlmdContext = Context; diff --git a/frontend/src/concepts/pipelines/apiHooks/mlmd/useExecutionsFromContext.ts b/frontend/src/concepts/pipelines/apiHooks/mlmd/useExecutionsFromContext.ts index 4b379d04e9..61072adada 100644 --- a/frontend/src/concepts/pipelines/apiHooks/mlmd/useExecutionsFromContext.ts +++ b/frontend/src/concepts/pipelines/apiHooks/mlmd/useExecutionsFromContext.ts @@ -1,6 +1,7 @@ import React from 'react'; +import { MlmdContext } from '~/concepts/pipelines/apiHooks/mlmd/types'; import { usePipelinesAPI } from '~/concepts/pipelines/context'; -import { Context, Execution, GetExecutionsByContextRequest } from '~/third_party/mlmd'; +import { Execution, GetExecutionsByContextRequest } from '~/third_party/mlmd'; import { FAST_POLL_INTERVAL } from '~/utilities/const'; import useFetchState, { FetchState, @@ -9,8 +10,8 @@ import useFetchState, { } from '~/utilities/useFetchState'; export const useExecutionsFromContext = ( - context: Context | null, - pipelineFinished?: boolean, + context: MlmdContext | null, + activelyRefresh?: boolean, ): FetchState => { const { metadataStoreServiceClient } = usePipelinesAPI(); @@ -26,6 +27,6 @@ export const useExecutionsFromContext = ( }, [metadataStoreServiceClient, context]); return useFetchState(call, null, { - refreshRate: !pipelineFinished ? FAST_POLL_INTERVAL : undefined, + refreshRate: !activelyRefresh ? FAST_POLL_INTERVAL : undefined, }); }; diff --git a/frontend/src/concepts/pipelines/apiHooks/mlmd/useMlmdContext.ts b/frontend/src/concepts/pipelines/apiHooks/mlmd/useMlmdContext.ts new file mode 100644 index 0000000000..8eba270909 --- /dev/null +++ b/frontend/src/concepts/pipelines/apiHooks/mlmd/useMlmdContext.ts @@ -0,0 +1,41 @@ +import React from 'react'; +import { MlmdContext } from '~/concepts/pipelines/apiHooks/mlmd/types'; +import { usePipelinesAPI } from '~/concepts/pipelines/context'; +import { GetContextByTypeAndNameRequest } from '~/third_party/mlmd'; +import { FAST_POLL_INTERVAL } from '~/utilities/const'; +import useFetchState, { + FetchState, + FetchStateCallbackPromise, + NotReadyError, +} from '~/utilities/useFetchState'; + +export const useMlmdContext = ( + name?: string, + type?: string, + activelyRefresh?: boolean, +): FetchState => { + const { metadataStoreServiceClient } = usePipelinesAPI(); + + const call = React.useCallback>(async () => { + if (!type) { + return Promise.reject(new NotReadyError('No context type')); + } + if (!name) { + return Promise.reject(new NotReadyError('No context name')); + } + + const request = new GetContextByTypeAndNameRequest(); + request.setTypeName(type); + request.setContextName(name); + const res = await metadataStoreServiceClient.getContextByTypeAndName(request); + const context = res.getContext(); + if (!context) { + return Promise.reject(new Error('Cannot find specified context')); + } + return context; + }, [metadataStoreServiceClient, type, name]); + + return useFetchState(call, null, { + refreshRate: activelyRefresh ? FAST_POLL_INTERVAL : undefined, + }); +}; diff --git a/frontend/src/concepts/pipelines/apiHooks/mlmd/usePipelineRunMlmdContext.ts b/frontend/src/concepts/pipelines/apiHooks/mlmd/usePipelineRunMlmdContext.ts index 7ec399473d..03af621d99 100644 --- a/frontend/src/concepts/pipelines/apiHooks/mlmd/usePipelineRunMlmdContext.ts +++ b/frontend/src/concepts/pipelines/apiHooks/mlmd/usePipelineRunMlmdContext.ts @@ -1,48 +1,11 @@ -import React from 'react'; -import { Context } from '~/third_party/mlmd'; -import { usePipelinesAPI } from '~/concepts/pipelines/context'; -import useFetchState, { - FetchState, - FetchStateCallbackPromise, - NotReadyError, -} from '~/utilities/useFetchState'; -import { GetContextByTypeAndNameRequest } from '~/third_party/mlmd/generated/ml_metadata/proto/metadata_store_service_pb'; -import { FAST_POLL_INTERVAL } from '~/utilities/const'; +import { FetchState } from '~/utilities/useFetchState'; +import { MlmdContext } from '~/concepts/pipelines/apiHooks/mlmd/types'; +import { useMlmdContext } from '~/concepts/pipelines/apiHooks/mlmd/useMlmdContext'; const KFP_V2_RUN_CONTEXT_TYPE = 'system.PipelineRun'; -const useMlmdContext = ( - name?: string, - type?: string, - pipelineFinished?: boolean, -): FetchState => { - const { metadataStoreServiceClient } = usePipelinesAPI(); - - const call = React.useCallback>(async () => { - if (!type) { - return Promise.reject(new NotReadyError('No context type')); - } - if (!name) { - return Promise.reject(new NotReadyError('No context name')); - } - - const request = new GetContextByTypeAndNameRequest(); - request.setTypeName(type); - request.setContextName(name); - const res = await metadataStoreServiceClient.getContextByTypeAndName(request); - const context = res.getContext(); - if (!context) { - return Promise.reject(new Error('Cannot find specified context')); - } - return context; - }, [metadataStoreServiceClient, type, name]); - - return useFetchState(call, null, { - refreshRate: !pipelineFinished ? FAST_POLL_INTERVAL : undefined, - }); -}; - export const usePipelineRunMlmdContext = ( runID?: string, - pipelineFinished?: boolean, -): FetchState => useMlmdContext(runID, KFP_V2_RUN_CONTEXT_TYPE, pipelineFinished); + activelyRefresh?: boolean, +): FetchState => + useMlmdContext(runID, KFP_V2_RUN_CONTEXT_TYPE, activelyRefresh); diff --git a/frontend/src/concepts/pipelines/apiHooks/usePipelineRunById.ts b/frontend/src/concepts/pipelines/apiHooks/usePipelineRunById.ts index 72b4522b0a..c112b91db5 100644 --- a/frontend/src/concepts/pipelines/apiHooks/usePipelineRunById.ts +++ b/frontend/src/concepts/pipelines/apiHooks/usePipelineRunById.ts @@ -9,7 +9,7 @@ import { PipelineRunKFv2, RuntimeStateKF, runtimeStateLabels } from '~/concepts/ import { FAST_POLL_INTERVAL } from '~/utilities/const'; import { computeRunStatus } from '~/concepts/pipelines/content/utils'; -export const isPipelineRunComplete = (run?: PipelineRunKFv2 | null): boolean => { +export const isPipelineRunFinished = (run?: PipelineRunKFv2 | null): boolean => { const { label } = computeRunStatus(run); return [ runtimeStateLabels[RuntimeStateKF.SUCCEEDED], @@ -41,13 +41,13 @@ const usePipelineRunById = ( }); const [run] = runData; - const isComplete = isPipelineRunComplete(run); + const isFinished = isPipelineRunFinished(run); React.useEffect(() => { - if (isComplete) { + if (isFinished) { setPipelineFinished(true); } - }, [isComplete]); + }, [isFinished]); return runData; }; diff --git a/frontend/src/concepts/pipelines/content/pipelinesDetails/pipelineRun/PipelineRunDetails.tsx b/frontend/src/concepts/pipelines/content/pipelinesDetails/pipelineRun/PipelineRunDetails.tsx index 91d50b370d..006828eed3 100644 --- a/frontend/src/concepts/pipelines/content/pipelinesDetails/pipelineRun/PipelineRunDetails.tsx +++ b/frontend/src/concepts/pipelines/content/pipelinesDetails/pipelineRun/PipelineRunDetails.tsx @@ -36,6 +36,7 @@ import { usePipelineTaskTopology } from '~/concepts/pipelines/topology'; import { PipelineRunType } from '~/pages/pipelines/global/runs/types'; import { routePipelineRunsNamespace } from '~/routes'; import PipelineJobReferenceName from '~/concepts/pipelines/content/PipelineJobReferenceName'; +import useExecutionsForPipelineRun from '~/concepts/pipelines/content/pipelinesDetails/pipelineRun/useExecutionsForPipelineRun'; const PipelineRunDetails: PipelineCoreDetailsPageComponent = ({ breadcrumbPath, contextPath }) => { const { runId } = useParams(); @@ -52,21 +53,16 @@ const PipelineRunDetails: PipelineCoreDetailsPageComponent = ({ breadcrumbPath, RunDetailsTabs.DETAILS, ); const [selectedId, setSelectedId] = React.useState(null); - const { - taskMap, - nodes, - loaded: topologyLoaded, - } = usePipelineTaskTopology(pipelineSpec, runResource ?? undefined); - const loaded = runLoaded && (versionLoaded || !!runResource?.pipeline_spec) && topologyLoaded; + const [executions, executionsLoaded, executionsError] = useExecutionsForPipelineRun(runResource); + const { taskMap, nodes } = usePipelineTaskTopology( + pipelineSpec, + runResource?.run_details, + executions, + ); + + const loaded = runLoaded && (versionLoaded || !!runResource?.pipeline_spec); const error = versionError || runError; - if (!loaded && !error) { - return ( - - - - ); - } if (error) { return ( @@ -81,6 +77,14 @@ const PipelineRunDetails: PipelineCoreDetailsPageComponent = ({ breadcrumbPath, ); } + if (!loaded || (!executionsLoaded && !executionsError)) { + return ( + + + + ); + } + return ( <> @@ -140,12 +144,10 @@ const PipelineRunDetails: PipelineCoreDetailsPageComponent = ({ breadcrumbPath, } headerAction={ - loaded && ( - setDeleting(true)} - /> - ) + setDeleting(true)} + /> } empty={false} > diff --git a/frontend/src/concepts/pipelines/content/pipelinesDetails/pipelineRun/runLogs/LogsTab.tsx b/frontend/src/concepts/pipelines/content/pipelinesDetails/pipelineRun/runLogs/LogsTab.tsx index c134a0e37e..13bd5267f5 100644 --- a/frontend/src/concepts/pipelines/content/pipelinesDetails/pipelineRun/runLogs/LogsTab.tsx +++ b/frontend/src/concepts/pipelines/content/pipelinesDetails/pipelineRun/runLogs/LogsTab.tsx @@ -40,11 +40,12 @@ import DownloadDropdown from '~/concepts/pipelines/content/pipelinesDetails/pipe import { PodStepStateType } from '~/types'; import useDebounceCallback from '~/utilities/useDebounceCallback'; import { PipelineTask } from '~/concepts/pipelines/topology'; +import { ExecutionStateKF } from '~/concepts/pipelines/kfTypes'; // TODO: If this gets large enough we should look to make this its own component file const LogsTab: React.FC<{ task: PipelineTask }> = ({ task }) => { const podName = task.status?.podName; - const isFailedPod = task.status?.state === 'Failed'; + const isFailedPod = task.status?.state === ExecutionStateKF.FAILED; if (!podName) { return <>No content; diff --git a/frontend/src/concepts/pipelines/content/pipelinesDetails/pipelineRun/useExecutionsForPipelineRun.ts b/frontend/src/concepts/pipelines/content/pipelinesDetails/pipelineRun/useExecutionsForPipelineRun.ts new file mode 100644 index 0000000000..2364204ebd --- /dev/null +++ b/frontend/src/concepts/pipelines/content/pipelinesDetails/pipelineRun/useExecutionsForPipelineRun.ts @@ -0,0 +1,19 @@ +import { useExecutionsFromContext } from '~/concepts/pipelines/apiHooks/mlmd/useExecutionsFromContext'; +import { usePipelineRunMlmdContext } from '~/concepts/pipelines/apiHooks/mlmd/usePipelineRunMlmdContext'; +import { isPipelineRunFinished } from '~/concepts/pipelines/apiHooks/usePipelineRunById'; +import { PipelineRunKFv2 } from '~/concepts/pipelines/kfTypes'; +import { Execution } from '~/third_party/mlmd'; + +const useExecutionsForPipelineRun = ( + run: PipelineRunKFv2 | null, +): [executions: Execution[] | null, loaded: boolean, error?: Error] => { + const isFinished = isPipelineRunFinished(run); + // contextError means mlmd service is not available, no need to check executions + const [context, , contextError] = usePipelineRunMlmdContext(run?.run_id, !isFinished); + // executionsLoaded is the flag to show the spinner or not + const [executions, executionsLoaded] = useExecutionsFromContext(context, !isFinished); + + return [executions, executionsLoaded, contextError]; +}; + +export default useExecutionsForPipelineRun; diff --git a/frontend/src/concepts/pipelines/kfTypes.ts b/frontend/src/concepts/pipelines/kfTypes.ts index 73cd7e6987..50a967abab 100644 --- a/frontend/src/concepts/pipelines/kfTypes.ts +++ b/frontend/src/concepts/pipelines/kfTypes.ts @@ -466,6 +466,22 @@ export enum RuntimeStateKF { PAUSED = 'PAUSED', } +export enum ExecutionStateKF { + NEW = 'New', + RUNNING = 'Running', + COMPLETE = 'Complete', + CANCELED = 'Canceled', + FAILED = 'Failed', + CACHED = 'Cached', +} + +export enum ArtifactStateKF { + PENDING = 'Pending', + LIVE = 'Live', + MARKED_FOR_DELETION = 'Marked for deletion', + DELETED = 'Deleted', +} + export const runtimeStateLabels = { [RuntimeStateKF.RUNTIME_STATE_UNSPECIFIED]: 'Unspecified', [RuntimeStateKF.PENDING]: 'Pending', diff --git a/frontend/src/concepts/pipelines/topology/__tests__/parseUtils.spec.ts b/frontend/src/concepts/pipelines/topology/__tests__/parseUtils.spec.ts index 9e68f40316..ea61b169da 100644 --- a/frontend/src/concepts/pipelines/topology/__tests__/parseUtils.spec.ts +++ b/frontend/src/concepts/pipelines/topology/__tests__/parseUtils.spec.ts @@ -7,9 +7,13 @@ import { parseComponentsForArtifactRelationship, parseTasksForArtifactRelationship, parseRuntimeInfoFromRunDetails, + getResourceStateText, + ResourceType, + parseRuntimeInfoFromExecutions, } from '~/concepts/pipelines/topology/parseUtils'; import { ArtifactType, + ExecutionStateKF, InputDefinitionParameterType, PipelineComponentsKF, RunDetailsKF, @@ -18,6 +22,7 @@ import { TaskKF, TriggerStrategy, } from '~/concepts/pipelines/kfTypes'; +import { Execution, Value } from '~/third_party/mlmd'; describe('pipeline topology parseUtils', () => { describe('parseInputOutput', () => { @@ -154,6 +159,88 @@ describe('pipeline topology parseUtils', () => { }); }); + describe('parseRuntimeInfoFromExecutions', () => { + const testTaskId = 'test-task-id'; + + it('returns undefined when executions are not provided', () => { + const result = parseRuntimeInfoFromExecutions(testTaskId); + expect(result).toBeUndefined(); + }); + + it('returns undefined when executions is null', () => { + const result = parseRuntimeInfoFromExecutions(testTaskId, null); + expect(result).toBeUndefined(); + }); + + it('returns undefined when executions are empty', () => { + const result = parseRuntimeInfoFromExecutions(testTaskId, []); + expect(result).toBeUndefined(); + }); + + it('returns undefined when there are no match executions', () => { + const mockExecution = new Execution(); + const result = parseRuntimeInfoFromExecutions(testTaskId, [mockExecution]); + expect(result).toBeUndefined(); + }); + + it('returns runtime info when execution id matches', () => { + const mockExecution = new Execution(); + const value = new Value(); + mockExecution.getCustomPropertiesMap().set('task_name', value.setStringValue(testTaskId)); + mockExecution.setCreateTimeSinceEpoch(1713285296322); + mockExecution.setLastUpdateTimeSinceEpoch(1713285296524); + mockExecution.setLastKnownState(Execution.State.COMPLETE); + const result = parseRuntimeInfoFromExecutions(testTaskId, [mockExecution]); + expect(result).toStrictEqual({ + completeTime: '2024-04-16T16:34:56.524Z', + podName: undefined, + startTime: '2024-04-16T16:34:56.322Z', + state: 'Complete', + taskId: 'task.test-task-id', + }); + }); + }); + + describe('getResourceStateText', () => { + it('returns undefined when state is not provided', () => { + const mockExecution = new Execution(); + const result = getResourceStateText({ + resourceType: ResourceType.EXECUTION, + resource: mockExecution, + }); + expect(result).toBeUndefined(); + }); + + it('returns undefined when state is "UNKNOWN"', () => { + const mockExecution = new Execution(); + mockExecution.setLastKnownState(Execution.State.UNKNOWN); + const result = getResourceStateText({ + resourceType: ResourceType.EXECUTION, + resource: mockExecution, + }); + expect(result).toBeUndefined(); + }); + + [ + { state: Execution.State.CACHED, status: ExecutionStateKF.CACHED }, + { state: Execution.State.CANCELED, status: ExecutionStateKF.CANCELED }, + { state: Execution.State.COMPLETE, status: ExecutionStateKF.COMPLETE }, + { state: Execution.State.FAILED, status: ExecutionStateKF.FAILED }, + { state: Execution.State.NEW, status: ExecutionStateKF.NEW }, + { state: Execution.State.RUNNING, status: ExecutionStateKF.RUNNING }, + ].forEach(({ state, status }) => { + it(`returns "${status}" with a provided "${state}" MLMD execution state`, () => { + const mockExecution = new Execution(); + mockExecution.setLastKnownState(state); + const result = getResourceStateText({ + resourceType: ResourceType.EXECUTION, + resource: mockExecution, + }); + expect(result).toBe(status); + }); + }); + }); + describe('translateStatusForNode', () => { it('returns undefined when state is not provided', () => { const result = translateStatusForNode(); @@ -179,6 +266,11 @@ describe('pipeline topology parseUtils', () => { { state: RuntimeStateKF.RUNNING, status: RunStatus.InProgress }, { state: RuntimeStateKF.SKIPPED, status: RunStatus.Skipped }, { state: RuntimeStateKF.SUCCEEDED, status: RunStatus.Succeeded }, + { state: ExecutionStateKF.CANCELED, status: RunStatus.Cancelled }, + { state: ExecutionStateKF.CACHED, status: RunStatus.Skipped }, + { state: ExecutionStateKF.COMPLETE, status: RunStatus.Succeeded }, + { state: ExecutionStateKF.FAILED, status: RunStatus.Failed }, + { state: ExecutionStateKF.RUNNING, status: RunStatus.Running }, ].forEach(({ state, status }) => { it(`returns "${status}" with a provided "${state}" state`, () => { const result = translateStatusForNode(state); diff --git a/frontend/src/concepts/pipelines/topology/parseUtils.ts b/frontend/src/concepts/pipelines/topology/parseUtils.ts index 415030634e..79870ead5b 100644 --- a/frontend/src/concepts/pipelines/topology/parseUtils.ts +++ b/frontend/src/concepts/pipelines/topology/parseUtils.ts @@ -1,6 +1,8 @@ import { RunStatus } from '@patternfly/react-topology'; import { + ArtifactStateKF, DAG, + ExecutionStateKF, InputOutputArtifactType, InputOutputDefinition, PipelineComponentsKF, @@ -166,7 +168,7 @@ export const parseRuntimeInfoFromRunDetails = ( export const parseRuntimeInfoFromExecutions = ( taskId: string, - executions: Execution[] | null, + executions?: Execution[] | null, ): PipelineTaskRunStatus | undefined => { if (!executions) { return undefined; @@ -182,12 +184,14 @@ export const parseRuntimeInfoFromExecutions = ( const lastUpdatedTime = execution.getLastUpdateTimeSinceEpoch(); let completeTime; + const lastKnownState = execution.getLastKnownState(); + // Logic comes from https://github.com/opendatahub-io/data-science-pipelines/blob/master/frontend/src/components/tabs/RuntimeNodeDetailsV2.tsx#L245-L253 if ( lastUpdatedTime && - (execution.getLastKnownState() === Execution.State.COMPLETE || - execution.getLastKnownState() === Execution.State.FAILED || - execution.getLastKnownState() === Execution.State.CACHED || - execution.getLastKnownState() === Execution.State.CANCELED) + (lastKnownState === Execution.State.COMPLETE || + lastKnownState === Execution.State.FAILED || + lastKnownState === Execution.State.CACHED || + lastKnownState === Execution.State.CANCELED) ) { completeTime = new Date(lastUpdatedTime).toISOString(); } @@ -197,7 +201,6 @@ export const parseRuntimeInfoFromExecutions = ( state: getResourceStateText({ resourceType: ResourceType.EXECUTION, resource: execution, - typeName: 'Execution', }), taskId: `task.${taskId}`, podName: execution.getCustomPropertiesMap().get('pod_name')?.getStringValue(), @@ -212,33 +215,31 @@ export enum ResourceType { export interface ArtifactProps { resourceType: ResourceType.ARTIFACT; resource: Artifact; - typeName: string; } export interface ExecutionProps { resourceType: ResourceType.EXECUTION; resource: Execution; - typeName: string; } export type ResourceInfoProps = ArtifactProps | ExecutionProps; // Get text representation of resource state. // Works for both artifact and execution. -export const getResourceStateText = (props: ResourceInfoProps): string | undefined => { +export const getResourceStateText = ( + props: ResourceInfoProps, +): ArtifactStateKF | ExecutionStateKF | undefined => { if (props.resourceType === ResourceType.ARTIFACT) { const state = props.resource.getState(); switch (state) { - case Artifact.State.UNKNOWN: - return undefined; // when state is not set, it defaults to UNKNOWN case Artifact.State.PENDING: - return 'Pending'; + return ArtifactStateKF.PENDING; case Artifact.State.LIVE: - return 'Live'; + return ArtifactStateKF.LIVE; case Artifact.State.MARKED_FOR_DELETION: - return 'Marked for deletion'; + return ArtifactStateKF.MARKED_FOR_DELETION; case Artifact.State.DELETED: - return 'Deleted'; + return ArtifactStateKF.DELETED; default: return undefined; } @@ -246,46 +247,47 @@ export const getResourceStateText = (props: ResourceInfoProps): string | undefin // type == EXECUTION const state = props.resource.getLastKnownState(); switch (state) { - case Execution.State.UNKNOWN: - return undefined; case Execution.State.NEW: - return 'New'; + return ExecutionStateKF.NEW; case Execution.State.RUNNING: - return 'Running'; + return ExecutionStateKF.RUNNING; case Execution.State.COMPLETE: - return 'Complete'; + return ExecutionStateKF.COMPLETE; case Execution.State.CANCELED: - return 'Canceled'; + return ExecutionStateKF.CANCELED; case Execution.State.FAILED: - return 'Failed'; + return ExecutionStateKF.FAILED; case Execution.State.CACHED: - return 'Cached'; + return ExecutionStateKF.CACHED; default: return undefined; } } }; -export const translateStatusForNode = (state?: string): RunStatus | undefined => { +export const translateStatusForNode = ( + state?: RuntimeStateKF | ExecutionStateKF | ArtifactStateKF, +): RunStatus | undefined => { switch (state) { - case 'Canceled': + case ExecutionStateKF.CANCELED: case RuntimeStateKF.CANCELED: case RuntimeStateKF.CANCELING: return RunStatus.Cancelled; - case 'Running': + case ExecutionStateKF.RUNNING: return RunStatus.Running; - case 'Failed': + case ExecutionStateKF.FAILED: case RuntimeStateKF.FAILED: return RunStatus.Failed; - case 'Pending': + case ArtifactStateKF.PENDING: case RuntimeStateKF.PAUSED: case RuntimeStateKF.PENDING: return RunStatus.Pending; case RuntimeStateKF.RUNNING: return RunStatus.InProgress; - case 'Complete': + case ExecutionStateKF.COMPLETE: case RuntimeStateKF.SUCCEEDED: return RunStatus.Succeeded; + case ExecutionStateKF.CACHED: case RuntimeStateKF.SKIPPED: return RunStatus.Skipped; case RuntimeStateKF.RUNTIME_STATE_UNSPECIFIED: diff --git a/frontend/src/concepts/pipelines/topology/pipelineTaskTypes.ts b/frontend/src/concepts/pipelines/topology/pipelineTaskTypes.ts index 18ffb0a235..db8f50610e 100644 --- a/frontend/src/concepts/pipelines/topology/pipelineTaskTypes.ts +++ b/frontend/src/concepts/pipelines/topology/pipelineTaskTypes.ts @@ -1,4 +1,9 @@ -import { InputDefinitionParameterType } from '~/concepts/pipelines/kfTypes'; +import { + ArtifactStateKF, + ExecutionStateKF, + InputDefinitionParameterType, + RuntimeStateKF, +} from '~/concepts/pipelines/kfTypes'; import { createNode } from '~/concepts/topology'; import { VolumeMount } from '~/types'; @@ -31,7 +36,7 @@ export type PipelineTaskRunStatus = { startTime: string; completeTime?: string; podName?: string; - state?: string; + state?: RuntimeStateKF | ExecutionStateKF | ArtifactStateKF; taskId?: string; }; @@ -57,5 +62,4 @@ export type KubeFlowTaskTopology = { * Nodes to render in topology. */ nodes: ReturnType[]; - loaded: boolean; }; diff --git a/frontend/src/concepts/pipelines/topology/usePipelineTaskTopology.ts b/frontend/src/concepts/pipelines/topology/usePipelineTaskTopology.ts index 1b6f92d259..a44cea3e36 100644 --- a/frontend/src/concepts/pipelines/topology/usePipelineTaskTopology.ts +++ b/frontend/src/concepts/pipelines/topology/usePipelineTaskTopology.ts @@ -1,10 +1,8 @@ -import { PipelineRunKFv2, PipelineSpecVariable, TaskKF } from '~/concepts/pipelines/kfTypes'; +import { PipelineSpecVariable, RunDetailsKF, TaskKF } from '~/concepts/pipelines/kfTypes'; import { createNode } from '~/concepts/topology'; import { PipelineNodeModelExpanded } from '~/concepts/topology/types'; import { createArtifactNode } from '~/concepts/topology/utils'; -import { usePipelineRunMlmdContext } from '~/concepts/pipelines/apiHooks/mlmd/usePipelineRunMlmdContext'; -import { useExecutionsFromContext } from '~/concepts/pipelines/apiHooks/mlmd/useExecutionsFromContext'; -import { isPipelineRunComplete } from '~/concepts/pipelines/apiHooks/usePipelineRunById'; +import { Execution } from '~/third_party/mlmd'; import { composeArtifactType, parseComponentsForArtifactRelationship, @@ -19,20 +17,11 @@ import { KubeFlowTaskTopology } from './pipelineTaskTypes'; export const usePipelineTaskTopology = ( spec?: PipelineSpecVariable, - run?: PipelineRunKFv2, + runDetails?: RunDetailsKF, + executions?: Execution[] | null, ): KubeFlowTaskTopology => { - const isComplete = isPipelineRunComplete(run); - // contextError means mlmd service is not available, no need to check executions - const [context, , contextError] = usePipelineRunMlmdContext(run?.run_id, isComplete); - // executionsLoaded is the flag to show the spinner or not - const [executions, executionsLoaded] = useExecutionsFromContext(context, isComplete); - - if (!executionsLoaded && !contextError) { - return { taskMap: {}, nodes: [], loaded: false }; - } - if (!spec) { - return { taskMap: {}, nodes: [], loaded: true }; + return { taskMap: {}, nodes: [] }; } const pipelineSpec = spec.pipeline_spec ?? spec; @@ -43,7 +32,6 @@ export const usePipelineTaskTopology = ( dag: { tasks: rootTasks }, }, } = pipelineSpec; - const { run_details: runDetails } = run || {}; const componentArtifactMap = parseComponentsForArtifactRelationship(components); const nodes: PipelineNodeModelExpanded[] = []; @@ -62,9 +50,9 @@ export const usePipelineTaskTopology = ( const executorLabel = component?.executorLabel; const executor = executorLabel ? executors[executorLabel] : undefined; - const status = contextError - ? parseRuntimeInfoFromRunDetails(taskId, runDetails) - : parseRuntimeInfoFromExecutions(taskId, executions); + const status = executions + ? parseRuntimeInfoFromExecutions(taskId, executions) + : parseRuntimeInfoFromRunDetails(taskId, runDetails); const runAfter: string[] = taskValue.dependentTasks ?? []; @@ -143,5 +131,5 @@ export const usePipelineTaskTopology = ( }); }; createNodes(rootTasks); - return { nodes, taskMap, loaded: true }; + return { nodes, taskMap }; };