Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
bbe7715
Create fetcher utility
Assem-Uber Nov 3, 2025
6b09945
rename query
Assem-Uber Nov 3, 2025
1b5796a
Create hook for fetching history
Assem-Uber Nov 3, 2025
6af4ebc
Merge branch 'master' into feature/15952/create-hook-for-history-fetcher
Assem-Uber Nov 3, 2025
ae114ec
add configurable throttleMs to the hook
Assem-Uber Nov 3, 2025
8611eed
use fetcher in workflow history
Assem-Uber Nov 3, 2025
819effb
remove useKeepLoadingEvents
Assem-Uber Nov 3, 2025
459acbe
grouping utility
Assem-Uber Nov 5, 2025
ca4f432
update test cases
Assem-Uber Nov 5, 2025
af78366
change the api of onChange and add destroy method
Assem-Uber Nov 5, 2025
0539afe
replace getGroups with getState
Assem-Uber Nov 6, 2025
6ff8c4e
init grouper hook
Assem-Uber Nov 6, 2025
6f02652
Merge branch 'feature/15952/use-fetcher-in-workflow-history-page' int…
Assem-Uber Nov 6, 2025
38351d8
user grouper in history
Assem-Uber Nov 6, 2025
96c383f
update fetcher based on feedback
Assem-Uber Nov 5, 2025
1e8eae6
rename unmout to destroy
Assem-Uber Nov 5, 2025
33d1163
Create hook for fetching history
Assem-Uber Nov 3, 2025
1437921
move condition into executeImmediately
Assem-Uber Nov 5, 2025
860dc41
update destroy in method
Assem-Uber Nov 5, 2025
32335a8
mock grouper throttle
Assem-Uber Nov 6, 2025
6958fc1
fix use fetcher test case
Assem-Uber Nov 6, 2025
d59be3a
feat: Grouped events table header (#1059)
Assem-Uber Nov 5, 2025
9122ff5
chore: Fixed docker URI for kafka image (#1066)
macrotim Nov 5, 2025
a192762
chore: New feature flag for Cron List View (#1068)
macrotim Nov 6, 2025
d7b05b9
use fetcher in workflow history
Assem-Uber Nov 3, 2025
2b9eb11
fix content clicks
Assem-Uber Nov 7, 2025
7803b25
fix use initial selected event
Assem-Uber Nov 7, 2025
01e7eb6
increase page size
Assem-Uber Nov 7, 2025
7841c38
optimize first page processing
Assem-Uber Nov 8, 2025
b91b8b7
fix test cases
Assem-Uber Nov 9, 2025
4558d59
fix: History content negative z-index disallow clicks (#1069)
Assem-Uber Nov 7, 2025
877be83
feat: Use fetcher in workflow history (#1064)
Assem-Uber Nov 10, 2025
75f3a34
fix: Autocomplete for bool values (#1072)
adhityamamallan Nov 12, 2025
e9e3898
Add feature flag for Failover History (#1070)
adhityamamallan Nov 12, 2025
ccf736d
Add failover history tab (#1071)
adhityamamallan Nov 13, 2025
20eb803
chore: Added ts-node to dev-deps (#1074)
macrotim Nov 13, 2025
7e44676
feat: Hook to fetch & filter Failover History of a domain (#1075)
adhityamamallan Nov 14, 2025
5cd9853
feat: New Cron Tab (#1078)
macrotim Nov 14, 2025
48b3d92
feat: Failover History table (#1076)
adhityamamallan Nov 14, 2025
ca0466c
chore: Fix console.error pollution (#1077)
macrotim Nov 14, 2025
d4773f5
Add flag for History Page v2 (#1081)
adhityamamallan Nov 17, 2025
6ef3012
feat: Filters for Failover History Table (#1079)
adhityamamallan Nov 17, 2025
d7ad6ae
feat: Create a grouping utility that only parses new events (#1065)
Assem-Uber Nov 17, 2025
b301d4a
feat: Add modal to view full failover event (#1080)
adhityamamallan Nov 18, 2025
4e27d59
feat: New components for Workflow History V2 (#1082)
adhityamamallan Nov 18, 2025
d994f9d
update test cases
Assem-Uber Nov 20, 2025
af8b0ee
hook for history grouper
Assem-Uber Nov 20, 2025
854b81f
remove comments
Assem-Uber Nov 20, 2025
cc38cb1
fetcher updates
Assem-Uber Nov 20, 2025
2a41fc6
add placeholder for fetcher
Assem-Uber Nov 20, 2025
1f54d95
update fetcher mock
Assem-Uber Nov 20, 2025
3398d8a
fetcher start update
Assem-Uber Nov 20, 2025
fb1f87c
update todo
Assem-Uber Nov 20, 2025
650a918
Apply suggestion from @Copilot
Assem-Uber Nov 20, 2025
a9e9469
fix copilot comments
Assem-Uber Nov 20, 2025
8dcf01c
remove extra comment
Assem-Uber Nov 20, 2025
573c195
lint fix
Assem-Uber Nov 20, 2025
7306663
change lastFlattented initial value to -1
Assem-Uber Nov 20, 2025
dab50eb
Merge branch 'feature/16026/fetcher-updates' into feature/16026/use-g…
Assem-Uber Nov 20, 2025
ce6c2e5
Merge branch 'feature/16026/create-hook-for-history-grouper' into fea…
Assem-Uber Nov 20, 2025
3b0821b
Merge branch 'master' into feature/16026/use-grouper-in-history-page
Assem-Uber Nov 21, 2025
a6477fe
remove unused import
Assem-Uber Nov 21, 2025
059577a
address comments
Assem-Uber Nov 24, 2025
0db4b8c
Merge branch 'master' into feature/16026/use-grouper-in-history-page
Assem-Uber Nov 24, 2025
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
@@ -1,5 +1,3 @@
import { type HistoryEvent } from '@/__generated__/proto-ts/uber/cadence/api/v1/HistoryEvent';

import type {
HistoryEventsGroups,
PendingActivityTaskStartEvent,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,61 +1,193 @@
import { renderHook } from '@/test-utils/rtl';

import { completedDecisionTaskEvents } from '../../__fixtures__/workflow-history-decision-events';
import {
mockActivityEventGroup,
mockDecisionEventGroup,
mockTimerEventGroup,
mockSingleEventGroup,
} from '../../__fixtures__/workflow-history-event-groups';
import { type HistoryEventsGroup } from '../../workflow-history.types';
import useInitialSelectedEvent from '../use-initial-selected-event';

jest.mock('../../helpers/get-history-event-group-id');

describe('useInitialSelectedEvent', () => {
const events = [...completedDecisionTaskEvents];
const filteredEventGroupsEntries: [string, any][] = [
['group1', completedDecisionTaskEvents],
];
// Create a more realistic set of event groups with multiple types
const mockEventGroups: Record<string, HistoryEventsGroup> = {
'1': mockSingleEventGroup,
'2': mockDecisionEventGroup,
'5': mockActivityEventGroup,
'10': mockTimerEventGroup,
'11': mockDecisionEventGroup,
'12': mockActivityEventGroup,
};

it('should return shouldSearchForInitialEvent as true when selectedEventId is defined', () => {
// Filtered entries contain only a subset of all event groups
const filteredEventGroupsEntries: [string, HistoryEventsGroup][] = [
['2', mockEventGroups['2']],
['5', mockEventGroups['5']],
];

it('should return shouldSearchForInitialEvent as true when initialEventId is defined', () => {
const { result } = renderHook(() =>
useInitialSelectedEvent({
selectedEventId: '2',
events,
eventGroups: mockEventGroups,
filteredEventGroupsEntries,
})
);

expect(result.current.shouldSearchForInitialEvent).toBe(true);
});

it('should return shouldSearchForInitialEvent as false when initialEventId is undefined', () => {
it('should return shouldSearchForInitialEvent as false when selectedEventId is undefined', () => {
const filteredEventGroupsEntries: [string, HistoryEventsGroup][] = [
['1', mockEventGroups['1']],
['2', mockEventGroups['2']],
['5', mockEventGroups['5']],
];

const { result } = renderHook(() =>
useInitialSelectedEvent({
selectedEventId: undefined,
events,
eventGroups: mockEventGroups,
filteredEventGroupsEntries,
})
);

expect(result.current.shouldSearchForInitialEvent).toBe(false);
});

it('should return initialEventGroupIndex as undefined when initialEventId is defined & group is not found', () => {
it('should return initialEventGroupIndex when event is found in a group and group key matches event ID', () => {
// Filtered entries contain only a subset - event '2' is at index 1
const filteredEventGroupsEntries: [string, HistoryEventsGroup][] = [
['1', mockEventGroups['1']],
['2', mockEventGroups['2']],
['5', mockEventGroups['5']],
];

const { result } = renderHook(() =>
useInitialSelectedEvent({
selectedEventId: '500',
events,
filteredEventGroupsEntries: [],
selectedEventId: '2',
eventGroups: mockEventGroups,
filteredEventGroupsEntries,
})
);

expect(result.current.initialEventGroupIndex).toBe(1);
expect(result.current.initialEventFound).toBe(true);
});

it('should return initialEventGroupIndex as undefined when selectedEventId is defined & event is not found in filtered entries', () => {
// Group '2' exists in mockEventGroups but is filtered out from the visible list
const filteredEventGroupsEntries: [string, HistoryEventsGroup][] = [
['1', mockEventGroups['1']],
['10', mockEventGroups['10']],
];

const { result } = renderHook(() =>
useInitialSelectedEvent({
selectedEventId: '2',
eventGroups: mockEventGroups,
filteredEventGroupsEntries,
})
);

expect(result.current.initialEventGroupIndex).toBe(undefined);
});

it('should return initialEventFound as false when initialEventId is defined & event is not found', () => {
it('should find event when group key does not match event ID but group contains the event', () => {
// Group key is '5' but contains event with ID '7' (activity events)
// The hook should find the event in the group regardless of the group key not matching
// Event '7' is in group '5' which is at index 1 in filtered entries
const filteredEventGroupsEntries: [string, HistoryEventsGroup][] = [
['2', mockEventGroups['2']],
['5', mockEventGroups['5']],
];

const { result } = renderHook(() =>
useInitialSelectedEvent({
selectedEventId: '7',
eventGroups: mockEventGroups,
filteredEventGroupsEntries,
})
);

expect(result.current.initialEventFound).toBe(true);
expect(result.current.initialEventGroupIndex).toBe(1);
});

it('should return initialEventFound as false when selectedEventId is defined & event is not found in groups', () => {
// Event ID '500' doesn't exist in any group
const filteredEventGroupsEntries: [string, HistoryEventsGroup][] = [
['1', mockEventGroups['1']],
['2', mockEventGroups['2']],
['5', mockEventGroups['5']],
['10', mockEventGroups['10']],
];

const { result } = renderHook(() =>
useInitialSelectedEvent({
selectedEventId: '500',
events,
eventGroups: mockEventGroups,
filteredEventGroupsEntries,
})
);

expect(result.current.initialEventFound).toBe(false);
});

it('should return initialEventFound as false when eventGroups is empty', () => {
// Edge case: no event groups available at all
const { result } = renderHook(() =>
useInitialSelectedEvent({
selectedEventId: '2',
eventGroups: {},
filteredEventGroupsEntries: [],
})
);

expect(result.current.initialEventFound).toBe(false);
expect(result.current.initialEventGroupIndex).toBe(undefined);
});

it('should find event at correct index when multiple groups are filtered', () => {
// Realistic scenario: many groups but only some are visible after filtering
// Event '7' is in group '5' which should be at index 2 in the filtered list
const filteredEventGroupsEntries: [string, HistoryEventsGroup][] = [
['1', mockEventGroups['1']],
['2', mockEventGroups['2']],
['5', mockEventGroups['5']],
];

const { result } = renderHook(() =>
useInitialSelectedEvent({
selectedEventId: '7',
eventGroups: mockEventGroups,
filteredEventGroupsEntries,
})
);

expect(result.current.initialEventFound).toBe(true);
expect(result.current.initialEventGroupIndex).toBe(2);
});

it('should handle event at the end of filtered list', () => {
// Event '16' is in group '10' which is at the last position in the filtered list
const filteredEventGroupsEntries: [string, HistoryEventsGroup][] = [
['1', mockEventGroups['1']],
['2', mockEventGroups['2']],
['5', mockEventGroups['5']],
['10', mockEventGroups['10']],
];

const { result } = renderHook(() =>
useInitialSelectedEvent({
selectedEventId: '16',
eventGroups: mockEventGroups,
filteredEventGroupsEntries,
})
);

expect(result.current.initialEventFound).toBe(true);
expect(result.current.initialEventGroupIndex).toBe(3);
});
});
37 changes: 25 additions & 12 deletions src/views/workflow-history/hooks/use-initial-selected-event.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import { useMemo, useState } from 'react';

import getHistoryEventGroupId from '../helpers/get-history-event-group-id';
import { useMemo, useRef, useState } from 'react';

import { type UseInitialSelectedEventParams } from './use-initial-selected-event.types';

Expand All @@ -12,27 +10,42 @@ import { type UseInitialSelectedEventParams } from './use-initial-selected-event
*/
export default function useInitialSelectedEvent({
selectedEventId,
events,
eventGroups,
filteredEventGroupsEntries,
}: UseInitialSelectedEventParams) {
// preserve initial event id even if prop changed.
const [initialEventId] = useState(selectedEventId);
const foundGroupIndexRef = useRef<number | undefined>(undefined);

const initialEvent = useMemo(() => {
const initialEventGroupEntry = useMemo(() => {
if (!initialEventId) return undefined;
return events.find((e) => e.eventId === initialEventId);
}, [events, initialEventId]);

return Object.entries(eventGroups).find(([_, group]) =>
group.events.find((e) => e.eventId === initialEventId)
);
}, [eventGroups, initialEventId]);

const shouldSearchForInitialEvent = initialEventId !== undefined;
const initialEventFound = initialEvent !== undefined;
const initialEventFound = initialEventGroupEntry !== undefined;

const initialEventGroupIndex = useMemo(() => {
if (!initialEvent) return undefined;
const groupId = getHistoryEventGroupId(initialEvent);
if (!initialEventGroupEntry) return undefined;

const groupId = initialEventGroupEntry[0];
// If group index not change do not search again.
if (
foundGroupIndexRef.current &&
filteredEventGroupsEntries[foundGroupIndexRef.current][0] === groupId
)
return foundGroupIndexRef.current;

const index = filteredEventGroupsEntries.findIndex(
([id]) => id === groupId
);
return index > -1 ? index : undefined;
}, [initialEvent, filteredEventGroupsEntries]);
const foundGroupIndex = index > -1 ? index : undefined;
foundGroupIndexRef.current = foundGroupIndex;
return foundGroupIndex;
}, [initialEventGroupEntry, filteredEventGroupsEntries]);

return {
shouldSearchForInitialEvent,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { type HistoryEvent } from '@/__generated__/proto-ts/uber/cadence/api/v1/HistoryEvent';
import { type HistoryEventsGroup } from '../workflow-history.types';

export type UseInitialSelectedEventParams = {
events: HistoryEvent[];
eventGroups: Record<string, HistoryEventsGroup>;
selectedEventId?: string;
filteredEventGroupsEntries: [string, any][];
filteredEventGroupsEntries: [string, HistoryEventsGroup][];
};
26 changes: 13 additions & 13 deletions src/views/workflow-history/workflow-history.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,12 @@ import { WORKFLOW_HISTORY_PAGE_SIZE_CONFIG } from './config/workflow-history-pag
import compareUngroupedEvents from './helpers/compare-ungrouped-events';
import getSortableEventId from './helpers/get-sortable-event-id';
import getVisibleGroupsHasMissingEvents from './helpers/get-visible-groups-has-missing-events';
import { groupHistoryEvents } from './helpers/group-history-events';
import pendingActivitiesInfoToEvents from './helpers/pending-activities-info-to-events';
import pendingDecisionInfoToEvent from './helpers/pending-decision-info-to-event';
import useEventExpansionToggle from './hooks/use-event-expansion-toggle';
import useInitialSelectedEvent from './hooks/use-initial-selected-event';
import useWorkflowHistoryFetcher from './hooks/use-workflow-history-fetcher';
import useWorkflowHistoryGrouper from './hooks/use-workflow-history-grouper';
import WorkflowHistoryCompactEventCard from './workflow-history-compact-event-card/workflow-history-compact-event-card';
import { WorkflowHistoryContext } from './workflow-history-context-provider/workflow-history-context-provider';
import WorkflowHistoryHeader from './workflow-history-header/workflow-history-header';
Expand All @@ -59,6 +59,12 @@ export default function WorkflowHistory({ params }: Props) {
waitForNewEvent: true,
};

const {
eventGroups,
updateEvents: updateGrouperEvents,
updatePendingEvents: updateGrouperPendingEvents,
} = useWorkflowHistoryGrouper();

const {
historyQuery,
startLoadingHistory,
Expand All @@ -73,8 +79,7 @@ export default function WorkflowHistory({ params }: Props) {
pageSize: wfHistoryRequestArgs.pageSize,
waitForNewEvent: wfHistoryRequestArgs.waitForNewEvent,
},
//TODO: @assem.hafez replace this with grouper callback
() => {},
updateGrouperEvents,
2000
);

Expand Down Expand Up @@ -125,24 +130,19 @@ export default function WorkflowHistory({ params }: Props) {
[result]
);

const pendingHistoryEvents = useMemo(() => {
useEffect(() => {
const pendingStartActivities = pendingActivitiesInfoToEvents(
wfExecutionDescription.pendingActivities
);
const pendingStartDecision = wfExecutionDescription.pendingDecision
? pendingDecisionInfoToEvent(wfExecutionDescription.pendingDecision)
: null;

return {
updateGrouperPendingEvents({
pendingStartActivities,
pendingStartDecision,
};
}, [wfExecutionDescription]);

const eventGroups = useMemo(
() => groupHistoryEvents(events, pendingHistoryEvents),
[events, pendingHistoryEvents]
);
});
}, [wfExecutionDescription, updateGrouperPendingEvents]);

const filteredEventGroupsEntries = useMemo(
() =>
Expand Down Expand Up @@ -235,7 +235,7 @@ export default function WorkflowHistory({ params }: Props) {
shouldSearchForInitialEvent,
} = useInitialSelectedEvent({
selectedEventId: queryParams.historySelectedEventId,
events,
eventGroups,
filteredEventGroupsEntries,
});

Expand Down