Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,14 @@ jest.mock(
))
);

jest.mock(
'../workflow-history-ungrouped-table/workflow-history-ungrouped-table',
() =>
jest.fn(() => (
<div data-testid="workflow-history-ungrouped-table">Ungrouped Table</div>
))
);

jest.mock('@/utils/decode-url-params', () => jest.fn((params) => params));

const mockResetAllFilters = jest.fn();
Expand Down Expand Up @@ -170,15 +178,19 @@ describe(WorkflowHistoryV2.name, () => {
expect(
await screen.findByTestId('workflow-history-grouped-table')
).toBeInTheDocument();
expect(screen.queryByText('WIP: ungrouped table')).not.toBeInTheDocument();
expect(
screen.queryByTestId('workflow-history-ungrouped-table')
).not.toBeInTheDocument();
});

it('should render grouped table by default when ungroupedHistoryViewEnabled is not set and user preference is null', async () => {
await setup({ ungroupedViewPreference: null });
expect(
await screen.findByTestId('workflow-history-grouped-table')
).toBeInTheDocument();
expect(screen.queryByText('WIP: ungrouped table')).not.toBeInTheDocument();
expect(
screen.queryByTestId('workflow-history-ungrouped-table')
).not.toBeInTheDocument();
});

it('should render ungrouped table when ungroupedHistoryViewEnabled query param is true', async () => {
Expand All @@ -187,7 +199,9 @@ describe(WorkflowHistoryV2.name, () => {
ungroupedHistoryViewEnabled: true,
},
});
expect(await screen.findByText('WIP: ungrouped table')).toBeInTheDocument();
expect(
await screen.findByTestId('workflow-history-ungrouped-table')
).toBeInTheDocument();
expect(
screen.queryByTestId('workflow-history-grouped-table')
).not.toBeInTheDocument();
Expand All @@ -202,12 +216,16 @@ describe(WorkflowHistoryV2.name, () => {
expect(
await screen.findByTestId('workflow-history-grouped-table')
).toBeInTheDocument();
expect(screen.queryByText('WIP: ungrouped table')).not.toBeInTheDocument();
expect(
screen.queryByTestId('workflow-history-ungrouped-table')
).not.toBeInTheDocument();
});

it('should render ungrouped table when user preference is true and query param is not set', async () => {
await setup({ ungroupedViewPreference: true });
expect(await screen.findByText('WIP: ungrouped table')).toBeInTheDocument();
expect(
await screen.findByTestId('workflow-history-ungrouped-table')
).toBeInTheDocument();
expect(
screen.queryByTestId('workflow-history-grouped-table')
).not.toBeInTheDocument();
Expand All @@ -220,7 +238,9 @@ describe(WorkflowHistoryV2.name, () => {
});

// Should show ungrouped table even though preference is false
expect(await screen.findByText('WIP: ungrouped table')).toBeInTheDocument();
expect(
await screen.findByTestId('workflow-history-ungrouped-table')
).toBeInTheDocument();
});

it('should use user preference when query param is undefined for ungrouped view', async () => {
Expand All @@ -230,7 +250,9 @@ describe(WorkflowHistoryV2.name, () => {
});

// Should use preference (true) when query param is undefined
expect(await screen.findByText('WIP: ungrouped table')).toBeInTheDocument();
expect(
await screen.findByTestId('workflow-history-ungrouped-table')
).toBeInTheDocument();
});

it('should call setUngroupedViewUserPreference and setQueryParams when toggle is clicked from grouped to ungrouped', async () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { styled as createStyled, type Theme } from 'baseui';

export const styled = {
TempContainer: createStyled('div', ({ $theme }: { $theme: Theme }) => ({
...$theme.typography.MonoParagraphXSmall,
padding: $theme.sizing.scale300,
...$theme.borders.border100,
})),
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { styled } from './workflow-history-ungrouped-event.styles';
import { type Props } from './workflow-history-ungrouped-event.types';

export default function WorkflowHistoryUngroupedEvent({ eventInfo }: Props) {
Copy link

Copilot AI Dec 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The component receives multiple props (workflowStartTimeMs, decodedPageUrlParams, isExpanded, toggleIsExpanded, animateOnEnter, onReset) but only destructures and uses eventInfo. While this is a placeholder implementation, consider at least destructuring the unused props to avoid ESLint warnings, or using a rest parameter pattern if they're genuinely not needed yet.

Suggested change
export default function WorkflowHistoryUngroupedEvent({ eventInfo }: Props) {
export default function WorkflowHistoryUngroupedEvent({
workflowStartTimeMs,
decodedPageUrlParams,
isExpanded,
toggleIsExpanded,
animateOnEnter,
onReset,
eventInfo,
}: Props) {

Copilot uses AI. Check for mistakes.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting, does this shows a warning ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope, it would show a warning if I implemented Copilot's suggestion though.
I think this is a good argument in favour of having inline "Props" instead of keeping them in a separate file.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does inline props help here ?

return (
<styled.TempContainer>{JSON.stringify(eventInfo)}</styled.TempContainer>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { type WorkflowPageTabsParams } from '@/views/workflow-page/workflow-page-tabs/workflow-page-tabs.types';

import { type UngroupedEventInfo } from '../workflow-history-ungrouped-table/workflow-history-ungrouped-table.types';

export type Props = {
// Core data props
eventInfo: UngroupedEventInfo;
workflowStartTimeMs: number | null;
decodedPageUrlParams: WorkflowPageTabsParams;

// Expansion state
isExpanded: boolean;
toggleIsExpanded: () => void;

// UI behavior
animateOnEnter?: boolean;
onReset?: () => void;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,265 @@
import React from 'react';

import { VirtuosoMockContext } from 'react-virtuoso';

import { render, screen, userEvent, waitFor } from '@/test-utils/rtl';

import { type RequestError } from '@/utils/request/request-error';
import { mockActivityEventGroup } from '@/views/workflow-history/__fixtures__/workflow-history-event-groups';
import { type HistoryEventsGroup } from '@/views/workflow-history/workflow-history.types';
import { type WorkflowPageTabsParams } from '@/views/workflow-page/workflow-page-tabs/workflow-page-tabs.types';

import WorkflowHistoryUngroupedTable from '../workflow-history-ungrouped-table';

jest.mock(
'@/views/workflow-history/workflow-history-timeline-load-more/workflow-history-timeline-load-more',
() =>
jest.fn(({ error, hasNextPage, isFetchingNextPage, fetchNextPage }) => (
<div data-testid="timeline-load-more">
{error && <div data-testid="load-more-error">Error loading more</div>}
{hasNextPage && <div data-testid="has-next-page">Has more</div>}
{isFetchingNextPage && <div data-testid="is-fetching">Fetching...</div>}
<button onClick={fetchNextPage} data-testid="fetch-more-button">
Fetch More
</button>
</div>
))
);

jest.mock(
'../../workflow-history-ungrouped-event/workflow-history-ungrouped-event',
() =>
jest.fn(
({
eventInfo,
isExpanded,
toggleIsExpanded,
onReset,
animateOnEnter,
}) => (
<div
data-testid="workflow-history-ungrouped-event"
data-expanded={isExpanded}
data-animate-on-enter={animateOnEnter}
data-event-id={eventInfo.id}
>
<button onClick={toggleIsExpanded}>Toggle Event</button>
<div>Event ID: {eventInfo.id}</div>
<div>Label: {eventInfo.label}</div>
{onReset && <button onClick={onReset}>Reset Event</button>}
</div>
)
)
);

describe(WorkflowHistoryUngroupedTable.name, () => {
it('should render all column headers in correct order', () => {
setup();

expect(screen.getByText('ID')).toBeInTheDocument();
expect(screen.getByText('Event group')).toBeInTheDocument();
expect(screen.getByText('Status')).toBeInTheDocument();
expect(screen.getByText('Time')).toBeInTheDocument();
expect(screen.getByText('Duration')).toBeInTheDocument();
expect(screen.getByText('Details')).toBeInTheDocument();
});

it('should render events from event groups', () => {
const mockEventGroups: Array<[string, HistoryEventsGroup]> = [
['group-1', mockActivityEventGroup],
];
setup({ eventGroupsById: mockEventGroups });

const events = screen.getAllByTestId('workflow-history-ungrouped-event');
expect(events.length).toBeGreaterThan(0);
expect(events[0]).toHaveTextContent('Event ID:');
});

it('should render events with correct labels from groups', () => {
const mockEventGroups: Array<[string, HistoryEventsGroup]> = [
['group-1', mockActivityEventGroup],
];
setup({ eventGroupsById: mockEventGroups });

const events = screen.getAllByTestId('workflow-history-ungrouped-event');
expect(events[0]).toHaveTextContent(
`Label: ${mockActivityEventGroup.label}`
);
});

it('should handle event expansion toggle', async () => {
const { user, mockToggleIsEventExpanded } = setup({
eventGroupsById: [['group-1', mockActivityEventGroup]],
});

const toggleButtons = screen.getAllByText('Toggle Event');
await user.click(toggleButtons[0]);

const firstEventId =
mockActivityEventGroup.events[0].eventId ??
mockActivityEventGroup.events[0].computedEventId;
expect(mockToggleIsEventExpanded).toHaveBeenCalledWith(firstEventId);
});

it('should pass isExpanded state to events', () => {
const mockEventGroups: Array<[string, HistoryEventsGroup]> = [
['group-1', mockActivityEventGroup],
];
const firstEventId =
mockActivityEventGroup.events[0].eventId ??
mockActivityEventGroup.events[0].computedEventId;

setup({
eventGroupsById: mockEventGroups,
getIsEventExpanded: jest.fn((id) => id === firstEventId),
});

const events = screen.getAllByTestId('workflow-history-ungrouped-event');
expect(events[0]).toHaveAttribute('data-expanded', 'true');
if (events.length > 1) {
expect(events[1]).toHaveAttribute('data-expanded', 'false');
}
});

it('should pass hasMoreEvents to load more component', () => {
setup({
hasMoreEvents: true,
isFetchingMoreEvents: false,
fetchMoreEvents: jest.fn(),
});

expect(screen.getByTestId('timeline-load-more')).toBeInTheDocument();
expect(screen.getByTestId('has-next-page')).toBeInTheDocument();
});

it('should pass animateOnEnter for selectedEventId', async () => {
const mockEventGroups: Array<[string, HistoryEventsGroup]> = [
['group-1', mockActivityEventGroup],
];
const firstEventId =
mockActivityEventGroup.events[0].eventId ??
mockActivityEventGroup.events[0].computedEventId;

setup({
eventGroupsById: mockEventGroups,
selectedEventId: firstEventId,
});

await waitFor(() => {
const events = screen.getAllByTestId('workflow-history-ungrouped-event');
expect(events[0]).toHaveAttribute('data-animate-on-enter', 'true');
});
});

it('should call resetToDecisionEventId when reset button is clicked on resettable event', async () => {
const mockEventGroups: Array<[string, HistoryEventsGroup]> = [
[
'group-1',
{
...mockActivityEventGroup,
resetToDecisionEventId: mockActivityEventGroup.events[0].eventId,
},
],
];
const { user, mockResetToDecisionEventId } = setup({
eventGroupsById: mockEventGroups,
});

const resetButtons = screen.getAllByText('Reset Event');
await user.click(resetButtons[0]);

const firstEventId =
mockActivityEventGroup.events[0].eventId ??
mockActivityEventGroup.events[0].computedEventId;
expect(mockResetToDecisionEventId).toHaveBeenCalledWith(firstEventId);
});

it('should not show reset button for non-resettable events', () => {
const mockEventGroups: Array<[string, HistoryEventsGroup]> = [
[
'group-1',
{
...mockActivityEventGroup,
resetToDecisionEventId: undefined,
},
],
];
setup({ eventGroupsById: mockEventGroups });

expect(screen.queryByText('Reset Event')).not.toBeInTheDocument();
});
});

function setup({
eventGroupsById = [],
error = null,
hasMoreEvents = false,
isFetchingMoreEvents = false,
fetchMoreEvents = jest.fn(),
setVisibleRange = jest.fn(),
initialStartIndex,
decodedPageUrlParams = {
domain: 'test-domain',
cluster: 'test-cluster',
workflowId: 'test-workflow-id',
runId: 'test-run-id',
workflowTab: 'history',
},
selectedEventId,
getIsEventExpanded = jest.fn(() => false),
toggleIsEventExpanded = jest.fn(),
resetToDecisionEventId = jest.fn(),
}: {
eventGroupsById?: Array<[string, HistoryEventsGroup]>;
error?: RequestError | null;
hasMoreEvents?: boolean;
isFetchingMoreEvents?: boolean;
fetchMoreEvents?: () => void;
setVisibleRange?: ({
startIndex,
endIndex,
}: {
startIndex: number;
endIndex: number;
}) => void;
initialStartIndex?: number;
decodedPageUrlParams?: WorkflowPageTabsParams;
selectedEventId?: string;
getIsEventExpanded?: (eventId: string) => boolean;
toggleIsEventExpanded?: (eventId: string) => void;
resetToDecisionEventId?: (decisionEventId: string) => void;
} = {}) {
const virtuosoRef = { current: null };
const user = userEvent.setup();

render(
<VirtuosoMockContext.Provider
value={{ viewportHeight: 1000, itemHeight: 36 }}
>
<WorkflowHistoryUngroupedTable
eventGroupsById={eventGroupsById}
virtuosoRef={virtuosoRef}
initialStartIndex={initialStartIndex}
setVisibleRange={setVisibleRange}
decodedPageUrlParams={decodedPageUrlParams}
selectedEventId={selectedEventId}
getIsEventExpanded={getIsEventExpanded}
toggleIsEventExpanded={toggleIsEventExpanded}
resetToDecisionEventId={resetToDecisionEventId}
error={error}
hasMoreEvents={hasMoreEvents}
fetchMoreEvents={fetchMoreEvents}
isFetchingMoreEvents={isFetchingMoreEvents}
/>
</VirtuosoMockContext.Provider>
);

return {
user,
virtuosoRef,
mockFetchMoreEvents: fetchMoreEvents,
mockSetVisibleRange: setVisibleRange,
mockToggleIsEventExpanded: toggleIsEventExpanded,
mockResetToDecisionEventId: resetToDecisionEventId,
};
}
Loading