Skip to content

Commit

Permalink
feat: show details panel for failed node (#254)
Browse files Browse the repository at this point in the history
* feat: show details panel for failed node

Signed-off-by: Pianist038801 <steven@union.ai>

* feat: use DAG for details panel content

Signed-off-by: Pianist038801 <steven@union.ai>

* fix: failing tests

Signed-off-by: Pianist038801 <steven@union.ai>

* fix: failing tests

Signed-off-by: Pianist038801 <steven@union.ai>

* fix: compound nodeId bug

Signed-off-by: Pianist038801 <steven@union.ai>

* fix: feedback on the PR

Signed-off-by: Pianist038801 <steven@union.ai>

* chore: force casesensitive file rename to be consumed by git

Signed-off-by: Nastya Rusina <nastya@union.ai>

Co-authored-by: Pianist038801 <steven@union.ai>
Co-authored-by: Nastya Rusina <nastya@union.ai>
  • Loading branch information
3 people authored Jan 18, 2022
1 parent c0ff255 commit e276493
Show file tree
Hide file tree
Showing 9 changed files with 167 additions and 22 deletions.
9 changes: 9 additions & 0 deletions assetsTransformer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const path = require('path');

module.exports = {
process(_src, filename, _config, _options) {
return (
'module.exports = ' + JSON.stringify(path.basename(filename)) + ';'
);
}
};
4 changes: 4 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ module.exports = {
'^.+\\.(j|t)sx?$': 'ts-jest'
},
transformIgnorePatterns: ['<rootDir>/node_modules/(?!@flyteorg/flyteidl)'],
moduleNameMapper: {
'\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$':
'<rootDir>/assetsTransformer.js'
},
coverageDirectory: '.coverage',
collectCoverageFrom: ['**/*.{ts,tsx}'],
coveragePathIgnorePatterns: [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export const ExecutionWorkflowGraph: React.FC<ExecutionWorkflowGraphProps> = ({
makeWorkflowQuery(useQueryClient(), workflowId)
);
const nodeExecutionsById = React.useMemo(
() => keyBy(nodeExecutions, 'scopedId'),
() => keyBy(nodeExecutions, 'id.nodeId'),
[nodeExecutions]
);

Expand All @@ -36,10 +36,6 @@ export const ExecutionWorkflowGraph: React.FC<ExecutionWorkflowGraphProps> = ({
if (nodeId === startNodeId || nodeId === endNodeId) {
return false;
}
const execution = nodeExecutionsById[nodeId];
if (!execution) {
return false;
}
return true;
});
setSelectedNodes(validSelection);
Expand All @@ -48,7 +44,14 @@ export const ExecutionWorkflowGraph: React.FC<ExecutionWorkflowGraphProps> = ({
// Note: flytegraph allows multiple selection, but we only support showing
// a single item in the details panel
const selectedExecution = selectedNodes.length
? nodeExecutionsById[selectedNodes[0]].id
? nodeExecutionsById[selectedNodes[0]]
? nodeExecutionsById[selectedNodes[0]].id
: {
nodeId: selectedNodes[0],
executionId:
nodeExecutionsById[Object.keys(nodeExecutionsById)[0]].id
.executionId
}
: null;

const onCloseDetailsPanel = () => setSelectedNodes([]);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { useEffect, useState } from 'react';
import { IconButton, Typography } from '@material-ui/core';
import { makeStyles, Theme } from '@material-ui/core/styles';
import Tab from '@material-ui/core/Tab';
Expand All @@ -6,11 +7,13 @@ import Close from '@material-ui/icons/Close';
import * as classnames from 'classnames';
import { useCommonStyles } from 'components/common/styles';
import { InfoIcon } from 'components/common/Icons/InfoIcon';
import { bodyFontFamily, smallFontSize } from 'components/Theme/constants';
import { ExecutionStatusBadge } from 'components/Executions/ExecutionStatusBadge';
import { LocationState } from 'components/hooks/useLocationState';
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,
Expand All @@ -19,7 +22,7 @@ import {
import { TaskTemplate } from 'models/Task/types';
import * as React from 'react';
import Skeleton from 'react-loading-skeleton';
import { useQuery } from 'react-query';
import { useQuery, useQueryClient } from 'react-query';
import { Link as RouterLink } from 'react-router-dom';
import { Routes } from 'routes/routes';
import { NodeExecutionCacheStatus } from '../NodeExecutionCacheStatus';
Expand All @@ -35,11 +38,34 @@ import { NodeExecutionOutputs } from './NodeExecutionOutputs';
import { NodeExecutionTaskDetails } from './NodeExecutionTaskDetails';
import { getTaskExecutionDetailReasons } from './utils';
import { ExpandableMonospaceText } from '../../common/ExpandableMonospaceText';
import { fetchWorkflowExecution } from '../useWorkflowExecution';
import { RemoteLiteralMapViewer } from 'components/Literals/RemoteLiteralMapViewer';
import { fetchWorkflow } from 'components/Workflow/workflowQueries';
import { dNode } from 'models/Graph/types';
import {
transformWorkflowToKeyedDag,
getNodeNameFromDag
} from 'components/WorkflowGraph/utils';

const useStyles = makeStyles((theme: Theme) => {
const paddingVertical = `${theme.spacing(2)}px`;
const paddingHorizontal = `${theme.spacing(3)}px`;
return {
notRunStatus: {
alignItems: 'center',
backgroundColor: 'gray',
borderRadius: '4px',
color: theme.palette.text.primary,
display: 'flex',
flex: '0 0 auto',
height: theme.spacing(3),
fontSize: smallFontSize,
justifyContent: 'center',
textTransform: 'uppercase',
width: theme.spacing(11),
fontFamily: bodyFontFamily,
fontWeight: 'bold'
},
closeButton: {
marginLeft: theme.spacing(1)
},
Expand Down Expand Up @@ -218,13 +244,64 @@ const NodeExecutionTabs: React.FC<{
);
};

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>
) : null;
break;
}
case tabIds.task: {
tabContent = taskTemplate ? (
<NodeExecutionTaskDetails taskTemplate={taskTemplate} />
) : null;
break;
}
}
return (
<>
<Tabs {...tabState} className={styles.tabs}>
<Tab value={tabIds.inputs} label="Inputs" />
{!!taskTemplate && <Tab value={tabIds.task} label="Task" />}
</Tabs>
<div className={styles.content}>{tabContent}</div>
</>
);
};

/** DetailsPanel content which renders execution information about a given NodeExecution
*/
export const NodeExecutionDetailsPanelContent: React.FC<NodeExecutionDetailsProps> = ({
nodeExecutionId,
onClose
}) => {
const [mounted, setMounted] = useState(true);
useEffect(() => {
return () => {
setMounted(false);
};
}, []);
const queryClient = useQueryClient();
const [isReasonsVisible, setReasonsVisible] = React.useState(false);
const [dag, setDag] = React.useState<any>(null);
const nodeExecutionQuery = useQuery<NodeExecution, Error>({
...makeNodeExecutionQuery(nodeExecutionId),
// The selected NodeExecution has been fetched at this point, we don't want to
Expand All @@ -238,6 +315,26 @@ export const NodeExecutionDetailsPanelContent: React.FC<NodeExecutionDetailsProp

const nodeExecution = nodeExecutionQuery.data;

const getWorkflowDag = async () => {
const workflowExecution = await fetchWorkflowExecution(
queryClient,
nodeExecutionId.executionId
);
const workflowData: Workflow = await fetchWorkflow(
queryClient,
workflowExecution.closure.workflowId
);
if (workflowData) {
const keyedDag = transformWorkflowToKeyedDag(workflowData);
if (mounted) setDag(keyedDag);
}
};

if (!nodeExecution) {
getWorkflowDag();
} else {
if (dag) setDag(null);
}
const listTaskExecutionsQuery = useQuery<
PaginatedEntityResponse<TaskExecution>,
Error
Expand Down Expand Up @@ -299,7 +396,9 @@ export const NodeExecutionDetailsPanelContent: React.FC<NodeExecutionDetailsProp
</div>
)}
</div>
) : null;
) : (
<div className={styles.notRunStatus}>NOT RUN</div>
);

const detailsContent = nodeExecution ? (
<>
Expand All @@ -319,7 +418,6 @@ export const NodeExecutionDetailsPanelContent: React.FC<NodeExecutionDetailsProp
taskTemplate={taskTemplate}
/>
) : null;

return (
<section className={styles.container}>
<header className={styles.header}>
Expand Down Expand Up @@ -348,13 +446,19 @@ export const NodeExecutionDetailsPanelContent: React.FC<NodeExecutionDetailsProp
variant="subtitle1"
color="textSecondary"
>
{displayName}
{dag
? getNodeNameFromDag(dag, nodeExecutionId.nodeId)
: displayName}
</Typography>
{statusContent}
{detailsContent}
{!dag && detailsContent}
</div>
</header>
{tabsContent}
{dag ? (
<WorkflowTabs nodeId={nodeExecutionId.nodeId} dagData={dag} />
) : (
tabsContent
)}
</section>
);
};
4 changes: 2 additions & 2 deletions src/components/Workflow/StaticGraphContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useQuery, useQueryClient } from 'react-query';
import { makeWorkflowQuery } from './workflowQueries';
import { WaitForQuery } from 'components/common/WaitForQuery';
import { DataError } from 'components/Errors/DataError';
import { transformerWorkflowToDAG } from 'components/WorkflowGraph/transformerWorkflowToDAG';
import { transformerWorkflowToDag } from 'components/WorkflowGraph/transformerWorkflowToDag';
import { ReactFlowWrapper } from 'components/flytegraph/ReactFlow/ReactFlowWrapper';
import { ConvertFlyteDagToReactFlows } from 'components/flytegraph/ReactFlow/transformerDAGToReactFlow';
import { dNode } from 'models/Graph/types';
Expand All @@ -19,7 +19,7 @@ export const renderStaticGraph = props => {
const workflow = props.closure.compiledWorkflow;
const version = props.id.version;

const dag: dNode = transformerWorkflowToDAG(workflow);
const dag: dNode = transformerWorkflowToDag(workflow);
const rfGraphJson = ConvertFlyteDagToReactFlows({
root: dag,
maxRenderDepth: 0,
Expand Down
4 changes: 2 additions & 2 deletions src/components/WorkflowGraph/WorkflowGraph.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { transformerWorkflowToDAG } from './transformerWorkflowToDAG';
import { transformerWorkflowToDag } from './transformerWorkflowToDag';
import { dNode } from 'models/Graph/types';
import { Workflow } from 'models/Workflow/types';
import * as React from 'react';
Expand Down Expand Up @@ -31,7 +31,7 @@ function workflowToDag(workflow: Workflow): PrepareDAGResult {
throw new Error('Workflow closure missing a compiled workflow');
}
const { compiledWorkflow } = workflow.closure;
const dag: dNode = transformerWorkflowToDAG(compiledWorkflow);
const dag: dNode = transformerWorkflowToDag(compiledWorkflow);
return { dag };
} catch (e) {
return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import {
* @param context input can be either CompiledWorkflow or CompiledNode
* @returns Display name
*/
export const transformerWorkflowToDAG = (
export const transformerWorkflowToDag = (
workflow: CompiledWorkflowClosure
): dNode => {
const { primary } = workflow;
Expand Down Expand Up @@ -255,7 +255,7 @@ export const buildOutWorkflowEdges = (
const list = context.downstream[ingress].ids;
for (let i = 0; i < list.length; i++) {
const edge: dEdge = {
sourceId: nodeMap[ingress].dNode.scopedId,
sourceId: nodeMap[ingress] && nodeMap[ingress].dNode.scopedId,
targetId: nodeMap[list[i]].dNode.scopedId
};
root.edges.push(edge);
Expand Down
31 changes: 28 additions & 3 deletions src/components/WorkflowGraph/utils.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { Identifier } from 'models/Common/types';
import { endNodeId, startNodeId } from 'models/Node/constants';
import { CompiledWorkflow } from 'models/Workflow/types';
import { CompiledWorkflow, Workflow } from 'models/Workflow/types';
import { CompiledNode, TaskNode } from 'models/Node/types';
import { CompiledTask, TaskTemplate } from 'models/Task/types';
import { dTypes } from 'models/Graph/types';

import { dTypes, dNode } from 'models/Graph/types';
import { transformerWorkflowToDag } from './transformerWorkflowToDag';
/**
* @TODO these are dupes for testing, remove once tests fixed
*/
Expand Down Expand Up @@ -116,3 +116,28 @@ export const getTaskTypeFromCompiledNode = (
}
return null;
};

export const getNodeNameFromDag = (dagData: dNode, nodeId: string) => {
const id = nodeId.slice(nodeId.lastIndexOf('-') + 1);
const value = dagData[id].value;

if (value.taskNode) {
return value.taskNode.referenceId.name;
} else if (value.workflowNode) {
return value.workflowNode.subWorkflowRef.name;
}
return '';
};

export const transformWorkflowToKeyedDag = (workflow: Workflow) => {
if (!workflow.closure?.compiledWorkflow) return {};

const dagData = transformerWorkflowToDag(
workflow.closure?.compiledWorkflow
);
const data = {};
dagData.nodes.forEach(node => {
data[`${node.id}`] = node;
});
return data;
};
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { Legend } from './NodeStatusLegend';

/**
* Renders workflow graph using React Flow.
* @param props.data DAG from transformerWorkflowToDAG
* @param props.data DAG from transformerWorkflowToDag
* @returns ReactFlow Graph as <ReactFlowWrapper>
*/
const ReactFlowGraphComponent = props => {
Expand Down

0 comments on commit e276493

Please sign in to comment.