From 097d94b111d3f1dfbeb2245c74a35896146eaa2a Mon Sep 17 00:00:00 2001 From: Randy Schott <1815175+schottra@users.noreply.github.com> Date: Mon, 29 Jun 2020 11:29:15 -0700 Subject: [PATCH] fix: cleanup for dynamic tasks refactoring (#76) * test: creating dynamic task cases for NodeExecutionsTable stories * fix: styling for child group labels * fix: mock api context for NodeExecutionsTable stories * test: mock nodeExecutionData endpoint * chore: remove unused imports * fix: extract nodes from subworkflows as well * fix: adjust borders to make child groups more obvious * refactor: checkpoint for getting the nesting styles correct * refactor: adding logic for borders/spacing based on nesting/index * fix: correct workflow execution table row styles --- .../Tables/NodeExecutionChildren.tsx | 39 ++++++--- .../Executions/Tables/NodeExecutionRow.tsx | 79 ++++++++++++----- .../Executions/Tables/NodeExecutionsTable.tsx | 3 +- .../Tables/WorkflowExecutionsTable.tsx | 12 ++- .../NodeExecutionsTable.stories.tsx | 85 ++++++++++++------- src/components/Executions/Tables/styles.ts | 40 ++++----- src/components/Executions/Tables/utils.ts | 9 ++ src/components/Executions/utils.ts | 26 +++--- src/components/hooks/test/utils.test.ts | 79 +++++++++++++++++ src/components/hooks/useNodeExecution.ts | 12 +-- src/components/hooks/utils.ts | 28 ++++-- src/models/Common/types.ts | 5 +- .../__mocks__/mockTaskExecutionsData.ts | 59 ++++++++++--- src/models/Workflow/types.ts | 1 + 14 files changed, 348 insertions(+), 129 deletions(-) create mode 100644 src/components/hooks/test/utils.test.ts diff --git a/src/components/Executions/Tables/NodeExecutionChildren.tsx b/src/components/Executions/Tables/NodeExecutionChildren.tsx index 80dfe64df..ee3bb32f5 100644 --- a/src/components/Executions/Tables/NodeExecutionChildren.tsx +++ b/src/components/Executions/Tables/NodeExecutionChildren.tsx @@ -1,45 +1,62 @@ import { Typography } from '@material-ui/core'; import * as classnames from 'classnames'; -import { useExpandableMonospaceTextStyles } from 'components/common/ExpandableMonospaceText'; +import { useTheme } from 'components/Theme/useTheme'; import * as React from 'react'; import { DetailedNodeExecutionGroup } from '../types'; import { NodeExecutionRow } from './NodeExecutionRow'; import { useExecutionTableStyles } from './styles'; +import { calculateNodeExecutionRowLeftSpacing } from './utils'; export interface NodeExecutionChildrenProps { childGroups: DetailedNodeExecutionGroup[]; + level: number; } /** Renders a nested list of row items for children of a NodeExecution */ export const NodeExecutionChildren: React.FC = ({ - childGroups + childGroups, + level }) => { const showNames = childGroups.length > 1; const tableStyles = useExecutionTableStyles(); - const monospaceTextStyles = useExpandableMonospaceTextStyles(); + const theme = useTheme(); + const childGroupLabelStyle = { + // The label is aligned with the parent above, so remove one level of spacing + marginLeft: `${calculateNodeExecutionRowLeftSpacing( + level - 1, + theme.spacing + )}px` + }; return ( <> - {childGroups.map(({ name, nodeExecutions }) => { - const rows = nodeExecutions.map(nodeExecution => ( + {childGroups.map(({ name, nodeExecutions }, groupIndex) => { + const rows = nodeExecutions.map((nodeExecution, index) => ( )); const key = `group-${name}`; return showNames ? (
-
- {name} -
0 }, + tableStyles.borderBottom, + tableStyles.childGroupLabel )} + style={childGroupLabelStyle} > - {rows} + + {name} +
+
{rows}
) : (
{rows}
diff --git a/src/components/Executions/Tables/NodeExecutionRow.tsx b/src/components/Executions/Tables/NodeExecutionRow.tsx index 41205b3bb..ecafd66a8 100644 --- a/src/components/Executions/Tables/NodeExecutionRow.tsx +++ b/src/components/Executions/Tables/NodeExecutionRow.tsx @@ -1,5 +1,5 @@ import * as classnames from 'classnames'; -import { useExpandableMonospaceTextStyles } from 'components/common/ExpandableMonospaceText'; +import { useTheme } from 'components/Theme/useTheme'; import * as React from 'react'; import { ExecutionContext, @@ -13,17 +13,23 @@ import { ExpandableExecutionError } from './ExpandableExecutionError'; import { NodeExecutionChildren } from './NodeExecutionChildren'; import { RowExpander } from './RowExpander'; import { selectedClassName, useExecutionTableStyles } from './styles'; +import { calculateNodeExecutionRowLeftSpacing } from './utils'; interface NodeExecutionRowProps { + index: number; execution: DetailedNodeExecution; + level?: number; style?: React.CSSProperties; } /** Renders a NodeExecution as a row inside a `NodeExecutionsTable` */ export const NodeExecutionRow: React.FC = ({ execution: nodeExecution, + index, + level = 0, style }) => { + const theme = useTheme(); const { columns, state } = React.useContext(NodeExecutionsTableContext); const requestConfig = React.useContext(NodeExecutionsRequestConfigContext); const { execution: workflowExecution } = React.useContext(ExecutionContext); @@ -33,6 +39,17 @@ export const NodeExecutionRow: React.FC = ({ setExpanded(!expanded); }; + // For the first level, we want the borders to span the entire table, + // so we'll use padding to space the content. For nested rows, we want the + // border to start where the content does, so we'll use margin. + const spacingProp = level === 0 ? 'paddingLeft' : 'marginLeft'; + const rowContentStyle = { + [spacingProp]: `${calculateNodeExecutionRowLeftSpacing( + level, + theme.spacing + )}px` + }; + // TODO: Handle error case for loading children. // Maybe show an expander in that case and make the content the error? const childNodeExecutions = useChildNodeExecutions({ @@ -47,7 +64,6 @@ export const NodeExecutionRow: React.FC = ({ const isExpandable = detailedChildNodeExecutions.length > 0; const tableStyles = useExecutionTableStyles(); - const monospaceTextStyles = useExpandableMonospaceTextStyles(); const selected = state.selectedExecution ? state.selectedExecution === nodeExecution @@ -64,17 +80,16 @@ export const NodeExecutionRow: React.FC = ({ const extraContent = expanded ? (
- {errorContent} - +
- ) : ( - errorContent - ); + ) : null; return (
= ({ })} style={style} > -
-
{expanderContent}
- {columns.map(({ className, key: columnKey, cellRenderer }) => ( +
0 && expanded), + [tableStyles.borderTop]: level > 0 && index > 0 + })} + style={rowContentStyle} + > +
- {cellRenderer({ - state, - execution: nodeExecution - })} + {expanderContent}
- ))} + {columns.map( + ({ className, key: columnKey, cellRenderer }) => ( +
+ {cellRenderer({ + state, + execution: nodeExecution + })} +
+ ) + )} +
+ {errorContent}
{extraContent}
diff --git a/src/components/Executions/Tables/NodeExecutionsTable.tsx b/src/components/Executions/Tables/NodeExecutionsTable.tsx index 907b523ae..9d9ed3eae 100644 --- a/src/components/Executions/Tables/NodeExecutionsTable.tsx +++ b/src/components/Executions/Tables/NodeExecutionsTable.tsx @@ -38,10 +38,11 @@ export const NodeExecutionsTable: React.FC = props => const rowProps = { state, onHeightChange: () => {} }; const content = state.executions.length > 0 ? ( - state.executions.map(execution => { + state.executions.map((execution, index) => { return ( diff --git a/src/components/Executions/Tables/WorkflowExecutionsTable.tsx b/src/components/Executions/Tables/WorkflowExecutionsTable.tsx index 5ebee3a6d..cc4b2b203 100644 --- a/src/components/Executions/Tables/WorkflowExecutionsTable.tsx +++ b/src/components/Executions/Tables/WorkflowExecutionsTable.tsx @@ -53,6 +53,9 @@ const useStyles = makeStyles((theme: Theme) => ({ marginLeft: theme.spacing(2), marginRight: theme.spacing(2), textAlign: 'left' + }, + row: { + paddingLeft: theme.spacing(2) } })); @@ -199,7 +202,14 @@ export const WorkflowExecutionsTable: React.FC = p const { error } = execution.closure; return ( -
+
{columns.map( ({ className, key: columnKey, cellRenderer }) => ( diff --git a/src/components/Executions/Tables/__stories__/NodeExecutionsTable.stories.tsx b/src/components/Executions/Tables/__stories__/NodeExecutionsTable.stories.tsx index 0a5dd5d92..dd465ae06 100644 --- a/src/components/Executions/Tables/__stories__/NodeExecutionsTable.stories.tsx +++ b/src/components/Executions/Tables/__stories__/NodeExecutionsTable.stories.tsx @@ -2,15 +2,12 @@ import { makeStyles, Theme } from '@material-ui/core/styles'; import { action } from '@storybook/addon-actions'; import { storiesOf } from '@storybook/react'; import { mockAPIContextValue } from 'components/data/__mocks__/apiContext'; +import { APIContext } from 'components/data/apiContext'; +import { createMockExecutionEntities } from 'components/Executions/__mocks__/createMockExecutionEntities'; import { ExecutionDataCacheContext } from 'components/Executions/contexts'; import { createExecutionDataCache } from 'components/Executions/useExecutionDataCache'; -import { - createMockWorkflow, - createMockWorkflowClosure -} from 'models/__mocks__/workflowData'; -import { createMockNodeExecutions } from 'models/Execution/__mocks__/mockNodeExecutionsData'; -import { Execution } from 'models/Execution/types'; -import { mockTasks } from 'models/Task/__mocks__/mockTaskData'; +import { keyBy } from 'lodash'; +import { createMockTaskExecutionForNodeExecution } from 'models/Execution/__mocks__/mockTaskExecutionsData'; import * as React from 'react'; import { NodeExecutionsTable, @@ -28,33 +25,59 @@ const useStyles = makeStyles((theme: Theme) => ({ })); const { - executions: mockExecutions, - nodes: mockNodes -} = createMockNodeExecutions(10); + nodes, + nodeExecutions, + workflow, + workflowExecution +} = createMockExecutionEntities({ + workflowName: 'SampleWorkflow', + nodeExecutionCount: 10 +}); -const mockWorkflow = createMockWorkflow('SampleWorkflow'); -const mockWorkflowClosure = createMockWorkflowClosure(); -const compiledWorkflow = mockWorkflowClosure.compiledWorkflow!; -const { - primary: { template }, - tasks -} = compiledWorkflow; -template.nodes = template.nodes.concat(mockNodes); -compiledWorkflow.tasks = tasks.concat(mockTasks); -mockWorkflow.closure = mockWorkflowClosure; +const nodesById = keyBy(nodes, n => n.id); +const nodesWithChildren = { + [nodes[0].id]: true, + [nodes[1].id]: true +}; +const nodeRetryAttempts = { + [nodes[1].id]: 2 +}; -const apiContext = mockAPIContextValue({}); +const apiContext = mockAPIContextValue({ + getExecution: () => Promise.resolve(workflowExecution), + getNodeExecutionData: () => Promise.resolve({ inputs: {}, outputs: {} }), + listTaskExecutions: nodeExecutionId => { + const length = nodeRetryAttempts[nodeExecutionId.nodeId] || 1; + const entities = Array.from({ length }, (_, retryAttempt) => + createMockTaskExecutionForNodeExecution( + nodeExecutionId, + nodesById[nodeExecutionId.nodeId], + retryAttempt, + { isParent: !!nodesWithChildren[nodeExecutionId.nodeId] } + ) + ); + return Promise.resolve({ entities }); + }, + listTaskExecutionChildren: ({ retryAttempt }) => + Promise.resolve({ + entities: nodeExecutions.slice(0, 2).map(ne => ({ + ...ne, + id: { + ...ne.id, + nodeId: `${ne.id.nodeId}_${retryAttempt}` + } + })) + }), + getWorkflow: () => Promise.resolve(workflow) +}); const dataCache = createExecutionDataCache(apiContext); -dataCache.insertWorkflow(mockWorkflow); -dataCache.insertWorkflowExecutionReference( - mockExecutions[0].id.executionId, - mockWorkflow.id -); +dataCache.insertWorkflow(workflow); +dataCache.insertWorkflowExecutionReference(workflowExecution.id, workflow.id); const fetchAction = action('fetch'); const props: NodeExecutionsTableProps = { - value: mockExecutions, + value: nodeExecutions, lastError: null, loading: false, moreItemsAvailable: false, @@ -64,9 +87,11 @@ const props: NodeExecutionsTableProps = { const stories = storiesOf('Tables/NodeExecutionsTable', module); stories.addDecorator(story => { return ( - -
{story()}
-
+ + +
{story()}
+
+
); }); stories.add('Basic', () => ); diff --git a/src/components/Executions/Tables/styles.ts b/src/components/Executions/Tables/styles.ts index 97f5f04f1..763392aba 100644 --- a/src/components/Executions/Tables/styles.ts +++ b/src/components/Executions/Tables/styles.ts @@ -3,6 +3,7 @@ import { headerGridHeight } from 'components'; import { headerFontFamily, listhoverColor, + nestedListColor, smallFontSize, tableHeaderColor, tablePlaceholderColor @@ -16,23 +17,32 @@ export const selectedClassName = 'selected'; // the columns styles in some cases. So the column styles should be defined // last. export const useExecutionTableStyles = makeStyles((theme: Theme) => ({ + borderBottom: { + borderBottom: `1px solid ${theme.palette.divider}` + }, + borderTop: { + borderTop: `1px solid ${theme.palette.divider}` + }, childrenContainer: { - borderTop: `1px solid ${theme.palette.divider}`, - minHeight: theme.spacing(7), - paddingLeft: theme.spacing(3) + backgroundColor: nestedListColor, + minHeight: theme.spacing(7) + }, + childGroupLabel: { + borderWidth: '2px', + padding: `${theme.spacing(2)}px 0` }, errorContainer: { - padding: `0 ${theme.spacing(7)}px ${theme.spacing(2)}px`, - '$childrenContainer > &': { + padding: `0 ${theme.spacing(8)}px ${theme.spacing(2)}px`, + '$childrenContainer &': { paddingTop: theme.spacing(2), - paddingLeft: theme.spacing(4) + paddingLeft: theme.spacing(2) } }, expander: { alignItems: 'center', display: 'flex', justifyContent: 'center', - marginLeft: theme.spacing(3), + marginLeft: theme.spacing(-4), marginRight: theme.spacing(1), width: theme.spacing(3) }, @@ -75,20 +85,14 @@ export const useExecutionTableStyles = makeStyles((theme: Theme) => ({ textAlign: 'center' }, row: { - borderBottom: `1px solid ${theme.palette.divider}`, display: 'flex', flexDirection: 'column', justifyContent: 'center', [`&.${selectedClassName}`]: { backgroundColor: listhoverColor - }, - '$childrenContainer &': { - borderBottom: 'none', - '&:not(:first-of-type)': { - borderTop: `1px solid ${theme.palette.divider}` - } } }, + rowContent: {}, rowColumns: { alignItems: 'center', display: 'flex', @@ -102,10 +106,7 @@ export const useExecutionTableStyles = makeStyles((theme: Theme) => ({ paddingBottom: theme.spacing(2), paddingTop: theme.spacing(2), textOverflow: 'ellipsis', - whiteSpace: 'nowrap', - '&:first-of-type': { - marginLeft: theme.spacing(2) - } + whiteSpace: 'nowrap' }, scrollContainer: { flex: '1 1 0', @@ -118,6 +119,7 @@ export const useExecutionTableStyles = makeStyles((theme: Theme) => ({ } })); +export const nameColumnLeftMarginGridWidth = 6; export const useColumnStyles = makeStyles((theme: Theme) => ({ columnName: { flexGrow: 1, @@ -126,7 +128,7 @@ export const useColumnStyles = makeStyles((theme: Theme) => ({ flexBasis: 0, overflow: 'hidden', '&:first-of-type': { - marginLeft: theme.spacing(7) + marginLeft: theme.spacing(nameColumnLeftMarginGridWidth) } }, columnType: { diff --git a/src/components/Executions/Tables/utils.ts b/src/components/Executions/Tables/utils.ts index 28edc9bce..5e097b23c 100644 --- a/src/components/Executions/Tables/utils.ts +++ b/src/components/Executions/Tables/utils.ts @@ -1,5 +1,7 @@ +import { Spacing } from '@material-ui/core/styles/createSpacing'; import { measureText } from 'components/common/utils'; import { TaskLog } from 'models'; +import { nameColumnLeftMarginGridWidth } from './styles'; interface MeasuredTaskLog extends TaskLog { width: number; @@ -35,3 +37,10 @@ export function splitLogLinksAtWidth( ); return [taken, left]; } + +export function calculateNodeExecutionRowLeftSpacing( + level: number, + spacing: Spacing +) { + return spacing(nameColumnLeftMarginGridWidth + 3 * level); +} diff --git a/src/components/Executions/utils.ts b/src/components/Executions/utils.ts index 679af0505..bc0c4d436 100644 --- a/src/components/Executions/utils.ts +++ b/src/components/Executions/utils.ts @@ -1,3 +1,13 @@ +import { log } from 'common/log'; +import { durationToMilliseconds, timestampToDate } from 'common/utils'; +import { getCacheKey } from 'components/Cache'; +import { + endNodeId, + Identifier, + startNodeId, + TaskTemplate, + TaskType +} from 'models'; import { BaseExecutionClosure, Execution, @@ -7,27 +17,11 @@ import { terminalNodeExecutionStates, terminalTaskExecutionStates } from 'models/Execution'; - import { NodeExecutionPhase, TaskExecutionPhase, WorkflowExecutionPhase } from 'models/Execution/enums'; - -import { log } from 'common/log'; -import { durationToMilliseconds, timestampToDate } from 'common/utils'; -import { getCacheKey } from 'components/Cache'; -import { extractTaskTemplates } from 'components/hooks/utils'; -import { keyBy } from 'lodash'; -import { - CompiledNode, - endNodeId, - Identifier, - startNodeId, - TaskTemplate, - TaskType, - Workflow -} from 'models'; import { nodeExecutionPhaseConstants, taskExecutionPhaseConstants, diff --git a/src/components/hooks/test/utils.test.ts b/src/components/hooks/test/utils.test.ts new file mode 100644 index 000000000..5b8027719 --- /dev/null +++ b/src/components/hooks/test/utils.test.ts @@ -0,0 +1,79 @@ +import { + createMockWorkflow, + createMockWorkflowClosure +} from 'models/__mocks__/workflowData'; +import { CompiledWorkflow, Workflow } from 'models/Workflow/types'; +import { extractAndIdentifyNodes, extractTaskTemplates } from '../utils'; + +describe('hooks/utils', () => { + let workflow: Workflow; + let subWorkflow: CompiledWorkflow; + + beforeEach(() => { + workflow = { + ...createMockWorkflow('SampleWorkflow'), + closure: createMockWorkflowClosure() + }; + const { template } = workflow.closure!.compiledWorkflow!.primary; + subWorkflow = { + // We don't process connections in these functions, so it can be empty + connections: { downstream: {}, upstream: {} }, + template: { + id: { ...template.id, name: `${template.id.name}_inner` }, + nodes: template.nodes.map(node => ({ + ...node, + id: `${node.id}_inner` + })) + } + }; + workflow.closure!.compiledWorkflow!.subWorkflows = [subWorkflow]; + }); + + describe('extractAndIdentifyNodes', () => { + it('returns empty for missing closure', () => { + delete workflow.closure; + expect(extractAndIdentifyNodes(workflow)).toEqual([]); + }); + + it('returns empty for missing compiledWorkflow', () => { + delete workflow.closure?.compiledWorkflow; + expect(extractAndIdentifyNodes(workflow)).toEqual([]); + }); + + it('includes nodes from subWorkflows', () => { + const nodes = extractAndIdentifyNodes(workflow); + subWorkflow.template.nodes.forEach(node => + expect(nodes).toContainEqual( + expect.objectContaining({ + id: { nodeId: node.id, workflowId: expect.anything() } + }) + ) + ); + }); + + it('assigns parent workflow id to subworkflow nodes', () => { + const nodes = extractAndIdentifyNodes(workflow); + subWorkflow.template.nodes.forEach(node => + expect(nodes).toContainEqual( + expect.objectContaining({ + id: { + nodeId: expect.anything(), + workflowId: workflow.id + } + }) + ) + ); + }); + }); + + describe('extractTaskTemplates', () => { + it('returns empty for missing closure', () => { + delete workflow.closure; + expect(extractTaskTemplates(workflow)).toEqual([]); + }); + it('returns empty for missing compiledWorkflow', () => { + delete workflow.closure?.compiledWorkflow; + expect(extractTaskTemplates(workflow)).toEqual([]); + }); + }); +}); diff --git a/src/components/hooks/useNodeExecution.ts b/src/components/hooks/useNodeExecution.ts index 821ed4657..e89457486 100644 --- a/src/components/hooks/useNodeExecution.ts +++ b/src/components/hooks/useNodeExecution.ts @@ -1,11 +1,5 @@ -import { - ExecutionData, - getNodeExecution, - getNodeExecutionData, - NodeExecution, - NodeExecutionIdentifier -} from 'models'; - +import { useAPIContext } from 'components/data/apiContext'; +import { ExecutionData, NodeExecution, NodeExecutionIdentifier } from 'models'; import { FetchableData } from './types'; import { useFetchableData } from './useFetchableData'; @@ -13,6 +7,7 @@ import { useFetchableData } from './useFetchableData'; export function useNodeExecution( id: NodeExecutionIdentifier ): FetchableData { + const { getNodeExecution } = useAPIContext(); return useFetchableData( { debugName: 'NodeExecution', @@ -27,6 +22,7 @@ export function useNodeExecution( export function useNodeExecutionData( id: NodeExecutionIdentifier ): FetchableData { + const { getNodeExecutionData } = useAPIContext(); return useFetchableData( { debugName: 'NodeExecutionData', diff --git a/src/components/hooks/utils.ts b/src/components/hooks/utils.ts index 813a34ca9..b734f9715 100644 --- a/src/components/hooks/utils.ts +++ b/src/components/hooks/utils.ts @@ -13,13 +13,25 @@ export function extractAndIdentifyNodes( if (!workflow.closure || !workflow.closure.compiledWorkflow) { return []; } - return workflow.closure.compiledWorkflow.primary.template.nodes.map( - node => ({ - node, - id: { - nodeId: node.id, - workflowId: workflow.id - } - }) + const { primary, subWorkflows = [] } = workflow.closure.compiledWorkflow; + const nodes = subWorkflows.reduce( + (out, subWorkflow) => [...out, ...subWorkflow.template.nodes], + primary.template.nodes ); + + return nodes.map(node => ({ + node, + id: { + nodeId: node.id, + // TODO: This is technically incorrect, as sub-workflow nodes + // will use the wrong parent workflow id. This is done intentionally + // to make sure that looking up the node information for a NodeExecution + // finds the entry successfully. + // When we are rendering sub-workflow nodes correctly, this should + // be updated to use the proper parent workflow id + // (subWorkflow.template.id) + // See https://github.com/lyft/flyte/issues/357 + workflowId: workflow.id + } + })); } diff --git a/src/models/Common/types.ts b/src/models/Common/types.ts index 45b2ba67a..ee970a86b 100644 --- a/src/models/Common/types.ts +++ b/src/models/Common/types.ts @@ -35,7 +35,10 @@ export interface RetryStrategy extends Core.IRetryStrategy {} export interface RuntimeMetadata extends Core.IRuntimeMetadata {} export interface Schedule extends Admin.ISchedule {} export type MessageFormat = Core.TaskLog.MessageFormat; -export type TaskLog = RequiredNonNullable; +export interface TaskLog extends Core.ITaskLog { + name: string; + uri: string; +} /*** Literals ****/ export interface Binary extends RequiredNonNullable {} diff --git a/src/models/Execution/__mocks__/mockTaskExecutionsData.ts b/src/models/Execution/__mocks__/mockTaskExecutionsData.ts index 568df91c6..d5da2d992 100644 --- a/src/models/Execution/__mocks__/mockTaskExecutionsData.ts +++ b/src/models/Execution/__mocks__/mockTaskExecutionsData.ts @@ -1,18 +1,38 @@ import { dateToTimestamp, millisecondsToDuration } from 'common/utils'; -import { Admin, Core } from 'flyteidl'; +import { Admin } from 'flyteidl'; import { cloneDeep } from 'lodash'; -import { NodeExecutionPhase, TaskExecutionPhase } from '../enums'; -import { TaskExecution } from '../types'; +import { TaskLog } from 'models/Common/types'; +import { CompiledNode } from 'models/Node/types'; +import { TaskExecutionPhase } from '../enums'; +import { + NodeExecutionIdentifier, + TaskExecution, + TaskExecutionClosure +} from '../types'; import { sampleError } from './sampleExecutionError'; -const sampleLogs: Core.ITaskLog[] = [ +const sampleLogs: TaskLog[] = [ { name: 'Kubernetes Logs', uri: 'http://localhost/k8stasklog' }, { name: 'User Logs', uri: 'http://localhost/containerlog' }, { name: 'AWS Batch Logs', uri: 'http://localhost/awsbatchlog' }, { name: 'Other Custom Logs', uri: 'http://localhost/customlog' } ]; +const inputUri = 's3://path/to/my/inputs.pb'; + +function createClosure(): TaskExecutionClosure { + return { + phase: TaskExecutionPhase.SUCCEEDED, + startedAt: dateToTimestamp(new Date(Date.now() - 1000 * 60 * 10)), + createdAt: dateToTimestamp(new Date(Date.now() - 1000 * 60 * 10)), + duration: millisecondsToDuration(1000 * 60 * 60 * 1.251), + outputUri: 's3://path/to/my/outputs.pb', + logs: [...sampleLogs] + }; +} + export const mockTaskExecutionResponse: Admin.ITaskExecution = { + inputUri, id: { nodeExecutionId: { executionId: { @@ -30,19 +50,32 @@ export const mockTaskExecutionResponse: Admin.ITaskExecution = { version: 'abcdef' } }, - inputUri: 's3://path/to/my/inputs.pb', - closure: { - phase: TaskExecutionPhase.SUCCEEDED, - startedAt: dateToTimestamp(new Date(Date.now() - 1000 * 60 * 10)), - createdAt: dateToTimestamp(new Date(Date.now() - 1000 * 60 * 10)), - duration: millisecondsToDuration(1000 * 60 * 60 * 1.251), - outputUri: 's3://path/to/my/outputs.pb', - logs: [...sampleLogs] - } + closure: createClosure() }; export const mockExecution = mockTaskExecutionResponse as TaskExecution; +export function createMockTaskExecutionForNodeExecution( + nodeExecutionId: NodeExecutionIdentifier, + node: CompiledNode, + retryAttempt: number, + overrides?: Partial +): TaskExecution { + return { + ...{ + inputUri, + id: { + nodeExecutionId, + retryAttempt, + taskId: node.taskNode!.referenceId + }, + isParent: false, + closure: createClosure() + }, + ...overrides + }; +} + export const createMockTaskExecutionsListResponse = (length: number) => { return { taskExecutions: Array.from({ length }, (_, idx) => { diff --git a/src/models/Workflow/types.ts b/src/models/Workflow/types.ts index 3afa905cf..59b6222ce 100644 --- a/src/models/Workflow/types.ts +++ b/src/models/Workflow/types.ts @@ -5,6 +5,7 @@ import { CompiledTask } from 'models/Task'; /** Holds information about all nodes existing in a Workflow graph */ export interface WorkflowTemplate extends Core.IWorkflowTemplate { + id: Identifier; interface?: TypedInterface; nodes: CompiledNode[]; }