From 4bbc71f200dbf5bd36f6ace59a6d54a1cab0df6c Mon Sep 17 00:00:00 2001 From: Nastya <55718143+anrusina@users.noreply.github.com> Date: Thu, 7 Apr 2022 18:25:55 -0700 Subject: [PATCH] feat(310): map task v1 visuals, supporting both old and new api (#357) * feat(310): map task v1 visuals, supporting both old and new api * chore: add tests Signed-off-by: Nastya Rusina --- .../Entities/EntityDetailsHeader.tsx | 2 +- .../NodeExecutionTabs/index.tsx | 17 ++- .../Timeline/BarChart/utils.ts | 1 - .../MapTaskExecutionListItem.tsx | 107 ++++++++++++++++++ .../TaskExecutionsList/TaskExecutionLogs.tsx | 29 +++-- .../TaskExecutions.mocks.ts | 97 ++++++++++++++-- .../TaskExecutionsList/TaskExecutionsList.tsx | 28 +++-- .../TaskExecutionsListContent.stories.tsx | 70 +++++++++++- .../TaskExecutionsList/test/utils.spec.ts | 31 ++++- .../Executions/TaskExecutionsList/utils.ts | 41 +++++++ .../MapTaskStatusInfo.stories.tsx | 8 +- .../MapTaskStatusInfo.test.tsx | 14 +-- .../MapTaskStatusInfo.tsx | 18 +-- src/flyteidl/index.ts | 3 +- 14 files changed, 411 insertions(+), 55 deletions(-) create mode 100644 src/components/Executions/TaskExecutionsList/MapTaskExecutionListItem.tsx diff --git a/src/components/Entities/EntityDetailsHeader.tsx b/src/components/Entities/EntityDetailsHeader.tsx index ed9a3375b..ce41b9ce6 100644 --- a/src/components/Entities/EntityDetailsHeader.tsx +++ b/src/components/Entities/EntityDetailsHeader.tsx @@ -24,7 +24,7 @@ const useStyles = makeStyles((theme: Theme) => ({ width: '100%', }, headerText: { - margin: `0 ${theme.spacing(1)}px`, + margin: theme.spacing(0, 1), }, headerTextContainer: { display: 'flex', diff --git a/src/components/Executions/ExecutionDetails/NodeExecutionTabs/index.tsx b/src/components/Executions/ExecutionDetails/NodeExecutionTabs/index.tsx index c3c763845..0ba94c0f3 100644 --- a/src/components/Executions/ExecutionDetails/NodeExecutionTabs/index.tsx +++ b/src/components/Executions/ExecutionDetails/NodeExecutionTabs/index.tsx @@ -6,6 +6,7 @@ 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 { isMapTaskType } from 'models/Task/utils'; import { TaskExecutionsList } from '../../TaskExecutionsList/TaskExecutionsList'; import { NodeExecutionInputs } from './NodeExecutionInputs'; import { NodeExecutionOutputs } from './NodeExecutionOutputs'; @@ -17,6 +18,12 @@ const useStyles = makeStyles((theme) => { }, tabs: { borderBottom: `1px solid ${theme.palette.divider}`, + '& .MuiTabs-flexContainer': { + justifyContent: 'space-around', + }, + }, + tabItem: { + margin: theme.spacing(0, 1), }, }; }); @@ -68,13 +75,15 @@ export const NodeExecutionTabs: React.FC<{ break; } } + + const executionLabel = isMapTaskType(taskTemplate?.type) ? 'Map Execution' : 'Executions'; return ( <> - - - - {!!taskTemplate && } + + + + {!!taskTemplate && }
{tabContent}
diff --git a/src/components/Executions/ExecutionDetails/Timeline/BarChart/utils.ts b/src/components/Executions/ExecutionDetails/Timeline/BarChart/utils.ts index a8b1d9510..b4da4a72a 100644 --- a/src/components/Executions/ExecutionDetails/Timeline/BarChart/utils.ts +++ b/src/components/Executions/ExecutionDetails/Timeline/BarChart/utils.ts @@ -45,7 +45,6 @@ export const formatSecondsToHmsFormat = (seconds: number) => { return `${seconds}s`; }; -// narusina - check if exports are still needed export const getOffsetColor = (isCachedValue: boolean[]) => { const colors = isCachedValue.map((val) => (val === true ? CASHED_GREEN : TRANSPARENT)); return colors; diff --git a/src/components/Executions/TaskExecutionsList/MapTaskExecutionListItem.tsx b/src/components/Executions/TaskExecutionsList/MapTaskExecutionListItem.tsx new file mode 100644 index 000000000..078d4b680 --- /dev/null +++ b/src/components/Executions/TaskExecutionsList/MapTaskExecutionListItem.tsx @@ -0,0 +1,107 @@ +import * as React from 'react'; +import { makeStyles, Theme } from '@material-ui/core/styles'; +import Typography from '@material-ui/core/Typography'; +import classnames from 'classnames'; +import { PanelSection } from 'components/common/PanelSection'; +import { useCommonStyles } from 'components/common/styles'; +import { TaskExecutionPhase } from 'models/Execution/enums'; +import { TaskExecution } from 'models/Execution/types'; +import { MapTaskStatusInfo } from 'components/common/MapTaskExecutionsList/MapTaskStatusInfo'; +import { TaskExecutionDetails } from './TaskExecutionDetails'; +import { TaskExecutionError } from './TaskExecutionError'; +import { TaskExecutionLogs } from './TaskExecutionLogs'; +import { formatRetryAttempt, getGroupedLogs } from './utils'; + +const useStyles = makeStyles((theme: Theme) => ({ + detailsLink: { + fontWeight: 'normal', + }, + header: { + marginBottom: theme.spacing(1), + }, + title: { + marginBottom: theme.spacing(1), + }, + showDetailsButton: { + marginTop: theme.spacing(1), + }, + section: { + marginBottom: theme.spacing(2), + }, +})); + +interface MapTaskExecutionsListItemProps { + taskExecution: TaskExecution; + showAttempts: boolean; +} + +const RENDER_ORDER: TaskExecutionPhase[] = [ + TaskExecutionPhase.UNDEFINED, + TaskExecutionPhase.INITIALIZING, + TaskExecutionPhase.WAITING_FOR_RESOURCES, + TaskExecutionPhase.QUEUED, + TaskExecutionPhase.RUNNING, + TaskExecutionPhase.SUCCEEDED, + TaskExecutionPhase.ABORTED, + TaskExecutionPhase.FAILED, +]; + +/** Renders an individual `TaskExecution` record as part of a list */ +export const MapTaskExecutionsListItem: React.FC = ({ + taskExecution, + showAttempts, +}) => { + const commonStyles = useCommonStyles(); + const styles = useStyles(); + + const { closure } = taskExecution; + const taskHasStarted = closure.phase >= TaskExecutionPhase.QUEUED; + const headerText = formatRetryAttempt(taskExecution.id.retryAttempt); + const logsInfo = getGroupedLogs(closure.metadata?.externalResources ?? []); + + // Set UI elements in a proper rendering order + const logsSections: JSX.Element[] = []; + for (const key of RENDER_ORDER) { + const values = logsInfo.get(key); + if (values) { + logsSections.push(); + } + } + + return ( + + {/* Attempts header is ahown only if there is more than one attempt */} + {showAttempts ? ( +
+
+ + {headerText} + +
+
+ ) : null} + {/* Error info is shown only if there is an error present for this map task */} + {closure.error ? ( +
+ +
+ ) : null} + + {/* If main map task has log attached - show it here */} + {closure.logs && closure.logs.length > 0 ? ( +
+ +
+ ) : null} + {/* child/array logs separated by subtasks phase */} + {logsSections} + + {/* If map task is actively started - show 'started' and 'run time' details */} + {taskHasStarted && ( +
+ +
+ )} +
+ ); +}; diff --git a/src/components/Executions/TaskExecutionsList/TaskExecutionLogs.tsx b/src/components/Executions/TaskExecutionsList/TaskExecutionLogs.tsx index f8b298d57..8bbe85496 100644 --- a/src/components/Executions/TaskExecutionsList/TaskExecutionLogs.tsx +++ b/src/components/Executions/TaskExecutionsList/TaskExecutionLogs.tsx @@ -1,9 +1,9 @@ import * as React from 'react'; import { Typography } from '@material-ui/core'; import { makeStyles, Theme } from '@material-ui/core/styles'; +import { Core } from 'flyteidl'; import { NewTargetLink } from 'components/common/NewTargetLink'; import { useCommonStyles } from 'components/common/styles'; -import { TaskLog } from 'models/Common/types'; import { noLogsFoundString } from '../constants'; const useStyles = makeStyles((theme: Theme) => ({ @@ -13,9 +13,12 @@ const useStyles = makeStyles((theme: Theme) => ({ sectionHeader: { marginTop: theme.spacing(1), }, + logName: { + fontWeight: 'lighter', + }, })); -const TaskLogList: React.FC<{ logs: TaskLog[] }> = ({ logs }) => { +export const TaskLogList: React.FC<{ logs: Core.ITaskLog[] }> = ({ logs }) => { const styles = useStyles(); const commonStyles = useCommonStyles(); if (!(logs && logs.length > 0)) { @@ -23,11 +26,16 @@ const TaskLogList: React.FC<{ logs: TaskLog[] }> = ({ logs }) => { } return ( <> - {logs.map(({ name, uri }) => ( - - {name} - - ))} + {logs.map(({ name, uri }) => + uri ? ( + + {name} + + ) : ( + // If there is no url, show item a a name string only, as it's not really clickable +
{name}
+ ), + )} ); }; @@ -35,12 +43,15 @@ const TaskLogList: React.FC<{ logs: TaskLog[] }> = ({ logs }) => { /** Renders log links from a `taskLogs`(aka taskExecution.closure.logs), if they exist. * Otherwise renders a message indicating that no logs are available. */ -export const TaskExecutionLogs: React.FC<{ taskLogs: TaskLog[] }> = ({ taskLogs }) => { +export const TaskExecutionLogs: React.FC<{ taskLogs: Core.ITaskLog[]; title?: string }> = ({ + taskLogs, + title, +}) => { const styles = useStyles(); return (
- Logs + {title ?? 'Logs'}
diff --git a/src/components/Executions/TaskExecutionsList/TaskExecutions.mocks.ts b/src/components/Executions/TaskExecutionsList/TaskExecutions.mocks.ts index 8fc2432f3..5d7ead286 100644 --- a/src/components/Executions/TaskExecutionsList/TaskExecutions.mocks.ts +++ b/src/components/Executions/TaskExecutionsList/TaskExecutions.mocks.ts @@ -1,9 +1,10 @@ -import { Protobuf } from 'flyteidl'; -import { MessageFormat, ResourceType } from 'models/Common/types'; +import { Protobuf, Event } from 'flyteidl'; +import { MessageFormat, ResourceType, TaskLog } from 'models/Common/types'; import { TaskExecutionPhase } from 'models/Execution/enums'; import { TaskExecution } from 'models/Execution/types'; import * as Long from 'long'; +import { TaskType } from 'models/Task/constants'; // 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 => { @@ -14,6 +15,19 @@ const getProtobufTimestampFromIsoTime = (isoDateTime: string): Protobuf.ITimesta return timestamp; }; +const getProtobufDurationFromString = (durationSec: string): Protobuf.Duration => { + const secondsInt = parseInt(durationSec, 10); + const duration = new Protobuf.Duration(); + duration.seconds = Long.fromInt(secondsInt); + return duration; +}; + +export const MockTaskExceutionLog: TaskLog = { + uri: '#', + name: 'Cloudwatch Logs (User)', + messageFormat: MessageFormat.JSON, +}; + export const MockPythonTaskExecution: TaskExecution = { id: { taskId: { @@ -38,16 +52,79 @@ export const MockPythonTaskExecution: TaskExecution = { 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, - }, - ], + logs: [MockTaskExceutionLog], createdAt: getProtobufTimestampFromIsoTime('2022-03-17T21:30:53.469624134Z'), updatedAt: getProtobufTimestampFromIsoTime('2022-03-17T21:31:04.011303736Z'), reason: 'task submitted to K8s', - taskType: 'python-task', + taskType: TaskType.PYTHON, + }, +}; + +export const getMockMapTaskLogItem = ( + phase: TaskExecutionPhase, + hasLogs: boolean, + index?: number, + retryAttempt?: number, +): Event.IExternalResourceInfo => { + const retryString = retryAttempt && retryAttempt > 0 ? `-${retryAttempt}` : ''; + return { + externalId: `y286hpfvwh-n0-0-${index ?? 0}`, + index: index, + phase: phase, + retryAttempt: retryAttempt, + logs: hasLogs + ? [ + { + uri: '#', + name: `Kubernetes Logs #0-${index ?? 0}${retryString} (State)`, + messageFormat: MessageFormat.JSON, + }, + ] + : [], + }; +}; + +export const MockMapTaskExecution: TaskExecution = { + id: { + taskId: { + resourceType: ResourceType.TASK, + project: 'flytesnacks', + domain: 'development', + name: 'flyte.workflows.example.mapper_a_mappable_task_0', + version: 'v2', + }, + nodeExecutionId: { + nodeId: 'n0', + executionId: { + project: 'flytesnacks', + domain: 'development', + name: 'y286hpfvwh', + }, + }, + }, + inputUri: + 's3://my-s3-bucket/metadata/propeller/sandbox/flytesnacks-development-y286hpfvwh/n0/data/inputs.pb', + closure: { + outputUri: + 's3://my-s3-bucket/metadata/propeller/sandbox/flytesnacks-development-y286hpfvwh/n0/data/0/outputs.pb', + phase: TaskExecutionPhase.SUCCEEDED, + startedAt: getProtobufTimestampFromIsoTime('2022-03-30T19:31:09.487343Z'), + duration: getProtobufDurationFromString('190.302384340s'), + createdAt: getProtobufTimestampFromIsoTime('2022-03-30T19:31:09.487343693Z'), + updatedAt: getProtobufTimestampFromIsoTime('2022-03-30T19:34:19.789727340Z'), + taskType: 'container_array', + metadata: { + generatedName: 'y286hpfvwh-n0-0', + externalResources: [ + getMockMapTaskLogItem(TaskExecutionPhase.SUCCEEDED, true), + getMockMapTaskLogItem(TaskExecutionPhase.SUCCEEDED, true, 1), + getMockMapTaskLogItem(TaskExecutionPhase.SUCCEEDED, true, 2), + getMockMapTaskLogItem(TaskExecutionPhase.FAILED, true, 3), + getMockMapTaskLogItem(TaskExecutionPhase.SUCCEEDED, true, 3, 1), + getMockMapTaskLogItem(TaskExecutionPhase.SUCCEEDED, true, 4), + getMockMapTaskLogItem(TaskExecutionPhase.FAILED, false, 5), + ], + pluginIdentifier: 'k8s-array', + }, }, }; diff --git a/src/components/Executions/TaskExecutionsList/TaskExecutionsList.tsx b/src/components/Executions/TaskExecutionsList/TaskExecutionsList.tsx index 0a991672b..16915dac9 100644 --- a/src/components/Executions/TaskExecutionsList/TaskExecutionsList.tsx +++ b/src/components/Executions/TaskExecutionsList/TaskExecutionsList.tsx @@ -1,10 +1,12 @@ +import * as React from 'react'; import { makeStyles, Theme } from '@material-ui/core/styles'; import { noExecutionsFoundString } from 'common/constants'; import { NonIdealState } from 'components/common/NonIdealState'; import { WaitForData } from 'components/common/WaitForData'; import { NodeExecution, TaskExecution } from 'models/Execution/types'; -import * as React from 'react'; +import { isMapTaskType } from 'models/Task/utils'; import { useTaskExecutions, useTaskExecutionsRefresher } from '../useTaskExecutions'; +import { MapTaskExecutionsListItem } from './MapTaskExecutionListItem'; import { TaskExecutionsListItem } from './TaskExecutionsListItem'; import { getUniqueTaskExecutionName } from './utils'; @@ -31,14 +33,26 @@ export const TaskExecutionsListContent: React.FC<{ /> ); } + return ( <> - {taskExecutions.map((taskExecution) => ( - - ))} + {taskExecutions.map((taskExecution) => { + const taskType = taskExecution.closure.taskType ?? undefined; + const useNewMapTaskView = + isMapTaskType(taskType) && taskExecution.closure.metadata?.externalResources; + return useNewMapTaskView ? ( + 1} + /> + ) : ( + + ); + })} ); }; diff --git a/src/components/Executions/TaskExecutionsList/TaskExecutionsListContent.stories.tsx b/src/components/Executions/TaskExecutionsList/TaskExecutionsListContent.stories.tsx index 4b7823ecd..e712c2441 100644 --- a/src/components/Executions/TaskExecutionsList/TaskExecutionsListContent.stories.tsx +++ b/src/components/Executions/TaskExecutionsList/TaskExecutionsListContent.stories.tsx @@ -1,9 +1,16 @@ import * as React from 'react'; import { ComponentStory, ComponentMeta } from '@storybook/react'; +import { Core } from 'flyteidl'; import { TaskExecutionPhase } from 'models/Execution/enums'; import { PanelViewDecorator } from 'components/common/__stories__/Decorators'; +import { TaskType } from 'models/Task/constants'; import { TaskExecutionsListContent } from './TaskExecutionsList'; -import { MockPythonTaskExecution } from './TaskExecutions.mocks'; +import { + getMockMapTaskLogItem, + MockMapTaskExecution, + MockPythonTaskExecution, + MockTaskExceutionLog, +} from './TaskExecutions.mocks'; export default { title: 'Task/NodeExecutionTabs', @@ -29,3 +36,64 @@ PythonTaskWithRetry.args = { { ...MockPythonTaskExecution, id: { ...MockPythonTaskExecution.id, retryAttempt: 1 } }, ], }; + +export const MapTaskOldView = Template.bind({}); +MapTaskOldView.args = { + taskExecutions: [ + { + ...MockPythonTaskExecution, + closure: { + ...MockPythonTaskExecution.closure, + taskType: TaskType.ARRAY, + logs: new Array(5).fill(MockTaskExceutionLog), + }, + }, + ], +}; + +export const MapTaskBase = Template.bind({}); +MapTaskBase.args = { + taskExecutions: [MockMapTaskExecution], +}; + +export const MapTaskEverything = Template.bind({}); +MapTaskEverything.decorators = [(Story) => PanelViewDecorator(Story)]; +MapTaskEverything.args = { + taskExecutions: [ + { + ...MockMapTaskExecution, + closure: { + ...MockMapTaskExecution.closure, + logs: [MockTaskExceutionLog], + error: { + code: '666', + message: 'Fake error occured, if you know what I mean', + errorUri: '#', + kind: Core.ExecutionError.ErrorKind.USER, + }, + metadata: { + ...MockMapTaskExecution.closure.metadata, + externalResources: [ + getMockMapTaskLogItem(TaskExecutionPhase.ABORTED, true, 1), + getMockMapTaskLogItem(TaskExecutionPhase.ABORTED, true, 5), + getMockMapTaskLogItem(TaskExecutionPhase.FAILED, true, 2), + getMockMapTaskLogItem(TaskExecutionPhase.FAILED, true, 2, 1), + getMockMapTaskLogItem(TaskExecutionPhase.FAILED, true, 10), + getMockMapTaskLogItem(TaskExecutionPhase.FAILED, true, 10, 1), + getMockMapTaskLogItem(TaskExecutionPhase.INITIALIZING, false, 3), + getMockMapTaskLogItem(TaskExecutionPhase.INITIALIZING, false, 12), + getMockMapTaskLogItem(TaskExecutionPhase.WAITING_FOR_RESOURCES, false, 4), + getMockMapTaskLogItem(TaskExecutionPhase.QUEUED, true, 6), + getMockMapTaskLogItem(TaskExecutionPhase.QUEUED, true, 7), + getMockMapTaskLogItem(TaskExecutionPhase.RUNNING, true, 8), + getMockMapTaskLogItem(TaskExecutionPhase.UNDEFINED, false, 9), + getMockMapTaskLogItem(TaskExecutionPhase.SUCCEEDED, true), + getMockMapTaskLogItem(TaskExecutionPhase.SUCCEEDED, true, 2, 2), + getMockMapTaskLogItem(TaskExecutionPhase.SUCCEEDED, true, 11), + ], + }, + }, + }, + { ...MockMapTaskExecution, id: { ...MockMapTaskExecution.id, retryAttempt: 1 } }, + ], +}; diff --git a/src/components/Executions/TaskExecutionsList/test/utils.spec.ts b/src/components/Executions/TaskExecutionsList/test/utils.spec.ts index 831820d7b..53b0a4599 100644 --- a/src/components/Executions/TaskExecutionsList/test/utils.spec.ts +++ b/src/components/Executions/TaskExecutionsList/test/utils.spec.ts @@ -1,5 +1,8 @@ +import { Event } from 'flyteidl'; +import { TaskExecutionPhase } from 'models/Execution/enums'; import { obj } from 'test/utils'; -import { formatRetryAttempt, getUniqueTaskExecutionName } from '../utils'; +import { getMockMapTaskLogItem } from '../TaskExecutions.mocks'; +import { formatRetryAttempt, getGroupedLogs, getUniqueTaskExecutionName } from '../utils'; describe('getUniqueTaskExecutionName', () => { const cases: [{ name: string; retryAttempt: number }, string][] = [ @@ -33,3 +36,29 @@ describe('formatRetryAttempt', () => { expect(formatRetryAttempt(input)).toEqual(expected)), ); }); + +describe('getGroupedLogs', () => { + const resources: Event.IExternalResourceInfo[] = [ + getMockMapTaskLogItem(TaskExecutionPhase.SUCCEEDED, true), + getMockMapTaskLogItem(TaskExecutionPhase.FAILED, true, 1), + getMockMapTaskLogItem(TaskExecutionPhase.FAILED, true, 1, 1), + getMockMapTaskLogItem(TaskExecutionPhase.SUCCEEDED, true, 1, 2), + getMockMapTaskLogItem(TaskExecutionPhase.FAILED, false, 2), + ]; + + it(`Should properly group to Success and Failed`, () => { + const logs = getGroupedLogs(resources); + // Do not have key which was not in the logs + expect(logs.get(TaskExecutionPhase.QUEUED)).toBeUndefined(); + + // To have keys which were in the logs + expect(logs.get(TaskExecutionPhase.SUCCEEDED)).not.toBeUndefined(); + expect(logs.get(TaskExecutionPhase.FAILED)).not.toBeUndefined(); + + // to include all items with last retry iterations + expect(logs.get(TaskExecutionPhase.SUCCEEDED)?.length).toEqual(2); + + // to filter our previous retry attempt + expect(logs.get(TaskExecutionPhase.FAILED)?.length).toEqual(1); + }); +}); diff --git a/src/components/Executions/TaskExecutionsList/utils.ts b/src/components/Executions/TaskExecutionsList/utils.ts index 8d71d2809..356d51b6d 100644 --- a/src/components/Executions/TaskExecutionsList/utils.ts +++ b/src/components/Executions/TaskExecutionsList/utils.ts @@ -1,4 +1,6 @@ import { leftPaddedNumber } from 'common/formatters'; +import { Core, Event } from 'flyteidl'; +import { TaskExecutionPhase } from 'models/Execution/enums'; import { TaskExecution } from 'models/Execution/types'; /** Generates a unique name for a task execution, suitable for display in a @@ -22,3 +24,42 @@ export function formatRetryAttempt(attempt: number | string | undefined): string // Retry attempts are zero-based, so incrementing before formatting return `Attempt ${leftPaddedNumber(parsed + 1, 2)}`; } + +export const getGroupedLogs = ( + resources: Event.IExternalResourceInfo[], +): Map => { + const logsInfo = new Map(); + + // sort output sample [0-2, 0-1, 0, 1, 2], where 0-1 means index = 0 retry = 1 + resources.sort((a, b) => { + const aIndex = a.index ?? 0; + const bIndex = b.index ?? 0; + if (aIndex !== bIndex) { + // return smaller index first + return aIndex - bIndex; + } + + const aRetry = a.retryAttempt ?? 0; + const bRetry = b.retryAttempt ?? 0; + return bRetry - aRetry; + }); + + let lastIndex = -1; + for (const item of resources) { + if (item.index === lastIndex) { + // skip, as we already added final retry to data + continue; + } + const phase = item.phase ?? TaskExecutionPhase.UNDEFINED; + const currentValue = logsInfo.get(phase); + lastIndex = item.index ?? 0; + if (item.logs) { + // if there is no log with active url, just create an item with externalId, + // for user to understand which array items are in this state + const newLogs = item.logs.length > 0 ? item.logs : [{ name: item.externalId }]; + logsInfo.set(phase, currentValue ? [...currentValue, ...newLogs] : [...newLogs]); + } + } + + return logsInfo; +}; diff --git a/src/components/common/MapTaskExecutionsList/MapTaskStatusInfo.stories.tsx b/src/components/common/MapTaskExecutionsList/MapTaskStatusInfo.stories.tsx index 1927630cf..f5fdfc86b 100644 --- a/src/components/common/MapTaskExecutionsList/MapTaskStatusInfo.stories.tsx +++ b/src/components/common/MapTaskExecutionsList/MapTaskStatusInfo.stories.tsx @@ -1,11 +1,11 @@ import * as React from 'react'; import { ComponentStory, ComponentMeta } from '@storybook/react'; -import { NodeExecutionPhase } from 'models/Execution/enums'; +import { TaskExecutionPhase } from 'models/Execution/enums'; import { MapTaskStatusInfo } from './MapTaskStatusInfo'; import { PanelViewDecorator } from '../__stories__/Decorators'; export default { - title: 'Task/MapTaskExecutionList/MapTaskStatusInfo', + title: 'Task/NodeExecutionTabs/MapTaskStatusInfo', component: MapTaskStatusInfo, parameters: { actions: { argTypesRegex: 'toggleExpanded' } }, } as ComponentMeta; @@ -24,12 +24,12 @@ Default.args = { { uri: '#', name: 'Kubernetes Logs #0-3' }, { uri: '#', name: 'Kubernetes Logs #0-4' }, ], - status: NodeExecutionPhase.QUEUED, + status: TaskExecutionPhase.QUEUED, expanded: true, }; export const AllSpace = Template.bind({}); AllSpace.args = { taskLogs: [], - status: NodeExecutionPhase.SUCCEEDED, + status: TaskExecutionPhase.SUCCEEDED, }; diff --git a/src/components/common/MapTaskExecutionsList/MapTaskStatusInfo.test.tsx b/src/components/common/MapTaskExecutionsList/MapTaskStatusInfo.test.tsx index 768535f50..219cd4d32 100644 --- a/src/components/common/MapTaskExecutionsList/MapTaskStatusInfo.test.tsx +++ b/src/components/common/MapTaskExecutionsList/MapTaskStatusInfo.test.tsx @@ -1,7 +1,7 @@ import { fireEvent, render, waitFor } from '@testing-library/react'; import { noLogsFoundString } from 'components/Executions/constants'; -import { getNodeExecutionPhaseConstants } from 'components/Executions/utils'; -import { NodeExecutionPhase } from 'models/Execution/enums'; +import { getTaskExecutionPhaseConstants } from 'components/Executions/utils'; +import { TaskExecutionPhase } from 'models/Execution/enums'; import * as React from 'react'; import { MapTaskStatusInfo } from './MapTaskStatusInfo'; @@ -14,8 +14,8 @@ const taskLogs = [ describe('MapTaskStatusInfo', () => { it('Phase and amount of links rendered correctly', async () => { - const status = NodeExecutionPhase.RUNNING; - const phaseData = getNodeExecutionPhaseConstants(status); + const status = TaskExecutionPhase.RUNNING; + const phaseData = getTaskExecutionPhaseConstants(status); const { queryByText, getByTitle } = render( , @@ -29,13 +29,13 @@ describe('MapTaskStatusInfo', () => { const buttonEl = getByTitle('Expand row'); fireEvent.click(buttonEl); await waitFor(() => { - expect(queryByText('Logs')).toBeInTheDocument(); + expect(queryByText(taskLogs[0].name)).toBeInTheDocument(); }); }); it('Phase with no links show proper texts when opened', () => { - const status = NodeExecutionPhase.ABORTED; - const phaseData = getNodeExecutionPhaseConstants(status); + const status = TaskExecutionPhase.ABORTED; + const phaseData = getTaskExecutionPhaseConstants(status); const { queryByText } = render( , diff --git a/src/components/common/MapTaskExecutionsList/MapTaskStatusInfo.tsx b/src/components/common/MapTaskExecutionsList/MapTaskStatusInfo.tsx index 451fbac29..7ed9fca1a 100644 --- a/src/components/common/MapTaskExecutionsList/MapTaskStatusInfo.tsx +++ b/src/components/common/MapTaskExecutionsList/MapTaskStatusInfo.tsx @@ -1,12 +1,12 @@ import * as React from 'react'; import { useState } from 'react'; +import { Typography } from '@material-ui/core'; import { makeStyles, Theme } from '@material-ui/core/styles'; import { RowExpander } from 'components/Executions/Tables/RowExpander'; -import { NodeExecutionPhase } from 'models/Execution/enums'; -import { getNodeExecutionPhaseConstants } from 'components/Executions/utils'; -import { Typography } from '@material-ui/core'; -import { TaskExecutionLogs } from 'components/Executions/TaskExecutionsList/TaskExecutionLogs'; -import { TaskLog } from 'models/Common/types'; +import { TaskExecutionPhase } from 'models/Execution/enums'; +import { getTaskExecutionPhaseConstants } from 'components/Executions/utils'; +import { TaskLogList } from 'components/Executions/TaskExecutionsList/TaskExecutionLogs'; +import { Core } from 'flyteidl'; const useStyles = makeStyles((_theme: Theme) => ({ mainWrapper: { @@ -36,8 +36,8 @@ const useStyles = makeStyles((_theme: Theme) => ({ })); interface MapTaskStatusInfoProps { - taskLogs: TaskLog[]; - status: NodeExecutionPhase; + taskLogs: Core.ITaskLog[]; + status: TaskExecutionPhase; expanded: boolean; } @@ -49,7 +49,7 @@ export const MapTaskStatusInfo = (props: MapTaskStatusInfoProps) => { setExpanded(!expanded); }; - const phaseData = getNodeExecutionPhaseConstants(props.status); + const phaseData = getTaskExecutionPhaseConstants(props.status); return (
@@ -62,7 +62,7 @@ export const MapTaskStatusInfo = (props: MapTaskStatusInfoProps) => {
{expanded && (
- +
)}
diff --git a/src/flyteidl/index.ts b/src/flyteidl/index.ts index 48a53823d..6071ed9e9 100644 --- a/src/flyteidl/index.ts +++ b/src/flyteidl/index.ts @@ -4,8 +4,9 @@ import { flyteidl, google } from '@flyteorg/flyteidl/gen/pb-js/flyteidl'; import admin = flyteidl.admin; import core = flyteidl.core; import service = flyteidl.service; +import event = flyteidl.event; /** Message classes for built-in Protobuf types */ import protobuf = google.protobuf; -export { admin as Admin, core as Core, service as Service, protobuf as Protobuf }; +export { admin as Admin, core as Core, service as Service, protobuf as Protobuf, event as Event };