Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[RHOAIENG-2989] artifact node drawer details section #2811

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import React from 'react';
import { MlmdContext } from '~/concepts/pipelines/apiHooks/mlmd/types';
import { usePipelinesAPI } from '~/concepts/pipelines/context';
import { Artifact } from '~/third_party/mlmd';
import { GetArtifactsByContextRequest } from '~/third_party/mlmd/generated/ml_metadata/proto/metadata_store_service_pb';
import useFetchState, {
FetchState,
FetchStateCallbackPromise,
NotReadyError,
} from '~/utilities/useFetchState';

export const useArtifactsFromMlmdContext = (
context: MlmdContext | null,
refreshRate?: number,
): FetchState<Artifact[]> => {
const { metadataStoreServiceClient } = usePipelinesAPI();

const getArtifactsList = React.useCallback<FetchStateCallbackPromise<Artifact[]>>(async () => {
if (!context) {
return Promise.reject(new NotReadyError('No context'));
}

const request = new GetArtifactsByContextRequest();
request.setContextId(context.getId());
const res = await metadataStoreServiceClient.getArtifactsByContext(request);
return res.getArtifactsList();
}, [metadataStoreServiceClient, context]);

return useFetchState(getArtifactsList, [], {
refreshRate,
});
};
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export interface ArtifactsListResponse {

export const useGetArtifactsList = (
refreshRate?: number,
): FetchState<ArtifactsListResponse | null> => {
): FetchState<ArtifactsListResponse | undefined> => {
const { pageToken, maxResultSize, filterQuery } = useMlmdListContext();
const { metadataStoreServiceClient } = usePipelinesAPI();

Expand All @@ -39,7 +39,7 @@ export const useGetArtifactsList = (
};
}, [filterQuery, pageToken, maxResultSize, metadataStoreServiceClient]);

return useFetchState(fetchArtifactsList, null, {
return useFetchState(fetchArtifactsList, undefined, {
refreshRate,
});
};
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,25 @@ import useFetchState, { FetchState, FetchStateCallbackPromise } from '~/utilitie

export const useGetEventsByExecutionId = (
executionId?: string,
): FetchState<GetEventsByExecutionIDsResponse | null> =>
useGetEventsByExecutionIds([Number(executionId)]);

export const useGetEventsByExecutionIds = (
executionIds: number[],
): FetchState<GetEventsByExecutionIDsResponse | null> => {
const { metadataStoreServiceClient } = usePipelinesAPI();

const call = React.useCallback<
FetchStateCallbackPromise<GetEventsByExecutionIDsResponse | null>
>(async () => {
const numberId = Number(executionId);
const request = new GetEventsByExecutionIDsRequest();

if (!executionId || Number.isNaN(numberId)) {
return null;
}

request.setExecutionIdsList([numberId]);
request.setExecutionIdsList(executionIds);

const response = await metadataStoreServiceClient.getEventsByExecutionIDs(request);

return response;
}, [executionId, metadataStoreServiceClient]);
}, [executionIds, metadataStoreServiceClient]);

return useFetchState(call, null);
};
Original file line number Diff line number Diff line change
Expand Up @@ -37,25 +37,40 @@ 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';
import { useGetEventsByExecutionIds } from '~/concepts/pipelines/apiHooks/mlmd/useGetEventsByExecutionId';
import { parseEventsByType } from '~/pages/pipelines/global/experiments/executions/utils';
import { Event } from '~/third_party/mlmd';
import { usePipelineRunArtifacts } from './artifacts';

const PipelineRunDetails: PipelineCoreDetailsPageComponent = ({ breadcrumbPath, contextPath }) => {
const { runId } = useParams();
const navigate = useNavigate();
const { namespace } = usePipelinesAPI();
const [runResource, runLoaded, runError] = usePipelineRunById(runId, true);
const [run, runLoaded, runError] = usePipelineRunById(runId, true);
const [version, versionLoaded, versionError] = usePipelineVersionById(
runResource?.pipeline_version_reference?.pipeline_id,
runResource?.pipeline_version_reference?.pipeline_version_id,
run?.pipeline_version_reference?.pipeline_id,
run?.pipeline_version_reference?.pipeline_version_id,
);
const pipelineSpec = version?.pipeline_spec ?? runResource?.pipeline_spec;
const pipelineSpec = version?.pipeline_spec ?? run?.pipeline_spec;
const [deleting, setDeleting] = React.useState(false);
const [detailsTab, setDetailsTab] = React.useState<RunDetailsTabSelection>(
RunDetailsTabs.DETAILS,
);
const [selectedId, setSelectedId] = React.useState<string | null>(null);

const [executions, executionsLoaded, executionsError] = useExecutionsForPipelineRun(runResource);
const nodes = usePipelineTaskTopology(pipelineSpec, runResource?.run_details, executions);
const [executions, executionsLoaded, executionsError] = useExecutionsForPipelineRun(run);
const [artifacts] = usePipelineRunArtifacts(run);
const [eventsResponse] = useGetEventsByExecutionIds(
React.useMemo(() => executions.map((execution) => execution.getId()), [executions]),
);
const events = parseEventsByType(eventsResponse);
const nodes = usePipelineTaskTopology(
pipelineSpec,
run?.run_details,
executions,
events[Event.Type.OUTPUT],
artifacts,
);

const selectedNode = React.useMemo(
() => nodes.find((n) => n.id === selectedId),
Expand All @@ -64,7 +79,7 @@ const PipelineRunDetails: PipelineCoreDetailsPageComponent = ({ breadcrumbPath,

const getFirstNode = (firstId: string) => nodes.find((n) => n.id === firstId);

const loaded = runLoaded && (versionLoaded || !!runResource?.pipeline_spec);
const loaded = runLoaded && (versionLoaded || !!run?.pipeline_spec);
const error = versionError || runError;

if (error) {
Expand Down Expand Up @@ -95,6 +110,7 @@ const PipelineRunDetails: PipelineCoreDetailsPageComponent = ({ breadcrumbPath,
panelContent={
<PipelineRunDrawerRightContent
task={selectedNode?.data.pipelineTask}
upstreamTaskName={selectedNode?.runAfterTasks?.[0]}
onClose={() => setSelectedId(null)}
executions={executions}
/>
Expand All @@ -110,29 +126,29 @@ const PipelineRunDetails: PipelineCoreDetailsPageComponent = ({ breadcrumbPath,
setDetailsTab(selection);
setSelectedId(null);
}}
pipelineRunDetails={runResource && pipelineSpec ? runResource : undefined}
pipelineRunDetails={run && pipelineSpec ? run : undefined}
/>
}
>
<ApplicationsPage
title={
runResource ? (
<PipelineDetailsTitle run={runResource} statusIcon pipelineRunLabel />
run ? (
<PipelineDetailsTitle run={run} statusIcon pipelineRunLabel />
) : (
'Error loading run'
)
}
subtext={
runResource && (
run && (
<PipelineJobReferenceName
runName={runResource.display_name}
recurringRunId={runResource.recurring_run_id}
runName={run.display_name}
recurringRunId={run.recurring_run_id}
/>
)
}
description={
runResource?.description ? (
<MarkdownView conciseDisplay markdown={runResource.description} />
run?.description ? (
<MarkdownView conciseDisplay markdown={run.description} />
) : (
''
)
Expand All @@ -143,15 +159,12 @@ const PipelineRunDetails: PipelineCoreDetailsPageComponent = ({ breadcrumbPath,
<Breadcrumb>
{breadcrumbPath}
<BreadcrumbItem isActive style={{ maxWidth: 300 }}>
<Truncate content={runResource?.display_name ?? 'Loading...'} />
<Truncate content={run?.display_name ?? 'Loading...'} />
</BreadcrumbItem>
</Breadcrumb>
}
headerAction={
<PipelineRunDetailsActions
run={runResource}
onDelete={() => setDeleting(true)}
/>
<PipelineRunDetailsActions run={run} onDelete={() => setDeleting(true)} />
}
empty={false}
>
Expand Down Expand Up @@ -180,7 +193,7 @@ const PipelineRunDetails: PipelineCoreDetailsPageComponent = ({ breadcrumbPath,
</Drawer>
<DeletePipelineRunsModal
type={PipelineRunType.Archived}
toDeleteResources={deleting && runResource ? [runResource] : []}
toDeleteResources={deleting && run ? [run] : []}
onClose={(deleteComplete) => {
if (deleteComplete) {
navigate(contextPath ?? routePipelineRunsNamespace(namespace));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,19 @@ import PipelineRunDrawerRightTabs from '~/concepts/pipelines/content/pipelinesDe
import './PipelineRunDrawer.scss';
import { PipelineTask } from '~/concepts/pipelines/topology';
import { Execution } from '~/third_party/mlmd';
import { ArtifactNodeDrawerContent } from './artifacts';

type PipelineRunDrawerRightContentProps = {
task?: PipelineTask;
executions: Execution[];
upstreamTaskName?: string;
onClose: () => void;
};

const PipelineRunDrawerRightContent: React.FC<PipelineRunDrawerRightContentProps> = ({
task,
executions,
upstreamTaskName,
onClose,
}) => {
if (!task) {
Expand All @@ -35,18 +38,28 @@ const PipelineRunDrawerRightContent: React.FC<PipelineRunDrawerRightContentProps
minSize="500px"
data-testid="pipeline-run-drawer-right-content"
>
<DrawerHead>
<Title headingLevel="h2" size="xl">
{task.name} {task.type === 'artifact' ? 'Artifact details' : ''}
</Title>
{task.status?.podName && <Text component="small">{task.status.podName}</Text>}
<DrawerActions>
<DrawerCloseButton onClick={onClose} />
</DrawerActions>
</DrawerHead>
<DrawerPanelBody className="pipeline-run__drawer-panel-body pf-v5-u-pr-sm">
<PipelineRunDrawerRightTabs task={task} executions={executions} />
</DrawerPanelBody>
{task.type === 'artifact' ? (
<ArtifactNodeDrawerContent
upstreamTaskName={upstreamTaskName}
task={task}
onClose={onClose}
/>
) : (
<>
<DrawerHead>
<Title headingLevel="h2" size="xl">
{task.name}
</Title>
{task.status?.podName && <Text component="small">{task.status.podName}</Text>}
<DrawerActions>
<DrawerCloseButton onClick={onClose} />
</DrawerActions>
</DrawerHead>
<DrawerPanelBody className="pipeline-run__drawer-panel-body pf-v5-u-pr-sm">
<PipelineRunDrawerRightTabs task={task} executions={executions} />
</DrawerPanelBody>
</>
)}
</DrawerPanelContent>
);
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import React from 'react';
import { Link } from 'react-router-dom';

import {
Title,
Flex,
FlexItem,
Stack,
DescriptionList,
DescriptionListGroup,
DescriptionListTerm,
DescriptionListDescription,
} from '@patternfly/react-core';

import { Artifact } from '~/third_party/mlmd';
import { artifactsDetailsRoute } from '~/routes';
import { usePipelinesAPI } from '~/concepts/pipelines/context';
import { getArtifactName } from '~/pages/pipelines/global/experiments/artifacts/utils';
import PipelinesTableRowTime from '~/concepts/pipelines/content/tables/PipelinesTableRowTime';
import PipelineRunDrawerRightContent from '~/concepts/pipelines/content/pipelinesDetails/pipelineRun/PipelineRunDrawerRightContent';

type ArtifactNodeDetailsProps = Pick<
React.ComponentProps<typeof PipelineRunDrawerRightContent>,
'upstreamTaskName'
> & {
artifact: Artifact.AsObject;
};

export const ArtifactNodeDetails: React.FC<ArtifactNodeDetailsProps> = ({
artifact,
upstreamTaskName,
}) => {
const { namespace } = usePipelinesAPI();
const artifactName = getArtifactName(artifact);

return (
<Flex
spaceItems={{ default: 'spaceItems2xl' }}
direction={{ default: 'column' }}
className="pf-v5-u-pt-lg pf-v5-u-pb-lg"
>
<FlexItem>
<Stack hasGutter>
<Title headingLevel="h3">Artifact details</Title>
<DescriptionList
isHorizontal
horizontalTermWidthModifier={{ default: '15ch' }}
data-testid="artifact-details-description-list"
>
<DescriptionListGroup>
<DescriptionListTerm>Upstream task</DescriptionListTerm>
<DescriptionListDescription>{upstreamTaskName}</DescriptionListDescription>

<DescriptionListTerm>Artifact name</DescriptionListTerm>
<DescriptionListDescription>
<Link to={artifactsDetailsRoute(namespace, artifact.id)}>{artifactName}</Link>
</DescriptionListDescription>

<DescriptionListTerm>Artifact type</DescriptionListTerm>
<DescriptionListDescription>{artifact.type}</DescriptionListDescription>

<DescriptionListTerm>Created at</DescriptionListTerm>
<DescriptionListDescription>
<PipelinesTableRowTime date={new Date(artifact.createTimeSinceEpoch)} />
</DescriptionListDescription>
</DescriptionListGroup>
</DescriptionList>
</Stack>
</FlexItem>

<FlexItem>
<Stack hasGutter>
<Title headingLevel="h3">Artifact URI</Title>
<DescriptionList
isHorizontal
horizontalTermWidthModifier={{ default: '15ch' }}
data-testid="artifact-uri-description-list"
>
<DescriptionListGroup>
<DescriptionListTerm>{artifactName}</DescriptionListTerm>
<DescriptionListDescription>{artifact.uri}</DescriptionListDescription>
</DescriptionListGroup>
</DescriptionList>
</Stack>
</FlexItem>
</Flex>
);
};
Loading
Loading