Skip to content

Commit

Permalink
Update graphview (flyteorg#176)
Browse files Browse the repository at this point in the history
* Checkin and merge to master

Signed-off-by: Jason Porter <[email protected]>

* Fix: removed console.log statements

Signed-off-by: Jason Porter <[email protected]>

* Fix: added color for node phase unknown

Signed-off-by: Jason Porter <[email protected]>

* Removed commented debugging code

Signed-off-by: Jason Porter <[email protected]>

Co-authored-by: Jason Porter <[email protected]>
  • Loading branch information
kumare3 and jsonporter authored Aug 16, 2021
1 parent e8f8cdb commit 950950d
Show file tree
Hide file tree
Showing 32 changed files with 31,380 additions and 561 deletions.
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM node:10 as builder
FROM node:14 as builder
LABEL org.opencontainers.image.source https://github.com/lyft/flyteconsole

WORKDIR /code/flyteconsole
Expand Down
28,623 changes: 28,623 additions & 0 deletions package-lock.json

Large diffs are not rendered by default.

6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
"chalk": "^2.0.1",
"cheerio": "^1.0.0-rc.2",
"cookie-parser": "^1.4.3",
"dagre-d3": "^0.6.4",
"dotenv": "^5.0.1",
"express": "^4.14.0",
"express-static-gzip": "^0.3.2",
Expand All @@ -54,6 +55,7 @@
"lodash": "^4.17.19",
"memory-fs": "^0.4.1",
"morgan": "^1.8.2",
"react-flow-renderer": "^9.6.3",
"react-helmet": "^5.1.3",
"react-responsive": "^4.1.0",
"react-transition-group": "^2.3.1",
Expand Down Expand Up @@ -150,7 +152,7 @@
"eslint-plugin-jest": "^24.1.3",
"eslint-plugin-react": "^7.21.5",
"eslint-plugin-react-hooks": "^4.2.0",
"favicons-webpack-plugin": "^1.0.2",
"favicons-webpack-plugin": "2.0",
"file-loader": "^1.1.11",
"fork-ts-checker-webpack-plugin": "^4.0.3",
"html-webpack-externals-plugin": "^3.7.0",
Expand Down Expand Up @@ -207,4 +209,4 @@
"resolutions": {
"micromatch": "^4.0.0"
}
}
}
3 changes: 3 additions & 0 deletions src/assets/SmallArrow.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions src/common/formatters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export function millisecondsToHMS(valueMS: number): string {
}

const duration = moment.duration(valueMS);
const parts = [];
const parts: string[] = [];

// Using asHours() because if it's greater than 24, we'll just show the total
if (duration.asHours() >= 1) {
Expand All @@ -95,7 +95,7 @@ export function durationToYMWDHMS(duration: moment.Duration): string {
return '';
}

const parts = [];
const parts: string[] = [];

if (duration.years() !== 0) {
parts.push(`${Math.abs(duration.years())}y`);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { WaitForQuery } from 'components/common/WaitForQuery';
import { DataError } from 'components/Errors/DataError';
import * as React from 'react';
import { useAllChildNodeExecutionGroupsQuery } from '../nodeExecutionQueries';
import { NodeExecutionsRequestConfigContext } from '../contexts';
import { ExecutionWorkflowGraph } from './ExecutionWorkflowGraph';
import { NodeExecution } from 'models/Execution/types';

export const ExecutionChildrenLoader = ({ nodeExecutions, workflowId }) => {
const requestConfig = React.useContext(NodeExecutionsRequestConfigContext);
const childGroupsQuery = useAllChildNodeExecutionGroupsQuery(
nodeExecutions,
requestConfig
);

const renderGraphComponent = childGroups => {
const output: any[] = [];
for (let i = 0; i < childGroups.length; i++) {
for (let j = 0; j < childGroups[i].length; j++) {
for (
let k = 0;
k < childGroups[i][j].nodeExecutions.length;
k++
) {
output.push(
childGroups[i][j].nodeExecutions[k] as NodeExecution
);
}
}
}
const executions: NodeExecution[] = output.concat(nodeExecutions);
return nodeExecutions.length > 0 ? (
<ExecutionWorkflowGraph
nodeExecutions={executions}
workflowId={workflowId}
/>
) : null;
};

return (
<WaitForQuery errorComponent={DataError} query={childGroupsQuery}>
{renderGraphComponent}
</WaitForQuery>
);
};
20 changes: 12 additions & 8 deletions src/components/Executions/ExecutionDetails/ExecutionNodeViews.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@ import { useTabState } from 'components/hooks/useTabState';
import { secondaryBackgroundColor } from 'components/Theme/constants';
import { Execution, NodeExecution } from 'models/Execution/types';
import * as React from 'react';
import { useState, useEffect } from 'react';
import { NodeExecutionsRequestConfigContext } from '../contexts';
import { ExecutionFilters } from '../ExecutionFilters';
import { useNodeExecutionFiltersState } from '../filters/useExecutionFiltersState';
import { NodeExecutionsTable } from '../Tables/NodeExecutionsTable';
import { tabs } from './constants';
import { ExecutionWorkflowGraph } from './ExecutionWorkflowGraph';
import { ExecutionChildrenLoader } from './ExecutionChildrenLoader';
import { useExecutionNodeViewsState } from './useExecutionNodeViewsState';

const useStyles = makeStyles((theme: Theme) => ({
Expand Down Expand Up @@ -42,6 +43,7 @@ export const ExecutionNodeViews: React.FC<ExecutionNodeViewsProps> = ({
const styles = useStyles();
const filterState = useNodeExecutionFiltersState();
const tabState = useTabState(tabs, tabs.nodes.id);
const [graphStateReady, setGraphStateReady] = useState(false);

/* We want to maintain the filter selection when switching away from the Nodes
tab and back, but do not want to filter the nodes when viewing the graph. So,
Expand All @@ -62,12 +64,14 @@ export const ExecutionNodeViews: React.FC<ExecutionNodeViewsProps> = ({
</NodeExecutionsRequestConfigContext.Provider>
);

const renderExecutionWorkflowGraph = (nodeExecutions: NodeExecution[]) => (
<ExecutionWorkflowGraph
nodeExecutions={nodeExecutions}
workflowId={execution.closure.workflowId}
/>
);
const renderExecutionLoader = (nodeExecutions: NodeExecution[]) => {
return (
<ExecutionChildrenLoader
nodeExecutions={nodeExecutions}
workflowId={execution.closure.workflowId}
/>
);
};

return (
<>
Expand All @@ -94,7 +98,7 @@ export const ExecutionNodeViews: React.FC<ExecutionNodeViewsProps> = ({
errorComponent={DataError}
query={nodeExecutionsQuery}
>
{renderExecutionWorkflowGraph}
{renderExecutionLoader}
</WaitForQuery>
)}
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import * as React from 'react';
import { useQuery, useQueryClient } from 'react-query';
import { NodeExecutionsContext } from '../contexts';
import { NodeExecutionDetailsPanelContent } from './NodeExecutionDetailsPanelContent';
import { TaskExecutionNodeRenderer } from './TaskExecutionNodeRenderer/TaskExecutionNodeRenderer';

export interface ExecutionWorkflowGraphProps {
nodeExecutions: NodeExecution[];
Expand Down Expand Up @@ -51,13 +50,13 @@ export const ExecutionWorkflowGraph: React.FC<ExecutionWorkflowGraphProps> = ({
const selectedExecution = selectedNodes.length
? nodeExecutionsById[selectedNodes[0]].id
: null;

const onCloseDetailsPanel = () => setSelectedNodes([]);

const renderGraph = (workflow: Workflow) => (
<WorkflowGraph
onNodeSelectionChanged={onNodeSelectionChanged}
nodeRenderer={TaskExecutionNodeRenderer}
selectedNodes={selectedNodes}
nodeExecutionsById={nodeExecutionsById}
workflow={workflow}
/>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ const NodeExecutionTabs: React.FC<{
const styles = useStyles();
const tabState = useTabState(tabIds, defaultTab);

let tabContent = null;
let tabContent: JSX.Element | null = null;
switch (tabState.value) {
case tabIds.executions: {
tabContent = <TaskExecutionsList nodeExecution={nodeExecution} />;
Expand Down
64 changes: 63 additions & 1 deletion src/components/Executions/nodeExecutionQueries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,7 @@ async function fetchGroupsForParentNodeExecution(
[nodeExecutionQueryParams.parentNodeId]: nodeExecution.id.nodeId
}
};

const children = await fetchNodeExecutionList(
queryClient,
nodeExecution.id.executionId,
Expand Down Expand Up @@ -252,7 +253,6 @@ function fetchChildNodeExecutionGroups(
config: RequestConfig
) {
const { workflowNodeMetadata } = nodeExecution.closure;

// Newer NodeExecution structures can directly indicate their parent
// status and have their children fetched in bulk.
if (isParentNode(nodeExecution)) {
Expand All @@ -278,6 +278,68 @@ function fetchChildNodeExecutionGroups(
return fetchGroupsForTaskExecutionNode(queryClient, nodeExecution, config);
}

/**
* Query returns all children for a list of `nodeExecutions`
* Note: diffrent from fetchGroupsForParentNodeExecution in that it expects a
* list of nodeExecitions
*/
async function fetchAllChildNodeExecutions(
queryClient: QueryClient,
nodeExecutions: NodeExecution[],
config: RequestConfig
): Promise<Array<NodeExecutionGroup[]>> {
const executions: Array<NodeExecutionGroup[]> = await Promise.all(
nodeExecutions.map(exe => {
return fetchChildNodeExecutionGroups(queryClient, exe, config);
})
);
return executions;
}

/**
*
* @param nodeExecutions list of parent node executionId's
* @param config
* @returns
*/
export function useAllChildNodeExecutionGroupsQuery(
nodeExecutions: NodeExecution[],
config: RequestConfig
): QueryObserverResult<Array<NodeExecutionGroup[]>, Error> {
const queryClient = useQueryClient();
const shouldEnableFn = groups => {
if (nodeExecutions[0] && groups.length > 0) {
if (!nodeExecutionIsTerminal(nodeExecutions[0])) {
return true;
}
return groups.some(group => {
if (group.nodeExecutions?.length > 0) {
return group.nodeExecutions.some(ne => {
return !nodeExecutionIsTerminal(ne);
});
} else {
return false;
}
});
} else {
return false;
}
};

return useConditionalQuery<Array<NodeExecutionGroup[]>>(
{
queryKey: [
QueryType.NodeExecutionChildList,
nodeExecutions[0]?.id,
config
],
queryFn: () =>
fetchAllChildNodeExecutions(queryClient, nodeExecutions, config)
},
shouldEnableFn
);
}

/** Fetches and groups `NodeExecution`s which are direct children of the given
* `NodeExecution`.
*/
Expand Down
67 changes: 21 additions & 46 deletions src/components/WorkflowGraph/WorkflowGraph.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,25 @@
import { NonIdealState } from 'components/common/NonIdealState';
import { Graph } from 'components/flytegraph/Graph';
import { NodeRenderer } from 'components/flytegraph/types';
import { keyBy } from 'lodash';
import { convertFlyteGraphToDAG } from 'models/Graph/convertFlyteGraphToDAG';
import { DAGNode } from 'models/Graph/types';
import { CompiledNode } from 'models/Node/types';
import { transformerWorkflowToDAG } from './transformerWorkflowToDAG';
import { dNode } from 'models/Graph/types';
import { Workflow } from 'models/Workflow/types';
import * as React from 'react';
import ReactFlowGraphComponent from 'components/flytegraph/ReactFlow/ReactFlowGraphComponent';
import { Error } from 'models/Common/types';

export interface WorkflowGraphProps {
nodeRenderer: NodeRenderer<DAGNode>;
onNodeSelectionChanged: (selectedNodes: string[]) => void;
selectedNodes?: string[];
workflow: Workflow;
nodeExecutionsById?: any;
}

interface WorkflowGraphState {
dag: DAGNode[];
nodesById: Record<string, CompiledNode>;
dag: dNode | null;
error?: Error;
}

interface PrepareDAGResult {
dag: DAGNode[];
dag: dNode | null;
error?: Error;
nodesById: Record<string, CompiledNode>;
}

function workflowToDag(workflow: Workflow): PrepareDAGResult {
Expand All @@ -36,56 +31,36 @@ function workflowToDag(workflow: Workflow): PrepareDAGResult {
throw new Error('Workflow closure missing a compiled workflow');
}
const { compiledWorkflow } = workflow.closure;
const nodesById = keyBy(compiledWorkflow.primary.template.nodes, 'id');
const dag = convertFlyteGraphToDAG(compiledWorkflow);
return { dag, nodesById };
const dag: dNode = transformerWorkflowToDAG(compiledWorkflow);
return { dag };
} catch (e) {
return {
dag: [],
error: e,
nodesById: {}
dag: null,
error: e as Error
};
}
}

/** Uses flytegraph to render a graph representation of a workflow closure,
* pipes node details about selected nodes into the DetailsPanel
*/
export class WorkflowGraph extends React.Component<
WorkflowGraphProps,
WorkflowGraphState
> {
constructor(props: WorkflowGraphProps) {
constructor(props) {
super(props);
const { dag, error, nodesById } = workflowToDag(this.props.workflow);
this.state = { dag, error, nodesById };
const { dag, error } = workflowToDag(this.props.workflow);
this.state = { dag, error };
}

render() {
const { dag, error } = this.state;
const {
nodeRenderer,
onNodeSelectionChanged,
selectedNodes
} = this.props;
if (error) {
return (
<NonIdealState
title="Cannot render Workflow graph"
description={error.message}
/>
);
}
const { dag } = this.state;
const { onNodeSelectionChanged, nodeExecutionsById } = this.props;

return (
<>
<Graph
data={dag}
nodeRenderer={nodeRenderer}
onNodeSelectionChanged={onNodeSelectionChanged}
selectedNodes={selectedNodes}
/>
</>
<ReactFlowGraphComponent
nodeExecutionsById={nodeExecutionsById}
data={dag}
onNodeSelectionChanged={onNodeSelectionChanged}
/>
);
}
}
Loading

0 comments on commit 950950d

Please sign in to comment.