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

chore: separate nodeDetailsPanel + add story for taskExecutionsList #351

Merged
merged 2 commits into from
Apr 4, 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
3 changes: 2 additions & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,10 @@ module.exports = {
'<rootDir>/assetsTransformer.js',
},
coverageDirectory: '.coverage',
collectCoverageFrom: ['**/*.{ts,tsx}', '!**/*/*.stories.tsx'],
collectCoverageFrom: ['**/*.{ts,tsx}', '!**/*/*.stories.{ts,tsx}', '!**/*/*.mocks.{ts,tsx}'],
coveragePathIgnorePatterns: [
'__stories__',
'__mocks__',
'<rootDir>/.storybook',
'<rootDir>/node_modules',
'<rootDir>/dist',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import * as React from 'react';
import { useEffect, useRef } from 'react';
import { IconButton, Typography } from '@material-ui/core';
import { IconButton, Typography, Tab, Tabs } from '@material-ui/core';
import { makeStyles, Theme } from '@material-ui/core/styles';
import Tab from '@material-ui/core/Tab';
import Tabs from '@material-ui/core/Tabs';
import Close from '@material-ui/icons/Close';
import classnames from 'classnames';
import { useCommonStyles } from 'components/common/styles';
Expand All @@ -15,27 +14,25 @@ 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 { TaskTemplate } from 'models/Task/types';
import * as React from 'react';
import Skeleton from 'react-loading-skeleton';
import { useQuery, useQueryClient } from 'react-query';
import { Link as RouterLink } from 'react-router-dom';
import { Routes } from 'routes/routes';
import { RemoteLiteralMapViewer } from 'components/Literals/RemoteLiteralMapViewer';
import { fetchWorkflow } from 'components/Workflow/workflowQueries';
import { PanelSection } from 'components/common/PanelSection';
import { DumpJSON } from 'components/common/DumpJSON';
import { dNode } from 'models/Graph/types';
import { NodeExecutionPhase } from 'models/Execution/enums';
import { transformWorkflowToKeyedDag, getNodeNameFromDag } from 'components/WorkflowGraph/utils';
import { NodeExecutionCacheStatus } from '../NodeExecutionCacheStatus';
import { makeListTaskExecutionsQuery, makeNodeExecutionQuery } from '../nodeExecutionQueries';
import { TaskExecutionsList } from '../TaskExecutionsList/TaskExecutionsList';
import { NodeExecutionDetails } from '../types';
import { useNodeExecutionContext } from '../contextProvider/NodeExecutionDetails';
import { NodeExecutionInputs } from './NodeExecutionInputs';
import { NodeExecutionOutputs } from './NodeExecutionOutputs';
import { NodeExecutionTaskDetails } from './NodeExecutionTaskDetails';
import { getTaskExecutionDetailReasons } from './utils';
import { ExpandableMonospaceText } from '../../common/ExpandableMonospaceText';
import { fetchWorkflowExecution } from '../useWorkflowExecution';
import { NodeExecutionTabs } from './NodeExecutionTabs';

const useStyles = makeStyles((theme: Theme) => {
const paddingVertical = `${theme.spacing(2)}px`;
Expand Down Expand Up @@ -127,8 +124,6 @@ const tabIds = {
task: 'task',
};

const defaultTab = tabIds.executions;

interface NodeExecutionDetailsProps {
nodeExecutionId: NodeExecutionIdentifier;
onClose?: () => void;
Expand Down Expand Up @@ -176,77 +171,32 @@ const ExecutionTypeDetails: React.FC<{
);
};

const NodeExecutionTabs: React.FC<{
nodeExecution: NodeExecution;
taskTemplate?: TaskTemplate | null;
}> = ({ nodeExecution, taskTemplate }) => {
const styles = useStyles();
const tabState = useTabState(tabIds, defaultTab);

if (tabState.value === tabIds.task && !taskTemplate) {
// Reset tab value, if task tab is selected, while no taskTemplate is avaible
// can happen when user switches between nodeExecutions without closing the drawer
tabState.onChange(() => {
/* */
}, defaultTab);
}

let tabContent: JSX.Element | null = null;
switch (tabState.value) {
case tabIds.executions: {
tabContent = <TaskExecutionsList nodeExecution={nodeExecution} />;
break;
}
case tabIds.inputs: {
tabContent = <NodeExecutionInputs execution={nodeExecution} />;
break;
}
case tabIds.outputs: {
tabContent = <NodeExecutionOutputs execution={nodeExecution} />;
break;
}
case tabIds.task: {
tabContent = taskTemplate ? <NodeExecutionTaskDetails taskTemplate={taskTemplate} /> : null;
break;
}
}
return (
<>
<Tabs {...tabState} className={styles.tabs}>
<Tab value={tabIds.executions} label="Executions" />
<Tab value={tabIds.inputs} label="Inputs" />
<Tab value={tabIds.outputs} label="Outputs" />
{!!taskTemplate && <Tab value={tabIds.task} label="Task" />}
</Tabs>
<div className={styles.content}>{tabContent}</div>
</>
);
};

const WorkflowTabs: React.FC<{
dagData: dNode;
nodeId: string;
}> = ({ dagData, nodeId }) => {
const styles = useStyles();
const tabState = useTabState(tabIds, tabIds.inputs);
const commonStyles = useCommonStyles();

let tabContent: JSX.Element | null = null;
const id = nodeId.slice(nodeId.lastIndexOf('-') + 1);
const taskTemplate = dagData[id].value.template;

switch (tabState.value) {
case tabIds.inputs: {
tabContent = taskTemplate ? (
<div className={commonStyles.detailsPanelCard}>
<div className={commonStyles.detailsPanelCardContent}>
<RemoteLiteralMapViewer blob={taskTemplate.interface.inputs} map={null} />
</div>
</div>
<PanelSection>
<RemoteLiteralMapViewer blob={taskTemplate.interface.inputs} map={null} />
</PanelSection>
) : null;
break;
}
case tabIds.task: {
tabContent = taskTemplate ? <NodeExecutionTaskDetails taskTemplate={taskTemplate} /> : null;
tabContent = taskTemplate ? (
<PanelSection>
<DumpJSON value={taskTemplate} />
</PanelSection>
) : null;
break;
}
}
Expand Down Expand Up @@ -340,7 +290,10 @@ export const NodeExecutionDetailsPanelContent: React.FC<NodeExecutionDetailsProp
const displayName = details?.displayName ?? <Skeleton />;

const isRunningPhase = React.useMemo(() => {
return nodeExecution?.closure.phase === 1 || nodeExecution?.closure.phase === 2;
return (
nodeExecution?.closure.phase === NodeExecutionPhase.QUEUED ||
nodeExecution?.closure.phase === NodeExecutionPhase.RUNNING
);
}, [nodeExecution]);

const handleReasonsVisibility = React.useCallback(() => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useCommonStyles } from 'components/common/styles';
import { PanelSection } from 'components/common/PanelSection';
import { WaitForData } from 'components/common/WaitForData';
import { useNodeExecutionData } from 'components/hooks/useNodeExecution';
import { RemoteLiteralMapViewer } from 'components/Literals/RemoteLiteralMapViewer';
Expand All @@ -7,22 +7,15 @@ import * as React from 'react';

/** Fetches and renders the input data for a given `NodeExecution` */
export const NodeExecutionInputs: React.FC<{ execution: NodeExecution }> = ({ execution }) => {
const commonStyles = useCommonStyles();
const executionData = useNodeExecutionData(execution.id);
return (
<WaitForData {...executionData}>
{() => (
<>
<div className={commonStyles.detailsPanelCard}>
<div className={commonStyles.detailsPanelCardContent}>
<RemoteLiteralMapViewer
map={executionData.value.fullInputs}
blob={executionData.value.inputs}
/>
</div>
</div>
</>
)}
<PanelSection>
<RemoteLiteralMapViewer
map={executionData.value.fullInputs}
blob={executionData.value.inputs}
/>
</PanelSection>
</WaitForData>
);
};
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useCommonStyles } from 'components/common/styles';
import { PanelSection } from 'components/common/PanelSection';
import { WaitForData } from 'components/common/WaitForData';
import { useNodeExecutionData } from 'components/hooks/useNodeExecution';
import { RemoteLiteralMapViewer } from 'components/Literals/RemoteLiteralMapViewer';
Expand All @@ -7,22 +7,15 @@ import * as React from 'react';

/** Fetches and renders the output data for a given `NodeExecution` */
export const NodeExecutionOutputs: React.FC<{ execution: NodeExecution }> = ({ execution }) => {
const commonStyles = useCommonStyles();
const executionData = useNodeExecutionData(execution.id);
return (
<WaitForData {...executionData}>
{() => (
<>
<div className={commonStyles.detailsPanelCard}>
<div className={commonStyles.detailsPanelCardContent}>
<RemoteLiteralMapViewer
map={executionData.value.fullOutputs}
blob={executionData.value.outputs}
/>
</div>
</div>
</>
)}
<PanelSection>
<RemoteLiteralMapViewer
map={executionData.value.fullOutputs}
blob={executionData.value.outputs}
/>
</PanelSection>
</WaitForData>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
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 { TaskTemplate } from 'models/Task/types';
import { useTabState } from 'components/hooks/useTabState';
import { PanelSection } from 'components/common/PanelSection';
import { DumpJSON } from 'components/common/DumpJSON';
import { TaskExecutionsList } from '../../TaskExecutionsList/TaskExecutionsList';
import { NodeExecutionInputs } from './NodeExecutionInputs';
import { NodeExecutionOutputs } from './NodeExecutionOutputs';

const useStyles = makeStyles((theme) => {
return {
content: {
overflowY: 'auto',
},
tabs: {
borderBottom: `1px solid ${theme.palette.divider}`,
},
};
});

const tabIds = {
executions: 'executions',
inputs: 'inputs',
outputs: 'outputs',
task: 'task',
};

const defaultTab = tabIds.executions;

export const NodeExecutionTabs: React.FC<{
nodeExecution: NodeExecution;
taskTemplate?: TaskTemplate | null;
}> = ({ nodeExecution, taskTemplate }) => {
const styles = useStyles();
const tabState = useTabState(tabIds, defaultTab);

if (tabState.value === tabIds.task && !taskTemplate) {
// Reset tab value, if task tab is selected, while no taskTemplate is avaible
// can happen when user switches between nodeExecutions without closing the drawer
tabState.onChange(() => {
/* */
}, defaultTab);
}

let tabContent: JSX.Element | null = null;
switch (tabState.value) {
case tabIds.executions: {
tabContent = <TaskExecutionsList nodeExecution={nodeExecution} />;
break;
}
case tabIds.inputs: {
tabContent = <NodeExecutionInputs execution={nodeExecution} />;
break;
}
case tabIds.outputs: {
tabContent = <NodeExecutionOutputs execution={nodeExecution} />;
break;
}
case tabIds.task: {
tabContent = taskTemplate ? (
<PanelSection>
<DumpJSON value={taskTemplate} />
</PanelSection>
) : null;
break;
}
}
return (
<>
<Tabs {...tabState} className={styles.tabs}>
<Tab value={tabIds.executions} label="Executions" />
<Tab value={tabIds.inputs} label="Inputs" />
<Tab value={tabIds.outputs} label="Outputs" />
{!!taskTemplate && <Tab value={tabIds.task} label="Task" />}
</Tabs>
<div className={styles.content}>{tabContent}</div>
</>
);
};

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { Protobuf } from 'flyteidl';
import { MessageFormat, ResourceType } from 'models/Common/types';
import { TaskExecutionPhase } from 'models/Execution/enums';
import { TaskExecution } from 'models/Execution/types';

import * as Long from 'long';

// we probably will create a new helper function in future, to make testing/storybooks closer to what we see in API Json responses
const getProtobufTimestampFromIsoTime = (isoDateTime: string): Protobuf.ITimestamp => {
const timeMs = Date.parse(isoDateTime);
const timestamp = new Protobuf.Timestamp();
timestamp.seconds = Long.fromInt(Math.floor(timeMs / 1000));
timestamp.nanos = (timeMs % 1000) * 1e6;
return timestamp;
};

export const MockPythonTaskExecution: TaskExecution = {
id: {
taskId: {
resourceType: ResourceType.TASK,
project: 'flytesnacks',
domain: 'development',
name: 'athena.workflows.example.say_hello',
version: 'v13',
},
nodeExecutionId: {
nodeId: 'ff65vi3y',
executionId: {
project: 'flytesnacks',
domain: 'development',
name: 'ogaayir2e3',
},
},
},
inputUri:
's3://flyte-demo/metadata/propeller/flytesnacks-development-ogaayir2e3/athenaworkflowsexamplesayhello/data/inputs.pb',
closure: {
outputUri:
's3://flyte-demo/metadata/propeller/flytesnacks-development-ogaayir2e3/athenaworkflowsexamplesayhello/data/0/outputs.pb',
phase: TaskExecutionPhase.SUCCEEDED,
logs: [
{
uri: 'https://console.aws.amazon.com/cloudwatch/home?region=us-east-2#logEventViewer:group=/aws/containerinsights/flyte-demo-2/application;stream=var.log.containers.ogaayir2e3-ff65vi3y-0_flytesnacks-development_ogaayir2e3-ff65vi3y-0-380d210ccaac45a6e2314a155822b36a67e044914069d01323bc18832487ac4a.log',
name: 'Cloudwatch Logs (User)',
messageFormat: MessageFormat.JSON,
},
],
createdAt: getProtobufTimestampFromIsoTime('2022-03-17T21:30:53.469624134Z'),
updatedAt: getProtobufTimestampFromIsoTime('2022-03-17T21:31:04.011303736Z'),
reason: 'task submitted to K8s',
taskType: 'python-task',
},
};
Loading