diff --git a/src/views/workflow-history-v2/workflow-history-ungrouped-event/__tests__/workflow-history-ungrouped-event.test.tsx b/src/views/workflow-history-v2/workflow-history-ungrouped-event/__tests__/workflow-history-ungrouped-event.test.tsx new file mode 100644 index 000000000..4c021f5f9 --- /dev/null +++ b/src/views/workflow-history-v2/workflow-history-ungrouped-event/__tests__/workflow-history-ungrouped-event.test.tsx @@ -0,0 +1,524 @@ +import { render, screen, userEvent } from '@/test-utils/rtl'; + +import { + scheduleActivityTaskEvent, + startActivityTaskEvent, +} from '@/views/workflow-history/__fixtures__/workflow-history-activity-events'; +import { mockActivityEventGroup } from '@/views/workflow-history/__fixtures__/workflow-history-event-groups'; +import type WorkflowHistoryEventStatusBadge from '@/views/workflow-history/workflow-history-event-status-badge/workflow-history-event-status-badge'; +import type WorkflowHistoryGroupLabel from '@/views/workflow-history/workflow-history-group-label/workflow-history-group-label'; +import type WorkflowHistoryTimelineResetButton from '@/views/workflow-history/workflow-history-timeline-reset-button/workflow-history-timeline-reset-button'; +import { + type ExtendedHistoryEvent, + type ActivityHistoryGroup, +} from '@/views/workflow-history/workflow-history.types'; + +import * as generateHistoryGroupDetailsModule from '../../helpers/generate-history-group-details'; +import type { EventDetailsEntries } from '../../workflow-history-event-details/workflow-history-event-details.types'; +import type WorkflowHistoryGroupDetails from '../../workflow-history-group-details/workflow-history-group-details'; +import type { GroupDetailsEntries } from '../../workflow-history-group-details/workflow-history-group-details.types'; +import type { UngroupedEventInfo } from '../../workflow-history-ungrouped-table/workflow-history-ungrouped-table.types'; +import WorkflowHistoryUngroupedEvent from '../workflow-history-ungrouped-event'; +import type { Props } from '../workflow-history-ungrouped-event.types'; + +jest.mock('@/utils/data-formatters/format-date', () => + jest.fn((timeMs: number) => `Formatted: ${timeMs}`) +); + +jest.mock('@/utils/datetime/parse-grpc-timestamp', () => + jest.fn((timestamp) => { + if (timestamp?.seconds) { + return ( + parseInt(timestamp.seconds) * 1000 + (timestamp.nanos || 0) / 1000000 + ); + } + return null; + }) +); + +jest.mock('../../helpers/generate-history-group-details', () => jest.fn()); + +jest.mock( + '../../workflow-history-group-details/workflow-history-group-details', + () => + jest.fn(({ groupDetailsEntries, initialEventId, onClose }) => ( +
+
+ {groupDetailsEntries.length} events +
+ {groupDetailsEntries.map(([eventId, { eventLabel }]) => ( +
+ {eventLabel} +
+ ))} + {initialEventId && ( +
{initialEventId}
+ )} + {onClose && ( + + )} +
+ )) +); + +jest.mock( + '@/views/workflow-history/workflow-history-event-status-badge/workflow-history-event-status-badge', + () => + jest.fn((props) => ( +
+ {props.statusReady ? props.status : 'Loading'} +
+ )) +); + +jest.mock( + '@/views/workflow-history/workflow-history-group-label/workflow-history-group-label', + () => jest.fn((props) => <>{props.label}) +); + +jest.mock( + '@/views/workflow-history/workflow-history-timeline-reset-button/workflow-history-timeline-reset-button', + () => + jest.fn((props) => ( + + )) +); + +jest.mock( + '../../workflow-history-event-group-duration/helpers/get-formatted-events-duration', + () => jest.fn(() => '1m 30s') +); + +jest.mock( + '../../workflow-history-event-group/helpers/get-event-group-filtering-type', + () => jest.fn(() => 'ACTIVITY') +); + +const mockActivityEventGroupWithMetadata: ActivityHistoryGroup = { + ...mockActivityEventGroup, + eventsMetadata: [ + { + label: 'Scheduled', + status: 'COMPLETED', + timeMs: 1725747370599, + timeLabel: 'Scheduled at 07 Sep, 22:16:10 UTC', + }, + { + label: 'Started', + status: 'COMPLETED', + timeMs: 1725747370612, + timeLabel: 'Started at 07 Sep, 22:16:10 UTC', + }, + { + label: 'Completed', + status: 'COMPLETED', + timeMs: 1725747370632, + timeLabel: 'Completed at 07 Sep, 22:16:10 UTC', + }, + ], +}; + +const createMockEventInfo = ( + event: ExtendedHistoryEvent = scheduleActivityTaskEvent, + eventMetadataIndex = 0 +): UngroupedEventInfo => ({ + id: event.eventId ?? 'unknown', + event: event, + eventMetadata: + mockActivityEventGroupWithMetadata.eventsMetadata[eventMetadataIndex], + eventGroup: mockActivityEventGroupWithMetadata, + label: mockActivityEventGroupWithMetadata.label, + shortLabel: mockActivityEventGroupWithMetadata.shortLabel, + canReset: false, +}); + +describe(WorkflowHistoryUngroupedEvent.name, () => { + beforeEach(() => { + jest.restoreAllMocks(); + }); + + it('renders event correctly', () => { + const eventInfo = createMockEventInfo(); + setup({ eventInfo }); + + expect(screen.getByText(eventInfo.id)).toBeInTheDocument(); + expect( + screen.getByText(mockActivityEventGroupWithMetadata.label) + ).toBeInTheDocument(); + expect(screen.getByText('1m 30s')).toBeInTheDocument(); + expect(screen.getByTestId('status-badge')).toBeInTheDocument(); + expect(screen.getByText('COMPLETED')).toBeInTheDocument(); + expect(screen.getByText('Scheduled')).toBeInTheDocument(); + }); + + it('renders formatted date when eventTime is provided', () => { + const eventInfo = createMockEventInfo(); + setup({ eventInfo }); + + expect(screen.getByText(/Formatted:/)).toBeInTheDocument(); + }); + + it('does not render date when eventTime is missing', () => { + const eventInfo = createMockEventInfo({ + ...scheduleActivityTaskEvent, + eventTime: null, + }); + setup({ eventInfo }); + + expect(screen.queryByText(/Formatted:/)).not.toBeInTheDocument(); + }); + + it('renders duration when eventTime and workflowStartTimeMs are provided', () => { + const eventInfo = createMockEventInfo(); + setup({ eventInfo, workflowStartTimeMs: 1725747370000 }); + + expect(screen.getByText('1m 30s')).toBeInTheDocument(); + }); + + it('does not render duration when workflowStartTimeMs is missing', () => { + const eventInfo = createMockEventInfo(); + setup({ eventInfo, workflowStartTimeMs: null }); + + expect(screen.queryByText('1m 30s')).not.toBeInTheDocument(); + }); + + it('renders reset button when canReset is true', () => { + const eventInfo = createMockEventInfo(); + const eventInfoWithReset = { + ...eventInfo, + canReset: true, + }; + setup({ eventInfo: eventInfoWithReset }); + + expect(screen.getByTestId('reset-button')).toBeInTheDocument(); + }); + + it('calls onReset when reset button is clicked', async () => { + const eventInfo = createMockEventInfo(); + const eventInfoWithReset = { + ...eventInfo, + canReset: true, + }; + const { mockOnReset, user } = setup({ eventInfo: eventInfoWithReset }); + + const resetButton = screen.getByTestId('reset-button'); + await user.click(resetButton); + + expect(mockOnReset).toHaveBeenCalledTimes(1); + }); + + it('expands panel when isExpanded is true', () => { + const eventInfo = createMockEventInfo(); + setup({ eventInfo, isExpanded: true }); + + expect( + screen.getByTestId('workflow-history-group-details') + ).toBeInTheDocument(); + }); + + it('does not expand panel when isExpanded is false', () => { + const eventInfo = createMockEventInfo(); + setup({ eventInfo, isExpanded: false }); + + expect( + screen.queryByTestId('workflow-history-group-details') + ).not.toBeInTheDocument(); + }); + + it('calls toggleIsExpanded when panel is toggled', async () => { + const eventInfo = createMockEventInfo(); + const { mockToggleIsExpanded, user } = setup({ eventInfo }); + + const headerLabel = screen.getByText(eventInfo.id); + await user.click(headerLabel); + + expect(mockToggleIsExpanded).toHaveBeenCalledTimes(1); + }); + + it('calls toggleIsExpanded when WorkflowHistoryGroupDetails onClose is called', async () => { + const eventInfo = createMockEventInfo(); + const { mockToggleIsExpanded, user } = setup({ + eventInfo, + isExpanded: true, + }); + + const closeButton = screen.getByTestId('group-details-close'); + await user.click(closeButton); + + expect(mockToggleIsExpanded).toHaveBeenCalledTimes(1); + }); + + it('renders event summary details when available', () => { + const mockEventDetails: EventDetailsEntries = [ + { + key: 'input', + path: 'input', + value: 'test input value', + isGroup: false, + renderConfig: null, + }, + ]; + + const mockSummaryDetails: EventDetailsEntries = [ + { + key: 'activityType', + path: 'activityType', + value: 'TestActivity', + isGroup: false, + renderConfig: null, + }, + ]; + + const eventInfo = createMockEventInfo(); + setup({ + eventInfo, + mockGroupDetails: { + groupDetailsEntries: [ + [ + eventInfo.id, + { + eventLabel: 'Scheduled', + eventDetails: mockEventDetails, + }, + ], + ], + summaryDetailsEntries: [ + [ + eventInfo.id, + { + eventLabel: 'Scheduled', + eventDetails: mockSummaryDetails, + }, + ], + ], + }, + }); + + // Summary details should be rendered in the header + expect(screen.getByText('TestActivity')).toBeInTheDocument(); + }); + + it('renders empty div when event summary details are not available', () => { + const eventInfo = createMockEventInfo(); + setup({ eventInfo }); + + // Should render an empty div in the summarized details container + const container = screen.getByTestId('status-badge').parentElement; + expect(container).toBeInTheDocument(); + }); + + it('includes summary tab in groupDetailsEntries when expanded', () => { + const mockEventDetails: EventDetailsEntries = [ + { + key: 'input', + path: 'input', + value: 'test input value', + isGroup: false, + renderConfig: null, + }, + ]; + + const mockSummaryDetails: EventDetailsEntries = [ + { + key: 'activityType', + path: 'activityType', + value: 'TestActivity', + isGroup: false, + renderConfig: null, + }, + ]; + + const eventInfo = createMockEventInfo(); + const secondEventInfo = createMockEventInfo(startActivityTaskEvent, 1); + setup({ + eventInfo, + isExpanded: true, + mockGroupDetails: { + groupDetailsEntries: [ + [ + eventInfo.id, + { + eventLabel: 'Scheduled', + eventDetails: mockEventDetails, + }, + ], + [ + secondEventInfo.id, + { + eventLabel: 'Started', + eventDetails: mockEventDetails, + }, + ], + ], + summaryDetailsEntries: [ + [ + eventInfo.id, + { + eventLabel: 'Scheduled', + eventDetails: mockSummaryDetails, + }, + ], + [ + secondEventInfo.id, + { + eventLabel: 'Started', + eventDetails: mockSummaryDetails, + }, + ], + ], + }, + }); + + expect(screen.getByText('Summary')).toBeInTheDocument(); + }); + + it('does not include summary tab in groupDetailsEntries when there is only one entry', () => { + const mockEventDetails: EventDetailsEntries = [ + { + key: 'input', + path: 'input', + value: 'test input value', + isGroup: false, + renderConfig: null, + }, + ]; + + const mockSummaryDetails: EventDetailsEntries = [ + { + key: 'activityType', + path: 'activityType', + value: 'TestActivity', + isGroup: false, + renderConfig: null, + }, + ]; + + const eventInfo = createMockEventInfo(); + setup({ + eventInfo, + isExpanded: true, + mockGroupDetails: { + groupDetailsEntries: [ + [ + eventInfo.id, + { + eventLabel: 'Scheduled', + eventDetails: mockEventDetails, + }, + ], + ], + summaryDetailsEntries: [ + [ + eventInfo.id, + { + eventLabel: 'Scheduled', + eventDetails: mockSummaryDetails, + }, + ], + ], + }, + }); + + expect(screen.queryByText('Summary')).not.toBeInTheDocument(); + }); + + it('renders status badge with correct status', () => { + const eventInfo = createMockEventInfo(); + setup({ eventInfo }); + + expect(screen.getByTestId('status-badge')).toBeInTheDocument(); + expect(screen.getByText('COMPLETED')).toBeInTheDocument(); + }); + + it('renders event metadata label', () => { + const eventInfo = createMockEventInfo(); + setup({ eventInfo }); + + expect(screen.getByText('Scheduled')).toBeInTheDocument(); + }); +}); + +function setup({ + eventInfo, + workflowStartTimeMs = 1725747370000, + decodedPageUrlParams = { + domain: 'test-domain', + cluster: 'test-cluster', + workflowId: 'test-workflow-id', + runId: 'test-run-id', + workflowTab: 'history', + }, + isExpanded = false, + toggleIsExpanded = jest.fn(), + animateOnEnter = false, + onReset = jest.fn(), + mockGroupDetails, +}: Partial & { + mockGroupDetails?: { + groupDetailsEntries: GroupDetailsEntries; + summaryDetailsEntries: GroupDetailsEntries; + }; +} = {}) { + const mockGenerateHistoryGroupDetails = jest.spyOn( + generateHistoryGroupDetailsModule, + 'default' + ); + + if (mockGroupDetails) { + mockGenerateHistoryGroupDetails.mockReturnValue(mockGroupDetails); + } else { + const defaultMockEventDetails: EventDetailsEntries = [ + { + key: 'testKey', + path: 'testPath', + value: 'testValue', + isGroup: false, + renderConfig: null, + }, + ]; + + const defaultEventInfo = eventInfo ?? createMockEventInfo(); + mockGenerateHistoryGroupDetails.mockReturnValue({ + groupDetailsEntries: defaultEventInfo.eventGroup.events + .filter((event) => event.eventId) + .map((event, index) => [ + event.eventId!, + { + eventLabel: + defaultEventInfo.eventGroup.eventsMetadata[index]?.label ?? + 'Unknown', + eventDetails: defaultMockEventDetails, + }, + ]), + summaryDetailsEntries: [], + }); + } + + const mockToggleIsExpanded = toggleIsExpanded || jest.fn(); + const mockOnReset = onReset || jest.fn(); + const user = userEvent.setup(); + + const defaultEventInfo = eventInfo ?? createMockEventInfo(); + + render( + + ); + + return { + mockToggleIsExpanded, + mockOnReset, + user, + }; +} diff --git a/src/views/workflow-history-v2/workflow-history-ungrouped-event/workflow-history-ungrouped-event.styles.ts b/src/views/workflow-history-v2/workflow-history-ungrouped-event/workflow-history-ungrouped-event.styles.ts index 8a65a8bd9..499737df4 100644 --- a/src/views/workflow-history-v2/workflow-history-ungrouped-event/workflow-history-ungrouped-event.styles.ts +++ b/src/views/workflow-history-v2/workflow-history-ungrouped-event/workflow-history-ungrouped-event.styles.ts @@ -1,9 +1,160 @@ import { styled as createStyled, type Theme } from 'baseui'; +import { type PanelOverrides } from 'baseui/accordion'; +import { type BadgeOverrides } from 'baseui/badge'; +import { type StyleObject } from 'styletron-react'; + +import { type WorkflowHistoryEventFilteringType } from '@/views/workflow-history/workflow-history-filters-type/workflow-history-filters-type.types'; + +import workflowHistoryEventFilteringTypeColorsConfig from '../config/workflow-history-event-filtering-type-colors.config'; +import { WORKFLOW_HISTORY_UNGROUPED_GRID_TEMPLATE_COLUMNS } from '../workflow-history-ungrouped-table/workflow-history-ungrouped-table.constants'; export const styled = { - TempContainer: createStyled('div', ({ $theme }: { $theme: Theme }) => ({ - ...$theme.typography.MonoParagraphXSmall, - padding: $theme.sizing.scale300, - ...$theme.borders.border100, + HeaderContent: createStyled('div', ({ $theme }: { $theme: Theme }) => ({ + ...$theme.typography.ParagraphSmall, + display: 'flex', + flexDirection: 'column', + gap: $theme.sizing.scale200, + paddingBottom: $theme.sizing.scale300, + [$theme.mediaQuery.medium]: { + display: 'grid', + gridTemplateColumns: WORKFLOW_HISTORY_UNGROUPED_GRID_TEMPLATE_COLUMNS, + width: '100%', + alignItems: 'center', + gap: $theme.sizing.scale600, + paddingBottom: 0, + }, + })), + HeaderLabel: createStyled('div', ({ $theme }: { $theme: Theme }) => ({ + ...$theme.typography.LabelSmall, + whiteSpace: 'nowrap', + textOverflow: 'ellipsis', + overflow: 'hidden', })), + StatusContainer: createStyled('div', ({ $theme }: { $theme: Theme }) => ({ + display: 'flex', + gap: $theme.sizing.scale300, + alignItems: 'center', + })), + SummarizedDetailsContainer: createStyled('div', { + overflow: 'hidden', + }), + ActionsContainer: createStyled('div', ({ $theme }: { $theme: Theme }) => ({ + display: 'flex', + gap: $theme.sizing.scale300, + alignItems: 'center', + [$theme.mediaQuery.medium]: { + margin: `-${$theme.sizing.scale200} 0`, + }, + })), + GroupDetailsGridContainer: createStyled('div', { + display: 'grid', + gridTemplateColumns: WORKFLOW_HISTORY_UNGROUPED_GRID_TEMPLATE_COLUMNS, + }), + GroupDetailsNameSpacer: createStyled( + 'div', + ({ $theme }: { $theme: Theme }) => ({ + display: 'none', + [$theme.mediaQuery.medium]: { + gridColumn: '1 / span 3', + }, + }) + ), + GroupDetailsContainer: createStyled( + 'div', + ({ $theme }: { $theme: Theme }) => ({ + gridColumn: '1 / -1', + [$theme.mediaQuery.medium]: { + gridColumn: '4 / -1', + }, + border: `2px solid ${$theme.colors.borderOpaque}`, + borderRadius: $theme.borders.radius400, + padding: $theme.sizing.scale500, + backgroundColor: $theme.colors.backgroundPrimary, + }) + ), }; + +export const overrides = ( + eventFilteringType: WorkflowHistoryEventFilteringType, + animateOnEnter?: boolean +) => ({ + panel: { + PanelContainer: { + style: ({ $theme }: { $theme: Theme }): StyleObject => ({ + borderColor: $theme.borders.border100.borderColor, + borderStyle: $theme.borders.border100.borderStyle, + borderRadius: 0, + borderTopWidth: $theme.borders.border100.borderWidth, + borderBottomWidth: 0, + borderLeftWidth: 0, + borderRightWidth: 0, + marginTop: 0, + marginBottom: 0, + overflow: 'hidden', + }), + }, + Header: { + style: ({ $theme }: { $theme: Theme }): StyleObject => ({ + // https://github.com/uber/baseweb/blob/main/src/accordion/styled-components.ts#L50 + // Since the original Panel uses longhand properties, we need to use longhand in overrides + paddingTop: $theme.sizing.scale300, + paddingBottom: $theme.sizing.scale300, + paddingLeft: $theme.sizing.scale300, + paddingRight: $theme.sizing.scale300, + backgroundColor: 'inherit', + display: 'flex', + alignItems: 'center', + ':hover': { + backgroundColor: + workflowHistoryEventFilteringTypeColorsConfig[eventFilteringType] + .backgroundHighlighted, + }, + ...(animateOnEnter && { + animationDuration: '2s', + animationName: { + from: { + backgroundColor: + workflowHistoryEventFilteringTypeColorsConfig[ + eventFilteringType + ].backgroundHighlighted, + }, + to: { + backgroundColor: 'inherit', + }, + }, + }), + }), + }, + Content: { + style: ({ $theme }: { $theme: Theme }): StyleObject => ({ + // https://github.com/uber/baseweb/blob/main/src/accordion/styled-components.ts#L102 + // Since the original Panel uses longhand properties, we need to use longhand in overrides + paddingTop: 0, + paddingBottom: $theme.sizing.scale600, + paddingLeft: 0, + paddingRight: 0, + [$theme.mediaQuery.medium]: { + paddingLeft: $theme.sizing.scale700, + paddingRight: $theme.sizing.scale700, + }, + backgroundColor: 'inherit', + }), + }, + ToggleIcon: { + style: { + display: 'none', + }, + }, + } satisfies PanelOverrides, + badge: { + Badge: { + style: ({ $theme }: { $theme: Theme }): StyleObject => ({ + backgroundColor: $theme.colors.backgroundSecondary, + color: $theme.colors.contentSecondary, + ...$theme.typography.LabelXSmall, + whiteSpace: 'nowrap', + marginLeft: $theme.sizing.scale100, + }), + }, + } satisfies BadgeOverrides, +}); diff --git a/src/views/workflow-history-v2/workflow-history-ungrouped-event/workflow-history-ungrouped-event.tsx b/src/views/workflow-history-v2/workflow-history-ungrouped-event/workflow-history-ungrouped-event.tsx index 47d543dc9..afdd79f66 100644 --- a/src/views/workflow-history-v2/workflow-history-ungrouped-event/workflow-history-ungrouped-event.tsx +++ b/src/views/workflow-history-v2/workflow-history-ungrouped-event/workflow-history-ungrouped-event.tsx @@ -1,8 +1,159 @@ -import { styled } from './workflow-history-ungrouped-event.styles'; +import { useCallback, useMemo } from 'react'; + +import { Panel } from 'baseui/accordion'; +import { MdOutlineCircle } from 'react-icons/md'; + +import formatDate from '@/utils/data-formatters/format-date'; +import parseGrpcTimestamp from '@/utils/datetime/parse-grpc-timestamp'; +import WorkflowHistoryEventStatusBadge from '@/views/workflow-history/workflow-history-event-status-badge/workflow-history-event-status-badge'; +import WorkflowHistoryGroupLabel from '@/views/workflow-history/workflow-history-group-label/workflow-history-group-label'; +import WorkflowHistoryTimelineResetButton from '@/views/workflow-history/workflow-history-timeline-reset-button/workflow-history-timeline-reset-button'; + +import workflowHistoryEventFilteringTypeColorsConfig from '../config/workflow-history-event-filtering-type-colors.config'; +import generateHistoryGroupDetails from '../helpers/generate-history-group-details'; +import WorkflowHistoryDetailsRow from '../workflow-history-details-row/workflow-history-details-row'; +import getEventGroupFilteringType from '../workflow-history-event-group/helpers/get-event-group-filtering-type'; +import getSummaryTabContentEntry from '../workflow-history-event-group/helpers/get-summary-tab-content-entry'; +import getFormattedEventsDuration from '../workflow-history-event-group-duration/helpers/get-formatted-events-duration'; +import WorkflowHistoryGroupDetails from '../workflow-history-group-details/workflow-history-group-details'; + +import { + styled, + overrides as getOverrides, +} from './workflow-history-ungrouped-event.styles'; import { type Props } from './workflow-history-ungrouped-event.types'; -export default function WorkflowHistoryUngroupedEvent({ eventInfo }: Props) { +export default function WorkflowHistoryUngroupedEvent({ + eventInfo, + animateOnEnter, + workflowStartTimeMs, + onReset, + decodedPageUrlParams, + isExpanded, + toggleIsExpanded, +}: Props) { + const eventFilteringType = getEventGroupFilteringType(eventInfo.eventGroup); + + const overrides = getOverrides(eventFilteringType, animateOnEnter); + + const handleReset = useCallback(() => { + if (onReset) { + onReset(); + } + }, [onReset]); + + const { groupDetailsEntries, summaryDetailsEntries } = useMemo( + () => generateHistoryGroupDetails(eventInfo.eventGroup), + [eventInfo.eventGroup] + ); + + const eventSummaryDetails = summaryDetailsEntries.find( + ([eventId]) => eventId === eventInfo.id + )?.[1].eventDetails; + + const groupSummaryDetails = useMemo( + () => + summaryDetailsEntries.flatMap( + ([_eventId, { eventDetails }]) => eventDetails + ), + [summaryDetailsEntries] + ); + + const groupDetailsEntriesWithSummary = useMemo( + () => [ + ...(groupSummaryDetails.length > 0 && groupDetailsEntries.length > 1 + ? [ + getSummaryTabContentEntry({ + groupId: eventInfo.eventGroup.firstEventId ?? 'unknown', + summaryDetails: groupSummaryDetails, + }), + ] + : []), + ...groupDetailsEntries, + ], + [ + eventInfo.eventGroup.firstEventId, + groupDetailsEntries, + groupSummaryDetails, + ] + ); + return ( - {JSON.stringify(eventInfo)} + + + {eventInfo.id} + + + + + + {eventInfo.eventMetadata.label} + +
+ {eventInfo.event.eventTime + ? formatDate(parseGrpcTimestamp(eventInfo.event.eventTime)) + : null} +
+
+ {eventInfo.event.eventTime && workflowStartTimeMs + ? getFormattedEventsDuration( + workflowStartTimeMs, + parseGrpcTimestamp(eventInfo.event.eventTime) + ) + : null} +
+ + {eventSummaryDetails && eventSummaryDetails.length > 0 ? ( + + ) : ( +
+ )} + + + {eventInfo.canReset && ( + + )} + + + } + expanded={isExpanded} + onChange={toggleIsExpanded} + overrides={overrides.panel} + > + + + + toggleIsExpanded()} + /> + + + ); }