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

Add execution details page #2772

Merged
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
11 changes: 10 additions & 1 deletion frontend/src/concepts/pipelines/apiHooks/mlmd/types.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
import { Context } from '~/third_party/mlmd';
import { Artifact, Context, ContextType, Event } from '~/third_party/mlmd';

export type MlmdContext = Context;

export type MlmdContextType = ContextType;

export enum MlmdContextTypes {
Gkrumbach07 marked this conversation as resolved.
Show resolved Hide resolved
RUN = 'system.PipelineRun',
}

// An artifact which has associated event.
// You can retrieve artifact name from event.path.steps[0].key
export interface LinkedArtifact {
event: Event;
artifact: Artifact;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import React from 'react';
import { usePipelinesAPI } from '~/concepts/pipelines/context';
import { GetArtifactTypesRequest } from '~/third_party/mlmd';
import useFetchState, { FetchState, FetchStateCallbackPromise } from '~/utilities/useFetchState';

export const useGetArtifactTypeMap = (): FetchState<Record<number, string>> => {
const { metadataStoreServiceClient } = usePipelinesAPI();

const call = React.useCallback<FetchStateCallbackPromise<Record<number, string>>>(async () => {
const request = new GetArtifactTypesRequest();

const res = await metadataStoreServiceClient.getArtifactTypes(request);

const artifactTypeMap: Record<number, string> = {};
res.getArtifactTypesList().forEach((artifactType) => {
artifactTypeMap[artifactType.getId()] = artifactType.getName();
});
return artifactTypeMap;
}, [metadataStoreServiceClient]);

return useFetchState(call, {});
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import React from 'react';
import { usePipelinesAPI } from '~/concepts/pipelines/context';
import {
GetEventsByExecutionIDsRequest,
GetEventsByExecutionIDsResponse,
} from '~/third_party/mlmd';
import useFetchState, { FetchState, FetchStateCallbackPromise } from '~/utilities/useFetchState';

export const useGetEventsByExecutionId = (
executionId?: string,
): 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]);

const response = await metadataStoreServiceClient.getEventsByExecutionIDs(request);

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

return useFetchState(call, null);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import React from 'react';
import { usePipelinesAPI } from '~/concepts/pipelines/context';
import { Execution, GetExecutionsByIDRequest } from '~/third_party/mlmd';
import useFetchState, { FetchState, FetchStateCallbackPromise } from '~/utilities/useFetchState';

export const useGetExecutionById = (executionId?: string): FetchState<Execution | null> => {
const { metadataStoreServiceClient } = usePipelinesAPI();

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

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

request.setExecutionIdsList([numberId]);

const response = await metadataStoreServiceClient.getExecutionsByID(request);

return response.getExecutionsList().length !== 0 ? response.getExecutionsList()[0] : null;
}, [executionId, metadataStoreServiceClient]);

return useFetchState(call, null);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import React from 'react';
import { LinkedArtifact } from '~/concepts/pipelines/apiHooks/mlmd/types';
import { usePipelinesAPI } from '~/concepts/pipelines/context';
import { Artifact, Event, GetArtifactsByIDRequest } from '~/third_party/mlmd';
import useFetchState, { FetchState, FetchStateCallbackPromise } from '~/utilities/useFetchState';

export const useGetLinkedArtifactsByEvents = (events: Event[]): FetchState<LinkedArtifact[]> => {
const { metadataStoreServiceClient } = usePipelinesAPI();

const call = React.useCallback<FetchStateCallbackPromise<LinkedArtifact[]>>(async () => {
const artifactIds = events
.filter((event) => event.getArtifactId())
.map((event) => event.getArtifactId());
const request = new GetArtifactsByIDRequest();

if (artifactIds.length === 0) {
return [];
}

request.setArtifactIdsList(artifactIds);

const response = await metadataStoreServiceClient.getArtifactsByID(request);

const artifactMap: Record<number, Artifact> = {};
response.getArtifactsList().forEach((artifact) => (artifactMap[artifact.getId()] = artifact));

return events.map((event) => {
const artifact = artifactMap[event.getArtifactId()];
return { event, artifact };
});
}, [events, metadataStoreServiceClient]);

return useFetchState(call, []);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import React from 'react';
import { MlmdContext, MlmdContextTypes } from '~/concepts/pipelines/apiHooks/mlmd/types';
import { useGetMlmdContextType } from '~/concepts/pipelines/apiHooks/mlmd/useGetMlmdContextType';
import { usePipelinesAPI } from '~/concepts/pipelines/context';
import { Execution } from '~/third_party/mlmd';
import { GetContextsByExecutionRequest } from '~/third_party/mlmd/generated/ml_metadata/proto/metadata_store_service_pb';
import useFetchState, { FetchState, FetchStateCallbackPromise } from '~/utilities/useFetchState';

const useGetMlmdContextByExecution = (
execution: Execution,
type?: MlmdContextTypes,
): FetchState<MlmdContext | null> => {
const { metadataStoreServiceClient } = usePipelinesAPI();
const executionId = execution.getId();
const [contextType] = useGetMlmdContextType(type);

const contextTypeId = contextType?.getId();

const call = React.useCallback<FetchStateCallbackPromise<MlmdContext | null>>(async () => {
const request = new GetContextsByExecutionRequest();

request.setExecutionId(executionId);

const response = await metadataStoreServiceClient.getContextsByExecution(request);

const result = response.getContextsList().filter((c) => c.getTypeId() === contextTypeId);

return result.length === 1 ? result[0] : null;
}, [executionId, metadataStoreServiceClient, contextTypeId]);

return useFetchState(call, null);
};

export const useGetPipelineRunContextByExecution = (
execution: Execution,
): FetchState<MlmdContext | null> => useGetMlmdContextByExecution(execution, MlmdContextTypes.RUN);
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import React from 'react';
import { MlmdContextType, MlmdContextTypes } from '~/concepts/pipelines/apiHooks/mlmd/types';
import { usePipelinesAPI } from '~/concepts/pipelines/context';
import { GetContextTypeRequest } from '~/third_party/mlmd';
import useFetchState, {
FetchState,
FetchStateCallbackPromise,
NotReadyError,
} from '~/utilities/useFetchState';

export const useGetMlmdContextType = (
type?: MlmdContextTypes,
): FetchState<MlmdContextType | null> => {
const { metadataStoreServiceClient } = usePipelinesAPI();

const call = React.useCallback<FetchStateCallbackPromise<MlmdContextType | null>>(async () => {
if (!type) {
return Promise.reject(new NotReadyError('No context type'));
}

const request = new GetContextTypeRequest();
request.setTypeName(type);
const res = await metadataStoreServiceClient.getContextType(request);
const contextType = res.getContextType() || null;
return contextType;
}, [metadataStoreServiceClient, type]);

return useFetchState(call, null);
};
12 changes: 12 additions & 0 deletions frontend/src/pages/pipelines/GlobalPipelineExecutionsRoutes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import {
executionsPageTitle,
} from '~/pages/pipelines/global/experiments/executions/const';
import GlobalExecutions from '~/pages/pipelines/global/experiments/executions/GlobalExecutions';
import ExecutionDetails from '~/pages/pipelines/global/experiments/executions/details/ExecutionDetails';
import GlobalPipelineCoreDetails from '~/pages/pipelines/global/GlobalPipelineCoreDetails';

const GlobalPipelineExecutionsRoutes: React.FC = () => (
<ProjectsRoutes>
Expand All @@ -22,6 +24,16 @@ const GlobalPipelineExecutionsRoutes: React.FC = () => (
}
>
<Route index element={<GlobalExecutions />} />
<Route
path=":executionId"
element={
<GlobalPipelineCoreDetails
BreadcrumbDetailsComponent={ExecutionDetails}
pageName="Executions"
redirectPath={executionsBaseRoute}
/>
}
/>
<Route path="*" element={<Navigate to="." />} />
</Route>
</ProjectsRoutes>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,24 @@
import React from 'react';
import { Icon, Tooltip } from '@patternfly/react-core';
import { Icon, Label, Tooltip } from '@patternfly/react-core';
import {
CheckCircleIcon,
ExclamationCircleIcon,
InProgressIcon,
OutlinedWindowRestoreIcon,
QuestionCircleIcon,
PendingIcon,
TimesCircleIcon,
} from '@patternfly/react-icons';
import { Execution } from '~/third_party/mlmd';

type ExecutionsTableRowStatusIconProps = {
type ExecutionStatusProps = {
status: Execution.State;
isIcon?: boolean;
};

const ExecutionsTableRowStatusIcon: React.FC<ExecutionsTableRowStatusIconProps> = ({ status }) => {
const ExecutionStatus: React.FC<ExecutionStatusProps> = ({ status, isIcon }) => {
let tooltip;
let icon;
let label;
switch (status) {
case Execution.State.COMPLETE:
icon = (
Expand All @@ -24,6 +27,11 @@ const ExecutionsTableRowStatusIcon: React.FC<ExecutionsTableRowStatusIconProps>
</Icon>
);
tooltip = 'Complete';
label = (
<Label color="green" icon={<CheckCircleIcon />}>
Complete
</Label>
);
break;
case Execution.State.CACHED:
icon = (
Expand All @@ -32,6 +40,11 @@ const ExecutionsTableRowStatusIcon: React.FC<ExecutionsTableRowStatusIconProps>
</Icon>
);
tooltip = 'Cached';
label = (
<Label color="cyan" icon={<OutlinedWindowRestoreIcon />}>
Cached
</Label>
);
break;
case Execution.State.CANCELED:
icon = (
Expand All @@ -40,6 +53,7 @@ const ExecutionsTableRowStatusIcon: React.FC<ExecutionsTableRowStatusIconProps>
</Icon>
);
tooltip = 'Canceled';
label = <Label icon={<TimesCircleIcon />}>Canceled</Label>;
break;
case Execution.State.FAILED:
icon = (
Expand All @@ -48,30 +62,32 @@ const ExecutionsTableRowStatusIcon: React.FC<ExecutionsTableRowStatusIconProps>
</Icon>
);
tooltip = 'Failed';
label = (
<Label color="red" icon={<ExclamationCircleIcon />}>
Failed
</Label>
);
break;
case Execution.State.RUNNING:
icon = <Icon isInProgress />;
tooltip = 'Running';
label = <Label icon={<InProgressIcon />}>Running</Label>;
break;
// TODO: change the icon here
case Execution.State.NEW:
icon = (
<Icon>
<QuestionCircleIcon />
<PendingIcon />
</Icon>
);
tooltip = 'New';
label = <Label icon={<PendingIcon />}>New</Label>;
break;
default:
icon = (
<Icon>
<QuestionCircleIcon />
</Icon>
);
tooltip = 'Unknown';
icon = <>Unknown</>;
label = <Label>Unknown</Label>;
}

return <Tooltip content={tooltip}>{icon}</Tooltip>;
return isIcon ? <Tooltip content={tooltip}>{icon}</Tooltip> : <>{label}</>;
};

export default ExecutionsTableRowStatusIcon;
export default ExecutionStatus;
Original file line number Diff line number Diff line change
@@ -1,23 +1,32 @@
import * as React from 'react';
import { Td, Tr } from '@patternfly/react-table';
import { Link } from 'react-router-dom';
import { Execution } from '~/third_party/mlmd';
import ExecutionsTableRowStatusIcon from '~/pages/pipelines/global/experiments/executions/ExecutionsTableRowStatusIcon';
import { getExecutionDisplayName } from '~/pages/pipelines/global/experiments/executions/utils';
import { executionDetailsRoute } from '~/routes';
import { usePipelinesAPI } from '~/concepts/pipelines/context';
import ExecutionStatus from '~/pages/pipelines/global/experiments/executions/ExecutionStatus';

type ExecutionsTableRowProps = {
obj: Execution;
};

const ExecutionsTableRow: React.FC<ExecutionsTableRowProps> = ({ obj }) => (
<Tr>
<Td dataLabel="Executions">
{obj.getCustomPropertiesMap().get('task_name')?.getStringValue() || '(No name)'}
</Td>
<Td dataLabel="Status">
<ExecutionsTableRowStatusIcon status={obj.getLastKnownState()} />
</Td>
<Td dataLabel="ID">{obj.getId()}</Td>
<Td dataLabel="Type">{obj.getType()}</Td>
</Tr>
);
const ExecutionsTableRow: React.FC<ExecutionsTableRowProps> = ({ obj }) => {
const { namespace } = usePipelinesAPI();
return (
<Tr>
<Td dataLabel="Executions">
<Link to={executionDetailsRoute(namespace, obj.getId().toString())}>
{getExecutionDisplayName(obj)}
</Link>
</Td>
<Td dataLabel="Status">
<ExecutionStatus isIcon status={obj.getLastKnownState()} />
</Td>
<Td dataLabel="ID">{obj.getId()}</Td>
<Td dataLabel="Type">{obj.getType()}</Td>
</Tr>
);
};

export default ExecutionsTableRow;
Loading
Loading