Skip to content

Commit

Permalink
chore: separate nodeDetailsPanel + add story for taskExecutionsList (f…
Browse files Browse the repository at this point in the history
…lyteorg#351)

* chore: separate nodeDetailsPanel + add story for taskExecutionsList

Signed-off-by: Nastya Rusina <[email protected]>

* chore: fix retryAttemptCheck

Signed-off-by: Nastya Rusina <[email protected]>
  • Loading branch information
anrusina authored Apr 4, 2022
1 parent ce1f113 commit cb3c69b
Show file tree
Hide file tree
Showing 17 changed files with 256 additions and 153 deletions.
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

0 comments on commit cb3c69b

Please sign in to comment.