Skip to content

Commit

Permalink
feat: Remove intermediate NodeExecutionsTable row content (#75)
Browse files Browse the repository at this point in the history
* refactor: removing specialized rows and rendering only nodes

* refactor: moving contexts up to common folder

* refactor: use a data cache for nested node mapping

* refactor: update loading of workflow data

* fix: update usage of NodeExecutions in graph tab

* fix: update TaskExecutionDetails to use data cache

* fix: getting tests and stories working again

* chore: docs and cleanup

* test: use a more robust element query

* refactor: use filter instead of reduce

* docs: adding some missing function docs
  • Loading branch information
schottra committed Jun 30, 2020
1 parent 20f273d commit 1d4670e
Show file tree
Hide file tree
Showing 47 changed files with 859 additions and 1,073 deletions.
8 changes: 4 additions & 4 deletions src/components/Cache/createCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ function hasId(value: Object): value is HasIdObject {
* strictly equal (===). The cache provides methods for getting/setting values
* by key and for merging values or arrays of values with any existing items.
*/
export interface ValueCache {
export interface ValueCache<ValueType = object> {
/** Retrieve an item by key */
get(key: EntityKey): object | undefined;
get(key: EntityKey): ValueType | undefined;
/** Check existence of an item by key */
has(key: EntityKey): boolean;
/** Merge an array of values. If the items have an `id` property, its value
Expand All @@ -30,9 +30,9 @@ export interface ValueCache {
* performed. For arrays, the value is _replaced_.
* @returns The merged value
*/
mergeValue(key: EntityKey, value: object): object;
mergeValue(key: EntityKey, value: ValueType): ValueType;
/** Set an item value by key. Replaces any existing value. */
set(key: EntityKey, value: object): object;
set(key: EntityKey, value: ValueType): ValueType;
}

type Cacheable = object | any[];
Expand Down
26 changes: 16 additions & 10 deletions src/components/Executions/ExecutionDetails/ExecutionDetails.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
import { WaitForData, withRouteParams } from 'components/common';
import {
RefreshConfig,
useDataRefresher,
useWorkflowExecution
} from 'components/hooks';
import { RefreshConfig, useDataRefresher } from 'components/hooks';
import { Execution } from 'models';
import * as React from 'react';
import { executionRefreshIntervalMs } from '../constants';
import { ExecutionContext, ExecutionDataCacheContext } from '../contexts';
import { useExecutionDataCache } from '../useExecutionDataCache';
import { useWorkflowExecution } from '../useWorkflowExecution';
import { executionIsTerminal } from '../utils';
import { ExecutionContext } from './contexts';
import { ExecutionDetailsAppBarContent } from './ExecutionDetailsAppBarContent';
import { ExecutionNodeViews } from './ExecutionNodeViews';

Expand All @@ -35,15 +33,23 @@ export const ExecutionDetailsContainer: React.FC<ExecutionDetailsRouteParams> =
domain: domainId,
name: executionId
};

const { fetchable, terminateExecution } = useWorkflowExecution(id);
const dataCache = useExecutionDataCache();
const { fetchable, terminateExecution } = useWorkflowExecution(
id,
dataCache
);
useDataRefresher(id, fetchable, executionRefreshConfig);
const contextValue = { terminateExecution, execution: fetchable.value };
const contextValue = {
terminateExecution,
execution: fetchable.value
};
return (
<WaitForData {...fetchable}>
<ExecutionContext.Provider value={contextValue}>
<ExecutionDetailsAppBarContent execution={fetchable.value} />
<ExecutionNodeViews execution={fetchable.value} />
<ExecutionDataCacheContext.Provider value={dataCache}>
<ExecutionNodeViews execution={fetchable.value} />
</ExecutionDataCacheContext.Provider>
</ExecutionContext.Provider>
</WaitForData>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { NodeDetailsProps } from 'components/WorkflowGraph';
import { useStyles as useBaseStyles } from 'components/WorkflowGraph/NodeDetails/styles';

import { LiteralMapViewer } from 'components/Literals';
import { ExecutionContext } from '../contexts';
import { ExecutionContext } from '../../contexts';

/** Details panel renderer for the start/input node in a graph. Displays the
* top level `WorkflowExecution` inputs.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { SectionHeader, WaitForData } from 'components/common';
import { useCommonStyles } from 'components/common/styles';
import { useWorkflowExecutionData } from 'components/hooks';
import { LiteralMapViewer, RemoteLiteralMapViewer } from 'components/Literals';
import { NodeDetailsProps } from 'components/WorkflowGraph';
import { useStyles as useBaseStyles } from 'components/WorkflowGraph/NodeDetails/styles';
import { emptyLiteralMapBlob, Execution } from 'models';
import * as React from 'react';
import { ExecutionContext } from '../contexts';
import { ExecutionContext } from '../../contexts';
import { useWorkflowExecutionData } from '../../useWorkflowExecution';

const RemoteExecutionOutputs: React.FC<{ execution: Execution }> = ({
execution
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
TaskNodeDetails
} from 'components/WorkflowGraph';
import { useStyles as useBaseStyles } from 'components/WorkflowGraph/NodeDetails/styles';
import { NodeExecutionsContext } from '../contexts';
import { NodeExecutionsContext } from '../../contexts';
import { NodeExecutionData } from '../NodeExecutionData';
import { NodeExecutionInputs } from '../NodeExecutionInputs';
import { NodeExecutionOutputs } from '../NodeExecutionOutputs';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ import { WaitForData } from 'components/common';
import { useTabState } from 'components/hooks/useTabState';
import { Execution } from 'models';
import * as React from 'react';
import { NodeExecutionsRequestConfigContext } from '../contexts';
import { ExecutionFilters } from '../ExecutionFilters';
import { useNodeExecutionFiltersState } from '../filters/useExecutionFiltersState';
import { NodeExecutionsTable } from '../Tables/NodeExecutionsTable';
import { useWorkflowExecutionState } from '../useWorkflowExecutionState';
import { NodeExecutionsRequestConfigContext } from './contexts';
import { ExecutionWorkflowGraph } from './ExecutionWorkflowGraph';

const useStyles = makeStyles((theme: Theme) => ({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import { DetailsPanel } from 'components/common';
import { WorkflowGraph } from 'components/WorkflowGraph';
import { keyBy } from 'lodash';
import { endNodeId, startNodeId } from 'models';
import { endNodeId, NodeExecution, startNodeId } from 'models';
import { Workflow } from 'models/Workflow';
import * as React from 'react';
import { DetailedNodeExecution } from '../types';
import { NodeExecutionsContext } from './contexts';
import { NodeExecutionsContext } from '../contexts';
import { useDetailedNodeExecutions } from '../useDetailedNodeExecutions';
import { NodeExecutionDetails } from './NodeExecutionDetails';
import { TaskExecutionNodeRenderer } from './TaskExecutionNodeRenderer/TaskExecutionNodeRenderer';

export interface ExecutionWorkflowGraphProps {
nodeExecutions: DetailedNodeExecution[];
nodeExecutions: NodeExecution[];
workflow: Workflow;
}

Expand All @@ -19,9 +19,10 @@ export const ExecutionWorkflowGraph: React.FC<ExecutionWorkflowGraphProps> = ({
nodeExecutions,
workflow
}) => {
const detailedNodeExecutions = useDetailedNodeExecutions(nodeExecutions);
const nodeExecutionsById = React.useMemo(
() => keyBy(nodeExecutions, 'id.nodeId'),
[nodeExecutions]
() => keyBy(detailedNodeExecutions, 'id.nodeId'),
[detailedNodeExecutions]
);
const [selectedNodes, setSelectedNodes] = React.useState<string[]>([]);
const onNodeSelectionChanged = (newSelection: string[]) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { TaskNodeRenderer } from 'components/WorkflowGraph/TaskNodeRenderer';
import { NodeExecutionPhase } from 'models/Execution/enums';
import { DAGNode } from 'models/Graph';

import { NodeExecutionsContext } from '../contexts';
import { NodeExecutionsContext } from '../../contexts';
import { StatusIndicator } from './StatusIndicator';

/** Renders DAGNodes with colors based on their node type, as well as dots to
Expand Down
2 changes: 1 addition & 1 deletion src/components/Executions/ExecutionInputsOutputsModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ import { Dialog, DialogContent, Tab, Tabs } from '@material-ui/core';
import { makeStyles, Theme } from '@material-ui/core/styles';
import { WaitForData } from 'components/common';
import { ClosableDialogTitle } from 'components/common/ClosableDialogTitle';
import { useWorkflowExecutionData } from 'components/hooks';
import { LiteralMapViewer, RemoteLiteralMapViewer } from 'components/Literals';
import { emptyLiteralMapBlob, Execution } from 'models';
import * as React from 'react';
import { useWorkflowExecutionData } from './useWorkflowExecution';

const useStyles = makeStyles((theme: Theme) => ({
content: {
Expand Down
162 changes: 40 additions & 122 deletions src/components/Executions/Tables/NodeExecutionChildren.tsx
Original file line number Diff line number Diff line change
@@ -1,132 +1,50 @@
import { WaitForData } from 'components/common';
import {
RefreshConfig,
useDataRefresher,
useWorkflowExecution
} from 'components/hooks';
import { isEqual } from 'lodash';
import { Execution, NodeExecutionClosure, WorkflowNodeMetadata } from 'models';
import { Typography } from '@material-ui/core';
import * as classnames from 'classnames';
import { useExpandableMonospaceTextStyles } from 'components/common/ExpandableMonospaceText';
import * as React from 'react';
import { executionIsTerminal, executionRefreshIntervalMs } from '..';
import { ExecutionContext } from '../ExecutionDetails/contexts';
import { DetailedNodeExecution } from '../types';
import { useDetailedTaskExecutions } from '../useDetailedTaskExecutions';
import {
useTaskExecutions,
useTaskExecutionsRefresher
} from '../useTaskExecutions';
import { generateRowSkeleton } from './generateRowSkeleton';
import { NoExecutionsContent } from './NoExecutionsContent';
import { useColumnStyles } from './styles';
import { generateColumns as generateTaskExecutionColumns } from './taskExecutionColumns';
import { TaskExecutionRow } from './TaskExecutionRow';
import { generateColumns as generateWorkflowExecutionColumns } from './workflowExecutionColumns';
import { WorkflowExecutionRow } from './WorkflowExecutionRow';
import { DetailedNodeExecutionGroup } from '../types';
import { NodeExecutionRow } from './NodeExecutionRow';
import { useExecutionTableStyles } from './styles';

export interface NodeExecutionChildrenProps {
execution: DetailedNodeExecution;
childGroups: DetailedNodeExecutionGroup[];
}

const TaskNodeExecutionChildren: React.FC<NodeExecutionChildrenProps> = ({
execution: nodeExecution
/** Renders a nested list of row items for children of a NodeExecution */
export const NodeExecutionChildren: React.FC<NodeExecutionChildrenProps> = ({
childGroups
}) => {
const taskExecutions = useDetailedTaskExecutions(
useTaskExecutions(nodeExecution.id)
);
useTaskExecutionsRefresher(nodeExecution, taskExecutions);

const columnStyles = useColumnStyles();
// Memoizing columns so they won't be re-generated unless the styles change
const { columns, Skeleton } = React.useMemo(() => {
const columns = generateTaskExecutionColumns(columnStyles);
return { columns, Skeleton: generateRowSkeleton(columns) };
}, [columnStyles]);
const showNames = childGroups.length > 1;
const tableStyles = useExecutionTableStyles();
const monospaceTextStyles = useExpandableMonospaceTextStyles();
return (
<WaitForData
spinnerVariant="medium"
loadingComponent={Skeleton}
{...taskExecutions}
>
{taskExecutions.value.length ? (
taskExecutions.value.map(taskExecution => (
<TaskExecutionRow
columns={columns}
key={taskExecution.cacheKey}
execution={taskExecution}
nodeExecution={nodeExecution}
<>
{childGroups.map(({ name, nodeExecutions }) => {
const rows = nodeExecutions.map(nodeExecution => (
<NodeExecutionRow
key={nodeExecution.cacheKey}
execution={nodeExecution}
/>
))
) : (
<NoExecutionsContent />
)}
</WaitForData>
);
};

interface WorkflowNodeExecution extends DetailedNodeExecution {
closure: NodeExecutionClosure & {
workflowNodeMetadata: WorkflowNodeMetadata;
};
}
interface WorkflowNodeExecutionChildrenProps
extends NodeExecutionChildrenProps {
execution: WorkflowNodeExecution;
}

const executionRefreshConfig: RefreshConfig<Execution> = {
interval: executionRefreshIntervalMs,
valueIsFinal: executionIsTerminal
};

const WorkflowNodeExecutionChildren: React.FC<WorkflowNodeExecutionChildrenProps> = ({
execution
}) => {
const { executionId } = execution.closure.workflowNodeMetadata;
const workflowExecution = useWorkflowExecution(executionId).fetchable;
useDataRefresher(executionId, workflowExecution, executionRefreshConfig);

const columnStyles = useColumnStyles();
// Memoizing columns so they won't be re-generated unless the styles change
const { columns, Skeleton } = React.useMemo(() => {
const columns = generateWorkflowExecutionColumns(columnStyles);
return { columns, Skeleton: generateRowSkeleton(columns) };
}, [columnStyles]);
return (
<WaitForData
spinnerVariant="medium"
loadingComponent={Skeleton}
{...workflowExecution}
>
{workflowExecution.value ? (
<WorkflowExecutionRow
columns={columns}
execution={workflowExecution.value}
/>
) : (
<NoExecutionsContent />
)}
</WaitForData>
));
const key = `group-${name}`;
return showNames ? (
<div key={key}>
<div className={tableStyles.row}>
<Typography variant="overline">{name}</Typography>
</div>
<div
className={classnames(
tableStyles.childrenContainer,
monospaceTextStyles.nestedParent
)}
>
{rows}
</div>
</div>
) : (
<div key={key}>{rows}</div>
);
})}
</>
);
};

/** Renders a nested list of row items for children of a NodeExecution */
export const NodeExecutionChildren: React.FC<NodeExecutionChildrenProps> = props => {
const { workflowNodeMetadata } = props.execution.closure;
const { execution: topExecution } = React.useContext(ExecutionContext);

// Nested NodeExecutions will sometimes have `workflowNodeMetadata` that
// points to the parent WorkflowExecution. We only want to expand workflow
// nodes that point to a different workflow execution
if (
workflowNodeMetadata &&
!isEqual(workflowNodeMetadata.executionId, topExecution.id)
) {
return (
<WorkflowNodeExecutionChildren
{...props}
execution={props.execution as WorkflowNodeExecution}
/>
);
}
return <TaskNodeExecutionChildren {...props} />;
};
Loading

0 comments on commit 1d4670e

Please sign in to comment.