diff --git a/src/utils/data-formatters/__tests__/format-duration.test.ts b/src/utils/data-formatters/__tests__/format-duration.test.ts index 3d3ea518d..dc188a0bb 100644 --- a/src/utils/data-formatters/__tests__/format-duration.test.ts +++ b/src/utils/data-formatters/__tests__/format-duration.test.ts @@ -31,4 +31,11 @@ describe('formatDuration', () => { const duration: Duration = { seconds: '31556952', nanos: 0 }; expect(formatDuration(duration, { separator: ' ' })).toBe('1y 5h 49m 12s'); }); + + it('should format duration with custom min unit', () => { + const duration: Duration = { seconds: '31556952', nanos: 0 }; + expect(formatDuration(duration, { separator: ' ', minUnit: 'h' })).toBe( + '1y 5h' + ); + }); }); diff --git a/src/utils/data-formatters/format-duration.ts b/src/utils/data-formatters/format-duration.ts index e75ed1ac6..a2c1ff672 100644 --- a/src/utils/data-formatters/format-duration.ts +++ b/src/utils/data-formatters/format-duration.ts @@ -1,9 +1,14 @@ import { type Duration } from '@/__generated__/proto-ts/google/protobuf/Duration'; import dayjs from '@/utils/datetime/dayjs'; +export type FormatDurationUnitType = 'y' | 'M' | 'd' | 'h' | 'm' | 's' | 'ms'; + const formatDuration = ( duration: Duration | null, - { separator = ', ' }: { separator?: string } = {} + { + separator = ', ', + minUnit = 'ms', + }: { separator?: string; minUnit?: FormatDurationUnitType } = {} ) => { const defaultReturn = '0s'; if (!duration) { @@ -16,7 +21,16 @@ const formatDuration = ( const intMillis = Math.floor(nanosAsMillis); const remainingNanosAsMillis = nanosAsMillis % 1; const milliseconds = secondsAsMillis + intMillis; - const units = ['y', 'M', 'd', 'h', 'm', 's', 'ms'] as const; + const allUnits: Array = [ + 'y', + 'M', + 'd', + 'h', + 'm', + 's', + 'ms', + ]; + const units = allUnits.slice(0, allUnits.indexOf(minUnit) + 1); const values: Partial> = {}; let d = dayjs.duration(milliseconds); units.forEach((unit) => { diff --git a/src/views/workflow-history/workflow-history-events-duration-badge/helpers/__tests__/get-formatted-events-duration.test.ts b/src/views/workflow-history/workflow-history-events-duration-badge/helpers/__tests__/get-formatted-events-duration.test.ts index 11ebe2e9e..98e9fb328 100644 --- a/src/views/workflow-history/workflow-history-events-duration-badge/helpers/__tests__/get-formatted-events-duration.test.ts +++ b/src/views/workflow-history/workflow-history-events-duration-badge/helpers/__tests__/get-formatted-events-duration.test.ts @@ -3,7 +3,8 @@ import getFormattedEventsDuration from '../get-formatted-events-duration'; jest.mock('@/utils/data-formatters/format-duration', () => ({ __esModule: true, default: jest.fn( - (duration) => `mocked: ${duration.seconds}s ${duration.nanos / 1000000}ms` + (duration, { minUnit }) => + `mocked: ${duration.seconds}s${minUnit === 'ms' ? ` ${duration.nanos / 1000000}ms` : ''}` ), })); diff --git a/src/views/workflow-history/workflow-history-events-duration-badge/helpers/get-formatted-events-duration.ts b/src/views/workflow-history/workflow-history-events-duration-badge/helpers/get-formatted-events-duration.ts index 2a54342f2..5a4e4f101 100644 --- a/src/views/workflow-history/workflow-history-events-duration-badge/helpers/get-formatted-events-duration.ts +++ b/src/views/workflow-history/workflow-history-events-duration-badge/helpers/get-formatted-events-duration.ts @@ -17,12 +17,8 @@ export default function getFormattedEventsDuration( seconds: seconds.toString(), nanos: (durationObj.asMilliseconds() - seconds * 1000) * 1000000, }, - { separator: ' ' } + { separator: ' ', minUnit: hideMs && seconds > 0 ? 's' : 'ms' } ); - // TODO: add this functionality to formatDuration in more reusable way - if (hideMs && seconds > 0) { - return duration.replace(/ \d+ms/i, ''); - } return duration; } diff --git a/src/views/workflow-history/workflow-history-events-duration-badge/workflow-history-events-duration-badge.styles.ts b/src/views/workflow-history/workflow-history-events-duration-badge/workflow-history-events-duration-badge.styles.ts index ae8e238ce..4cd935fc7 100644 --- a/src/views/workflow-history/workflow-history-events-duration-badge/workflow-history-events-duration-badge.styles.ts +++ b/src/views/workflow-history/workflow-history-events-duration-badge/workflow-history-events-duration-badge.styles.ts @@ -1,8 +1,6 @@ import { type BadgeOverrides } from 'baseui/badge/types'; import { type Theme } from 'baseui/theme'; -import themeLight from '@/config/theme/theme-light.config'; - export const overrides = { Badge: { Badge: { diff --git a/src/views/workflow-history/workflow-history-remaining-duration-badge/__tests__/workflow-history-remaining-duration-badge.test.tsx b/src/views/workflow-history/workflow-history-remaining-duration-badge/__tests__/workflow-history-remaining-duration-badge.test.tsx new file mode 100644 index 000000000..2b52f9160 --- /dev/null +++ b/src/views/workflow-history/workflow-history-remaining-duration-badge/__tests__/workflow-history-remaining-duration-badge.test.tsx @@ -0,0 +1,184 @@ +import React from 'react'; + +import { render, screen, act } from '@/test-utils/rtl'; + +import { WorkflowExecutionCloseStatus } from '@/__generated__/proto-ts/uber/cadence/api/v1/WorkflowExecutionCloseStatus'; + +import getFormattedRemainingDuration from '../helpers/get-formatted-remaining-duration'; +import WorkflowHistoryRemainingDurationBadge from '../workflow-history-remaining-duration-badge'; +import type { Props } from '../workflow-history-remaining-duration-badge.types'; + +jest.mock('../helpers/get-formatted-remaining-duration'); + +const mockStartTime = new Date('2024-01-01T10:00:00Z'); +const mockNow = new Date('2024-01-01T10:02:00Z'); + +const mockGetFormattedRemainingDuration = + getFormattedRemainingDuration as jest.MockedFunction< + typeof getFormattedRemainingDuration + >; + +describe('WorkflowHistoryRemainingDurationBadge', () => { + beforeEach(() => { + jest.useFakeTimers(); + jest.setSystemTime(mockNow); + mockGetFormattedRemainingDuration.mockReturnValue('5m 30s'); + }); + + afterEach(() => { + jest.clearAllMocks(); + jest.useRealTimers(); + }); + + it('renders remaining duration badge when duration is available', () => { + setup(); + + expect(screen.getByText('Remaining: 5m 30s')).toBeInTheDocument(); + }); + + it('does not render badge when loading more events', () => { + setup({ + loadingMoreEvents: true, + }); + + expect(screen.queryByText(/Remaining:/)).not.toBeInTheDocument(); + }); + + it('does not render badge when workflow is archived', () => { + setup({ + workflowIsArchived: true, + }); + + expect(screen.queryByText(/Remaining:/)).not.toBeInTheDocument(); + }); + + it('does not render badge when workflow has close status', () => { + const closeStatuses = [ + WorkflowExecutionCloseStatus.WORKFLOW_EXECUTION_CLOSE_STATUS_COMPLETED, + WorkflowExecutionCloseStatus.WORKFLOW_EXECUTION_CLOSE_STATUS_FAILED, + WorkflowExecutionCloseStatus.WORKFLOW_EXECUTION_CLOSE_STATUS_CANCELED, + WorkflowExecutionCloseStatus.WORKFLOW_EXECUTION_CLOSE_STATUS_TERMINATED, + WorkflowExecutionCloseStatus.WORKFLOW_EXECUTION_CLOSE_STATUS_CONTINUED_AS_NEW, + WorkflowExecutionCloseStatus.WORKFLOW_EXECUTION_CLOSE_STATUS_TIMED_OUT, + ]; + + closeStatuses.forEach((status) => { + const { unmount } = setup({ + workflowCloseStatus: status, + }); + + expect(screen.queryByText(/Remaining:/)).not.toBeInTheDocument(); + unmount(); + }); + }); + + it('does not render badge when helper returns null', () => { + mockGetFormattedRemainingDuration.mockReturnValue(null); + + setup(); + + expect(screen.queryByText(/Remaining:/)).not.toBeInTheDocument(); + }); + + it('updates remaining duration every second', () => { + setup(); + + expect(screen.getByText('Remaining: 5m 30s')).toBeInTheDocument(); + + // Mock different return values for subsequent calls + mockGetFormattedRemainingDuration.mockReturnValueOnce('5m 29s'); + + act(() => { + jest.advanceTimersByTime(1000); + }); + + expect(screen.getByText('Remaining: 5m 29s')).toBeInTheDocument(); + + mockGetFormattedRemainingDuration.mockReturnValueOnce('5m 28s'); + + act(() => { + jest.advanceTimersByTime(1000); + }); + + expect(screen.getByText('Remaining: 5m 28s')).toBeInTheDocument(); + + // Verify the helper was called the expected number of times + // Initial call + 2 interval updates = 3 calls + expect(mockGetFormattedRemainingDuration).toHaveBeenCalledTimes(3); + }); + + it('hides badge when duration becomes null during countdown', () => { + setup(); + + expect(screen.getByText('Remaining: 5m 30s')).toBeInTheDocument(); + + // Mock helper to return null (indicating overrun) + mockGetFormattedRemainingDuration.mockReturnValue(null); + + act(() => { + jest.advanceTimersByTime(1000); + }); + + expect(screen.queryByText(/Remaining:/)).not.toBeInTheDocument(); + }); + + it('cleans up interval when component unmounts', () => { + const { unmount } = setup(); + + const clearIntervalSpy = jest.spyOn(global, 'clearInterval'); + unmount(); + + expect(clearIntervalSpy).toHaveBeenCalled(); + }); + + it('does not set up interval when shouldHide is true', () => { + const setIntervalSpy = jest.spyOn(global, 'setInterval'); + + setup({ + workflowIsArchived: true, + }); + + expect(setIntervalSpy).not.toHaveBeenCalled(); + }); + + it('clears existing interval when shouldHide becomes true', () => { + const { rerender } = setup(); + + expect(screen.getByText('Remaining: 5m 30s')).toBeInTheDocument(); + + rerender( + + ); + + expect(screen.queryByText(/Remaining:/)).not.toBeInTheDocument(); + }); +}); + +function setup({ + startTime = mockStartTime, + expectedEndTime = new Date('2024-01-01T10:07:00Z').getTime(), // 5 minutes from mockNow + prefix = 'Remaining:', + workflowIsArchived = false, + workflowCloseStatus = WorkflowExecutionCloseStatus.WORKFLOW_EXECUTION_CLOSE_STATUS_INVALID, + loadingMoreEvents = false, +}: Partial = {}) { + return render( + + ); +} diff --git a/src/views/workflow-history/workflow-history-remaining-duration-badge/helpers/__tests__/get-formatted-remaining-duration.test.ts b/src/views/workflow-history/workflow-history-remaining-duration-badge/helpers/__tests__/get-formatted-remaining-duration.test.ts new file mode 100644 index 000000000..2b6d50bda --- /dev/null +++ b/src/views/workflow-history/workflow-history-remaining-duration-badge/helpers/__tests__/get-formatted-remaining-duration.test.ts @@ -0,0 +1,75 @@ +import getFormattedRemainingDuration from '../get-formatted-remaining-duration'; + +jest.mock('@/utils/data-formatters/format-duration', () => ({ + __esModule: true, + default: jest.fn((duration) => `mocked: ${duration.seconds}s`), +})); + +const mockNow = new Date('2024-01-01T10:02:00Z'); + +describe('getFormattedRemainingDuration', () => { + beforeEach(() => { + jest.useFakeTimers(); + jest.setSystemTime(mockNow); + }); + + afterEach(() => { + jest.useRealTimers(); + }); + + it('should return null when expected duration has passed', () => { + const expectedEndTimeMs = new Date('2024-01-01T10:01:00Z').getTime(); // 1 minute ago + + const result = getFormattedRemainingDuration(expectedEndTimeMs); + + expect(result).toBeNull(); + }); + + it('should return null when expected duration exactly matches current time', () => { + const expectedEndTimeMs = new Date('2024-01-01T10:02:00Z').getTime(); // exactly now + + const result = getFormattedRemainingDuration(expectedEndTimeMs); + + expect(result).toBeNull(); + }); + + it('should return remaining time when duration has not passed', () => { + const expectedEndTimeMs = new Date('2024-01-01T10:05:00Z').getTime(); // 3 minutes from now + + const result = getFormattedRemainingDuration(expectedEndTimeMs); + + expect(result).toEqual('mocked: 180s'); // 3 minutes = 180 seconds + }); + + it('should return 1s when less than 1 second remaining', () => { + const expectedEndTimeMs = new Date('2024-01-01T10:02:00.500Z').getTime(); // 0.5 seconds from now + + const result = getFormattedRemainingDuration(expectedEndTimeMs); + + expect(result).toEqual('mocked: 1s'); + }); + + it('should work with numeric timestamp for expected end time', () => { + const expectedEndTimeMs = new Date('2024-01-01T10:05:00Z').getTime(); // 3 minutes from now + + const result = getFormattedRemainingDuration(expectedEndTimeMs); + + expect(result).toEqual('mocked: 180s'); + }); + + it('should round up partial seconds using Math.ceil', () => { + const expectedEndTimeMs = new Date('2024-01-01T10:02:01.300Z').getTime(); // 1.3 seconds from now + + const result = getFormattedRemainingDuration(expectedEndTimeMs); + + expect(result).toEqual('mocked: 2s'); // Math.ceil(1.3) = 2 + }); + + it('should handle exactly 1 second remaining', () => { + const expectedEndTimeMs = new Date('2024-01-01T10:02:01Z').getTime(); // exactly 1 second from now + + const result = getFormattedRemainingDuration(expectedEndTimeMs); + + expect(result).toEqual('mocked: 1s'); + }); +}); diff --git a/src/views/workflow-history/workflow-history-remaining-duration-badge/helpers/get-formatted-remaining-duration.ts b/src/views/workflow-history/workflow-history-remaining-duration-badge/helpers/get-formatted-remaining-duration.ts new file mode 100644 index 000000000..c64c81851 --- /dev/null +++ b/src/views/workflow-history/workflow-history-remaining-duration-badge/helpers/get-formatted-remaining-duration.ts @@ -0,0 +1,31 @@ +import formatDuration from '@/utils/data-formatters/format-duration'; +import dayjs from '@/utils/datetime/dayjs'; + +export default function getFormattedRemainingDuration( + expectedEndTimeMs: number +): string | null { + const now = dayjs(); + const expectedEnd = dayjs(expectedEndTimeMs); + + if (now.isAfter(expectedEnd)) { + return null; + } + + const remainingDurationMs = expectedEnd.diff(now); + + // Round up, to compensate for the rounding-down in the events duration badge + const seconds = Math.ceil(remainingDurationMs / 1000); + if (seconds < 1) { + return null; + } + + const duration = formatDuration( + { + seconds: seconds.toString(), + nanos: 0, + }, + { separator: ' ', minUnit: 's' } + ); + + return duration; +} diff --git a/src/views/workflow-history/workflow-history-remaining-duration-badge/workflow-history-remaining-duration-badge.styles.ts b/src/views/workflow-history/workflow-history-remaining-duration-badge/workflow-history-remaining-duration-badge.styles.ts new file mode 100644 index 000000000..0d8210dd9 --- /dev/null +++ b/src/views/workflow-history/workflow-history-remaining-duration-badge/workflow-history-remaining-duration-badge.styles.ts @@ -0,0 +1,23 @@ +import { type BadgeOverrides } from 'baseui/badge/types'; +import { type Theme } from 'baseui/theme'; + +export const overrides = { + badge: { + Badge: { + style: ({ + $theme, + $hierarchy, + }: { + $theme: Theme; + $hierarchy: string; + }) => ({ + ...$theme.typography.LabelXSmall, + ...($hierarchy === 'secondary' + ? { + color: $theme.colors.contentSecondary, + } + : null), + }), + }, + } satisfies BadgeOverrides, +}; diff --git a/src/views/workflow-history/workflow-history-remaining-duration-badge/workflow-history-remaining-duration-badge.tsx b/src/views/workflow-history/workflow-history-remaining-duration-badge/workflow-history-remaining-duration-badge.tsx new file mode 100644 index 000000000..53d266db4 --- /dev/null +++ b/src/views/workflow-history/workflow-history-remaining-duration-badge/workflow-history-remaining-duration-badge.tsx @@ -0,0 +1,57 @@ +import React, { useEffect, useState } from 'react'; + +import { Badge } from 'baseui/badge'; + +import getFormattedRemainingDuration from './helpers/get-formatted-remaining-duration'; +import { overrides } from './workflow-history-remaining-duration-badge.styles'; +import { type Props } from './workflow-history-remaining-duration-badge.types'; + +export default function WorkflowHistoryRemainingDurationBadge({ + startTime, + expectedEndTime, + prefix, + workflowIsArchived, + workflowCloseStatus, + loadingMoreEvents, +}: Props) { + const workflowEnded = + workflowIsArchived || + workflowCloseStatus !== 'WORKFLOW_EXECUTION_CLOSE_STATUS_INVALID'; + + const shouldHide = loadingMoreEvents || workflowEnded; + + const [remainingDuration, setRemainingDuration] = useState( + null + ); + + useEffect(() => { + if (shouldHide) { + setRemainingDuration(null); + return; + } + + const updateRemainingDuration = () => { + setRemainingDuration(getFormattedRemainingDuration(expectedEndTime)); + }; + + updateRemainingDuration(); + + const interval = setInterval(updateRemainingDuration, 1000); + + return () => clearInterval(interval); + }, [startTime, expectedEndTime, workflowIsArchived, shouldHide]); + + if (shouldHide || !remainingDuration) { + return null; + } + + return ( + + ); +} diff --git a/src/views/workflow-history/workflow-history-remaining-duration-badge/workflow-history-remaining-duration-badge.types.ts b/src/views/workflow-history/workflow-history-remaining-duration-badge/workflow-history-remaining-duration-badge.types.ts new file mode 100644 index 000000000..8e3c80fd2 --- /dev/null +++ b/src/views/workflow-history/workflow-history-remaining-duration-badge/workflow-history-remaining-duration-badge.types.ts @@ -0,0 +1,10 @@ +import { type WorkflowExecutionCloseStatus } from '@/__generated__/proto-ts/uber/cadence/api/v1/WorkflowExecutionCloseStatus'; + +export type Props = { + startTime: Date | string | number; + expectedEndTime: number; + prefix: string; + workflowIsArchived: boolean; + workflowCloseStatus: WorkflowExecutionCloseStatus | null | undefined; + loadingMoreEvents: boolean; +}; diff --git a/src/views/workflow-history/workflow-history-timeline-group/__tests__/workflow-history-timeline-group.test.tsx b/src/views/workflow-history/workflow-history-timeline-group/__tests__/workflow-history-timeline-group.test.tsx index 69805752f..4169b0efa 100644 --- a/src/views/workflow-history/workflow-history-timeline-group/__tests__/workflow-history-timeline-group.test.tsx +++ b/src/views/workflow-history/workflow-history-timeline-group/__tests__/workflow-history-timeline-group.test.tsx @@ -4,6 +4,7 @@ import { startWorkflowExecutionEvent } from '../../__fixtures__/workflow-history import type WorkflowHistoryEventStatusBadge from '../../workflow-history-event-status-badge/workflow-history-event-status-badge'; import type WorkflowHistoryEventsCard from '../../workflow-history-events-card/workflow-history-events-card'; import type WorkflowHistoryEventsDurationBadge from '../../workflow-history-events-duration-badge/workflow-history-events-duration-badge'; +import type WorkflowHistoryRemainingDurationBadge from '../../workflow-history-remaining-duration-badge/workflow-history-remaining-duration-badge'; import type WorkflowHistoryTimelineResetButton from '../../workflow-history-timeline-reset-button/workflow-history-timeline-reset-button'; import WorkflowHistoryTimelineGroup from '../workflow-history-timeline-group'; import { type styled } from '../workflow-history-timeline-group.styles'; @@ -16,7 +17,12 @@ jest.mock( jest.mock( '../../workflow-history-events-duration-badge/workflow-history-events-duration-badge', - () => jest.fn(() =>
Duration Badge
) + () => jest.fn(() =>
Events Duration Badge
) +); + +jest.mock( + '../../workflow-history-remaining-duration-badge/workflow-history-remaining-duration-badge', + () => jest.fn(() =>
Remaining Duration Badge
) ); jest.mock( @@ -120,18 +126,52 @@ describe('WorkflowHistoryTimelineGroup', () => { expect(mockOnReset).toHaveBeenCalledTimes(1); }); - it('should render duration badge when startTimeMs is provided', () => { + it('should render events duration badge when startTimeMs is provided', () => { setup({ startTimeMs: 1726652232190.7927, }); - expect(screen.getByText('Duration Badge')).toBeInTheDocument(); + expect(screen.getByText('Events Duration Badge')).toBeInTheDocument(); }); - it('should not render duration badge when startTimeMs is not provided', () => { + it('should not render events duration badge when startTimeMs is not provided', () => { setup({ startTimeMs: null, }); - expect(screen.queryByText('Duration Badge')).not.toBeInTheDocument(); + expect(screen.queryByText('Events Duration Badge')).not.toBeInTheDocument(); + }); + + it('should render remaining duration badge when expectedEndTimeInfo and startTimeMs are provided', () => { + setup({ + expectedEndTimeInfo: { + timeMs: Date.now() + 300000, // 5 minutes from now + prefix: 'Timer expires in:', + }, + startTimeMs: 1726652232190.7927, + }); + expect(screen.getByText('Remaining Duration Badge')).toBeInTheDocument(); + }); + + it('should not render remaining duration badge when expectedEndTimeInfo is not provided', () => { + setup({ + expectedEndTimeInfo: undefined, + startTimeMs: 1726652232190.7927, + }); + expect( + screen.queryByText('Remaining Duration Badge') + ).not.toBeInTheDocument(); + }); + + it('should not render remaining duration badge when startTimeMs is not provided', () => { + setup({ + expectedEndTimeInfo: { + timeMs: Date.now() + 300000, + prefix: 'Timer expires in:', + }, + startTimeMs: null, + }); + expect( + screen.queryByText('Remaining Duration Badge') + ).not.toBeInTheDocument(); }); }); @@ -164,6 +204,7 @@ function setup({ workflowIsArchived = false, workflowCloseTimeMs = null, startTimeMs = 1726652232190.7927, + expectedEndTimeInfo, }: Partial) { const mockOnReset = jest.fn(); const user = userEvent.setup(); @@ -187,6 +228,7 @@ function setup({ workflowCloseStatus={workflowCloseStatus} workflowIsArchived={workflowIsArchived} workflowCloseTimeMs={workflowCloseTimeMs} + expectedEndTimeInfo={expectedEndTimeInfo} /> ); return { mockOnReset, user }; diff --git a/src/views/workflow-history/workflow-history-timeline-group/workflow-history-timeline-group.tsx b/src/views/workflow-history/workflow-history-timeline-group/workflow-history-timeline-group.tsx index 53cf22753..18fac6325 100644 --- a/src/views/workflow-history/workflow-history-timeline-group/workflow-history-timeline-group.tsx +++ b/src/views/workflow-history/workflow-history-timeline-group/workflow-history-timeline-group.tsx @@ -9,6 +9,7 @@ import WorkflowHistoryEventStatusBadge from '../workflow-history-event-status-ba import WorkflowHistoryEventsCard from '../workflow-history-events-card/workflow-history-events-card'; import WorkflowHistoryEventsDurationBadge from '../workflow-history-events-duration-badge/workflow-history-events-duration-badge'; import WorkflowHistoryGroupLabel from '../workflow-history-group-label/workflow-history-group-label'; +import WorkflowHistoryRemainingDurationBadge from '../workflow-history-remaining-duration-badge/workflow-history-remaining-duration-badge'; import WorkflowHistoryTimelineResetButton from '../workflow-history-timeline-reset-button/workflow-history-timeline-reset-button'; import { @@ -25,6 +26,7 @@ export default function WorkflowHistoryTimelineGroup({ timeLabel, startTimeMs, closeTimeMs, + expectedEndTimeInfo, workflowCloseTimeMs, workflowCloseStatus, workflowIsArchived, @@ -88,6 +90,16 @@ export default function WorkflowHistoryTimelineGroup({ workflowCloseStatus={workflowCloseStatus} /> )} + {expectedEndTimeInfo && startTimeMs ? ( + + ) : null}
{timeLabel}
diff --git a/src/views/workflow-history/workflow-history-timeline-group/workflow-history-timeline-group.types.ts b/src/views/workflow-history/workflow-history-timeline-group/workflow-history-timeline-group.types.ts index 7b0149d10..4c02f9092 100644 --- a/src/views/workflow-history/workflow-history-timeline-group/workflow-history-timeline-group.types.ts +++ b/src/views/workflow-history/workflow-history-timeline-group/workflow-history-timeline-group.types.ts @@ -21,6 +21,7 @@ export type Props = Pick< | 'resetToDecisionEventId' | 'startTimeMs' | 'closeTimeMs' + | 'expectedEndTimeInfo' | 'shortLabel' > & { isLastEvent: boolean;