Skip to content

Commit

Permalink
Add execution details page
Browse files Browse the repository at this point in the history
  • Loading branch information
DaoDaoNoCode committed May 3, 2024
1 parent 1aab9db commit a22e785
Show file tree
Hide file tree
Showing 21 changed files with 839 additions and 29 deletions.
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 {
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

0 comments on commit a22e785

Please sign in to comment.