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

[Mapping][TaskInfo] V.2 - Update Task details to allow check information for child task execution #467

Merged
merged 5 commits into from
May 18, 2022
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
Expand Up @@ -9,14 +9,12 @@ import { LaunchPlanSpec } from 'models/Launch/types';
import { dashedValueString } from 'common/constants';
import { ExecutionMetadataLabels } from './constants';

const useStyles = makeStyles((theme: Theme) => {
return {
detailItem: {
flexShrink: 0,
marginLeft: theme.spacing(4),
},
};
});
const useStyles = makeStyles((theme: Theme) => ({
detailItem: {
flexShrink: 0,
marginLeft: theme.spacing(4),
},
}));

interface DetailItem {
className?: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { ExternalResource, LogsByPhase, NodeExecution } from 'models/Execution/t
import { endNodeId, startNodeId } from 'models/Node/constants';
import { Workflow, WorkflowId } from 'models/Workflow/types';
import * as React from 'react';
import { useMemo, useState } from 'react';
import { useEffect, useMemo, useState } from 'react';
import { useQuery, useQueryClient } from 'react-query';
import { NodeExecutionsContext } from '../contexts';
import { getGroupedLogs } from '../TaskExecutionsList/utils';
Expand Down Expand Up @@ -72,14 +72,25 @@ export const ExecutionWorkflowGraph: React.FC<ExecutionWorkflowGraphProps> = ({
}
: null;

const onCloseDetailsPanel = () => setSelectedNodes([]);
const onCloseDetailsPanel = () => {
setSelectedPhase(undefined);
setIsDetailsTabClosed(true);
setSelectedNodes([]);
};

const [selectedPhase, setSelectedPhase] = useState<TaskExecutionPhase | undefined>(undefined);
const [isDetailsTabClosed, setIsDetailsTabClosed] = useState<boolean>(!selectedExecution);

useEffect(() => {
setIsDetailsTabClosed(!selectedExecution);
}, [selectedExecution]);

const renderGraph = (workflow: Workflow) => (
<WorkflowGraph
onNodeSelectionChanged={onNodeSelectionChanged}
selectedPhase={selectedPhase}
onPhaseSelectionChanged={setSelectedPhase}
isDetailsTabClosed={isDetailsTabClosed}
nodeExecutionsById={nodeExecutionsById}
workflow={workflow}
/>
Expand All @@ -92,7 +103,7 @@ export const ExecutionWorkflowGraph: React.FC<ExecutionWorkflowGraphProps> = ({
{renderGraph}
</WaitForQuery>
</NodeExecutionsContext.Provider>
<DetailsPanel open={selectedExecution !== null} onClose={onCloseDetailsPanel}>
<DetailsPanel open={!!selectedExecution} onClose={onCloseDetailsPanel}>
{selectedExecution && (
<NodeExecutionDetailsPanelContent
onClose={onCloseDetailsPanel}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,12 @@ import { useTabState } from 'components/hooks/useTabState';
import { LocationDescriptor } from 'history';
import { PaginatedEntityResponse } from 'models/AdminEntity/types';
import { Workflow } from 'models/Workflow/types';
import { NodeExecution, NodeExecutionIdentifier, TaskExecution } from 'models/Execution/types';
import {
MapTaskExecution,
NodeExecution,
NodeExecutionIdentifier,
TaskExecution,
} from 'models/Execution/types';
import Skeleton from 'react-loading-skeleton';
import { useQuery, useQueryClient } from 'react-query';
import { Link as RouterLink } from 'react-router-dom';
Expand Down Expand Up @@ -233,7 +238,7 @@ export const NodeExecutionDetailsPanelContent: React.FC<NodeExecutionDetailsProp
const [isReasonsVisible, setReasonsVisible] = useState<boolean>(false);
const [dag, setDag] = useState<any>(null);
const [details, setDetails] = useState<NodeExecutionDetails | undefined>();
const [shouldShowTaskDetails, setShouldShowTaskDetails] = useState<boolean>(false); // TODO to be reused in https://github.com/flyteorg/flyteconsole/issues/312
const [selectedTaskExecution, setSelectedTaskExecution] = useState<MapTaskExecution | null>(null);

const isMounted = useRef(false);
useEffect(() => {
Expand Down Expand Up @@ -267,6 +272,10 @@ export const NodeExecutionDetailsPanelContent: React.FC<NodeExecutionDetailsProp
setReasonsVisible(false);
}, [nodeExecutionId]);

useEffect(() => {
setSelectedTaskExecution(null);
}, [nodeExecutionId, phase]);

const nodeExecution = nodeExecutionQuery.data;

const getWorkflowDag = async () => {
Expand Down Expand Up @@ -297,24 +306,17 @@ export const NodeExecutionDetailsPanelContent: React.FC<NodeExecutionDetailsProp
const reasons = getTaskExecutionDetailReasons(listTaskExecutionsQuery.data);

const onBackClick = () => {
setShouldShowTaskDetails(false);
setSelectedTaskExecution(null);
};

const headerTitle = useMemo(() => {
// TODO to be reused in https://github.com/flyteorg/flyteconsole/issues/312
// // eslint-disable-next-line no-useless-escape
// const regex = /\-([\w\s-]+)\-/; // extract string between first and last dash

// const mapTaskHeader = `${mapTask?.[0].externalId?.match(regex)?.[1]} of ${
// nodeExecutionId.nodeId
// }`;
// const header = shouldShowTaskDetails ? mapTaskHeader : nodeExecutionId.nodeId;
const header = nodeExecutionId.nodeId;
const mapTaskHeader = `${selectedTaskExecution?.taskIndex} of ${nodeExecutionId.nodeId}`;
const header = selectedTaskExecution ? mapTaskHeader : nodeExecutionId.nodeId;

return (
<Typography className={classnames(commonStyles.textWrapped, styles.title)} variant="h3">
<div>
{shouldShowTaskDetails && (
{!!selectedTaskExecution && (
<IconButton onClick={onBackClick} size="small">
<ArrowBackIos />
</IconButton>
Expand All @@ -326,7 +328,7 @@ export const NodeExecutionDetailsPanelContent: React.FC<NodeExecutionDetailsProp
</IconButton>
</Typography>
);
}, [nodeExecutionId, shouldShowTaskDetails]);
}, [nodeExecutionId, selectedTaskExecution]);

const isRunningPhase = useMemo(() => {
return (
Expand Down Expand Up @@ -370,9 +372,10 @@ export const NodeExecutionDetailsPanelContent: React.FC<NodeExecutionDetailsProp
const tabsContent: JSX.Element | null = nodeExecution ? (
<NodeExecutionTabs
nodeExecution={nodeExecution}
shouldShowTaskDetails={shouldShowTaskDetails}
selectedTaskExecution={selectedTaskExecution}
phase={phase}
taskTemplate={details?.taskTemplate}
onTaskSelected={setSelectedTaskExecution}
/>
) : null;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import * as React from 'react';
import { makeStyles } from '@material-ui/core/styles';
import { Tab, Tabs } from '@material-ui/core';
import { NodeExecution } from 'models/Execution/types';
import { MapTaskExecution, NodeExecution } from 'models/Execution/types';
import { TaskTemplate } from 'models/Task/types';
import { useTabState } from 'components/hooks/useTabState';
import { PanelSection } from 'components/common/PanelSection';
import { DumpJSON } from 'components/common/DumpJSON';
import { isMapTaskType } from 'models/Task/utils';
import { TaskExecutionPhase } from 'models/Execution/enums';
import { MapTaskExecutionDetails } from 'components/Executions/TaskExecutionsList/MapTaskExecutionDetails';
import { TaskVersionDetailsLink } from 'components/Entities/VersionDetails/VersionDetailsLink';
import { Identifier } from 'models/Common/types';
import { TaskExecutionsList } from '../../TaskExecutionsList/TaskExecutionsList';
Expand Down Expand Up @@ -42,10 +43,11 @@ const defaultTab = tabIds.executions;

export const NodeExecutionTabs: React.FC<{
nodeExecution: NodeExecution;
shouldShowTaskDetails: boolean;
selectedTaskExecution: MapTaskExecution | null;
onTaskSelected: (val: MapTaskExecution) => void;
phase?: TaskExecutionPhase;
taskTemplate?: TaskTemplate | null;
}> = ({ nodeExecution, shouldShowTaskDetails, taskTemplate, phase }) => {
}> = ({ nodeExecution, selectedTaskExecution, onTaskSelected, taskTemplate, phase }) => {
const styles = useStyles();
const tabState = useTabState(tabIds, defaultTab);

Expand All @@ -60,7 +62,15 @@ export const NodeExecutionTabs: React.FC<{
let tabContent: JSX.Element | null = null;
switch (tabState.value) {
case tabIds.executions: {
tabContent = <TaskExecutionsList nodeExecution={nodeExecution} phase={phase} />;
tabContent = selectedTaskExecution ? (
<MapTaskExecutionDetails taskExecution={selectedTaskExecution} />
) : (
<TaskExecutionsList
nodeExecution={nodeExecution}
onTaskSelected={onTaskSelected}
phase={phase}
/>
);
break;
}
case tabIds.inputs: {
Expand All @@ -82,11 +92,8 @@ export const NodeExecutionTabs: React.FC<{
}
}

const executionLabel = isMapTaskType(taskTemplate?.type)
? shouldShowTaskDetails
? 'Execution'
: 'Map Execution'
: 'Executions';
const executionLabel =
isMapTaskType(taskTemplate?.type) && !selectedTaskExecution ? 'Map Execution' : 'Executions';

return (
<>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { createMockNodeExecutions } from 'models/Execution/__mocks__/mockNodeExe
import { TaskType } from 'models/Task/constants';
import { createMockWorkflow } from 'models/__mocks__/workflowData';
import * as React from 'react';
import { mockExecution as mockTaskExecution } from 'models/Execution/__mocks__/mockTaskExecutionsData';
import { NodeExecutionTabs } from '../index';

const getMockNodeExecution = () => createMockNodeExecutions(1).executions[0];
Expand All @@ -20,26 +21,28 @@ describe('NodeExecutionTabs', () => {
const mockUseTabState = useTabState as jest.Mock<any>;
mockUseTabState.mockReturnValue({ onChange: jest.fn(), value: 'executions' });
describe('with map tasks', () => {
it('should display proper tab name when it was provided and shouldShow is TRUE', async () => {
it('should display proper tab name when it was provided and shouldShow is TRUE', () => {
const { queryByText, queryAllByRole } = render(
<NodeExecutionTabs
nodeExecution={nodeExecution}
shouldShowTaskDetails={true}
selectedTaskExecution={{ ...mockTaskExecution, taskIndex: 0 }}
phase={phase}
taskTemplate={taskTemplate}
onTaskSelected={jest.fn()}
/>,
);
expect(queryAllByRole('tab')).toHaveLength(4);
expect(queryByText('Execution')).toBeInTheDocument();
expect(queryByText('Executions')).toBeInTheDocument();
});

it('should display proper tab name when it was provided and shouldShow is FALSE', async () => {
it('should display proper tab name when it was provided and shouldShow is FALSE', () => {
const { queryByText, queryAllByRole } = render(
<NodeExecutionTabs
nodeExecution={nodeExecution}
shouldShowTaskDetails={false}
selectedTaskExecution={null}
phase={phase}
taskTemplate={taskTemplate}
onTaskSelected={jest.fn()}
/>,
);

Expand All @@ -49,9 +52,13 @@ describe('NodeExecutionTabs', () => {
});

describe('without map tasks', () => {
it('should display proper tab name when mapTask was not provided', async () => {
it('should display proper tab name when mapTask was not provided', () => {
const { queryAllByRole, queryByText } = render(
<NodeExecutionTabs nodeExecution={nodeExecution} shouldShowTaskDetails={false} />,
<NodeExecutionTabs
nodeExecution={nodeExecution}
selectedTaskExecution={null}
onTaskSelected={jest.fn()}
/>,
);

expect(queryAllByRole('tab')).toHaveLength(3);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ type ExecutionStatusBadgeProps =
| NodeExecutionStatusBadgeProps
| TaskExecutionStatusBadgeProps;

function getPhaseConstants(
export function getPhaseConstants(
type: 'workflow' | 'node' | 'task',
phase: WorkflowExecutionPhase | NodeExecutionPhase | TaskExecutionPhase,
) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import * as React from 'react';
import { MapTaskExecution } from 'models/Execution/types';
import { TaskExecutionPhase } from 'models/Execution/enums';
import { PanelSection } from 'components/common/PanelSection';
import { formatRetryAttempt, getTaskRetryAtemptsForIndex } from './utils';
import { TaskExecutionLogsCard } from './TaskExecutionLogsCard';

interface MapTaskExecutionDetailsProps {
taskExecution: MapTaskExecution;
}

/** Renders an individual map task execution attempts as part of a list */
export const MapTaskExecutionDetails: React.FC<MapTaskExecutionDetailsProps> = ({
taskExecution,
}) => {
const {
closure: { metadata },
taskIndex,
} = taskExecution;

const filteredResources = getTaskRetryAtemptsForIndex(
metadata?.externalResources ?? [],
taskIndex,
);

return (
<PanelSection>
{filteredResources.map((item) => {
const attempt = item.retryAttempt ?? 0;
const headerText = formatRetryAttempt(attempt);

return (
<div key={`card-${attempt}`}>
<TaskExecutionLogsCard
taskExecution={taskExecution}
headerText={headerText}
phase={item.phase ?? TaskExecutionPhase.UNDEFINED}
logs={item.logs ?? []}
/>
</div>
);
})}
</PanelSection>
);
};
Loading