Skip to content

Commit 1b18bc4

Browse files
Rewrite history page filters to act after event grouping (#937)
- Rewrite workflow-history to allow filtering only event groups, to avoid the (expensive) event grouping operation every time filters are applied. - Modify existing filter functions to work with event groups - Add unit test coverage for filtering event groups by type Misc: - Add fixtures for more types of event groups - Change Virtuoso scroll-to-event behaviour to automatically snap to a selected event instead of scrolling to it - Fix typo in fixture file name
1 parent 8955503 commit 1b18bc4

13 files changed

+278
-152
lines changed

src/views/workflow-history/__fixtures__/all-workflow-event-types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ import {
3636
signalExternalWorkflowEvent,
3737
failSignalExternalWorkflowEvent,
3838
initiateSignalExternalWorkflowEvent,
39-
} from './workflow-history-singal-external-workflow-events';
39+
} from './workflow-history-signal-external-workflow-events';
4040
import {
4141
startWorkflowExecutionEvent,
4242
cancelRequestActivityTaskEvent,

src/views/workflow-history/__fixtures__/workflow-history-event-groups.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,17 @@ import {
22
type TimerHistoryGroup,
33
type ActivityHistoryGroup,
44
type SingleEventHistoryGroup,
5+
type DecisionHistoryGroup,
6+
type ChildWorkflowExecutionHistoryGroup,
7+
type SignalExternalWorkflowExecutionHistoryGroup,
8+
type RequestCancelExternalWorkflowExecutionHistoryGroup,
59
} from '../workflow-history.types';
610

711
import { completedActivityTaskEvents } from './workflow-history-activity-events';
12+
import { completedChildWorkflowEvents } from './workflow-history-child-workflow-events';
13+
import { completedDecisionTaskEvents } from './workflow-history-decision-events';
14+
import { requestedCancelExternalWorkflowEvents } from './workflow-history-request-cancel-external-workflow-events';
15+
import { signaledExternalWorkflowEvents } from './workflow-history-signal-external-workflow-events';
816
import { startWorkflowExecutionEvent } from './workflow-history-single-events';
917
import { startTimerTaskEvent } from './workflow-history-timer-events';
1018

@@ -20,6 +28,18 @@ export const mockActivityEventGroup: ActivityHistoryGroup = {
2028
events: completedActivityTaskEvents,
2129
};
2230

31+
export const mockDecisionEventGroup: DecisionHistoryGroup = {
32+
label: 'Mock decision',
33+
groupType: 'Decision',
34+
status: 'COMPLETED',
35+
eventsMetadata: [],
36+
hasMissingEvents: false,
37+
timeMs: 1725747370632,
38+
startTimeMs: 1725747370599,
39+
timeLabel: 'Mock time label',
40+
events: completedDecisionTaskEvents,
41+
};
42+
2343
export const mockTimerEventGroup: TimerHistoryGroup = {
2444
label: 'Mock event',
2545
groupType: 'Timer',
@@ -32,6 +52,44 @@ export const mockTimerEventGroup: TimerHistoryGroup = {
3252
events: [startTimerTaskEvent],
3353
};
3454

55+
export const mockChildWorkflowEventGroup: ChildWorkflowExecutionHistoryGroup = {
56+
label: 'Mock child workflow',
57+
groupType: 'ChildWorkflowExecution',
58+
status: 'COMPLETED',
59+
eventsMetadata: [],
60+
hasMissingEvents: false,
61+
timeMs: 1725769674218,
62+
startTimeMs: 1725769671830,
63+
timeLabel: 'Mock time label',
64+
events: completedChildWorkflowEvents,
65+
};
66+
67+
export const mockSignalExternalWorkflowEventGroup: SignalExternalWorkflowExecutionHistoryGroup =
68+
{
69+
label: 'Mock signal external workflow',
70+
groupType: 'SignalExternalWorkflowExecution',
71+
status: 'COMPLETED',
72+
eventsMetadata: [],
73+
hasMissingEvents: false,
74+
timeMs: 1725769570375,
75+
startTimeMs: 1725769470356,
76+
timeLabel: 'Mock time label',
77+
events: signaledExternalWorkflowEvents,
78+
};
79+
80+
export const mockRequestCancelExternalWorkflowEventGroup: RequestCancelExternalWorkflowExecutionHistoryGroup =
81+
{
82+
label: 'Mock request cancel external workflow',
83+
groupType: 'RequestCancelExternalWorkflowExecution',
84+
status: 'COMPLETED',
85+
eventsMetadata: [],
86+
hasMissingEvents: false,
87+
timeMs: 1725749570927,
88+
startTimeMs: 1725749470886,
89+
timeLabel: 'Mock time label',
90+
events: requestedCancelExternalWorkflowEvents,
91+
};
92+
3593
export const mockSingleEventGroup: SingleEventHistoryGroup = {
3694
label: 'Mock event',
3795
groupType: 'Event',

src/views/workflow-history/config/workflow-history-filters.config.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import filterEventsByEventStatus from '../workflow-history-filters-status/helpers/filter-events-by-event-status';
1+
import filterGroupsByGroupStatus from '../workflow-history-filters-status/helpers/filter-groups-by-group-status';
22
import WorkflowHistoryFiltersStatus from '../workflow-history-filters-status/workflow-history-filters-status';
33
import { type WorkflowHistoryFiltersStatusValue } from '../workflow-history-filters-status/workflow-history-filters-status.types';
4-
import filterEventsByEventType from '../workflow-history-filters-type/helpers/filter-events-by-event-type';
4+
import filterGroupsByGroupType from '../workflow-history-filters-type/helpers/filter-groups-by-group-type';
55
import WorkflowHistoryFiltersType from '../workflow-history-filters-type/workflow-history-filters-type';
66
import { type WorkflowHistoryFiltersTypeValue } from '../workflow-history-filters-type/workflow-history-filters-type.types';
77
import { type WorkflowHistoryFilterConfig } from '../workflow-history.types';
@@ -15,16 +15,14 @@ const workflowHistoryFiltersConfig: [
1515
getValue: (v) => ({ historyEventTypes: v.historyEventTypes }),
1616
formatValue: (v) => v,
1717
component: WorkflowHistoryFiltersType,
18-
filterFunc: filterEventsByEventType,
19-
filterTarget: 'event',
18+
filterFunc: filterGroupsByGroupType,
2019
},
2120
{
2221
id: 'historyEventStatus',
2322
getValue: (v) => ({ historyEventStatuses: v.historyEventStatuses }),
2423
formatValue: (v) => v,
2524
component: WorkflowHistoryFiltersStatus,
26-
filterFunc: filterEventsByEventStatus,
27-
filterTarget: 'group',
25+
filterFunc: filterGroupsByGroupStatus,
2826
},
2927
] as const;
3028

src/views/workflow-history/helpers/get-history-group-from-events/__tests__/get-signal-external-workflow-execution-group-from-events.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import {
22
initiateSignalExternalWorkflowEvent,
33
signalExternalWorkflowEvent,
44
failSignalExternalWorkflowEvent,
5-
} from '@/views/workflow-history/__fixtures__/workflow-history-singal-external-workflow-events';
5+
} from '@/views/workflow-history/__fixtures__/workflow-history-signal-external-workflow-events';
66

77
import type { SignalExternalWorkflowExecutionHistoryEvent } from '../../../workflow-history.types';
88
import getSignalExternalWorkflowExecutionGroupFromEvents from '../get-signal-external-workflow-execution-group-from-events';
Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { type ActivityHistoryGroup } from '../../../workflow-history.types';
22
import { type WorkflowHistoryFiltersStatusValue } from '../../workflow-history-filters-status.types';
3-
import filterEventsByEventStatus from '../filter-events-by-event-status';
3+
import filterGroupsByGroupStatus from '../filter-groups-by-group-status';
44

55
const ACTIVITY_HISTORY_GROUP_COMPLETED: ActivityHistoryGroup = {
66
label: 'Mock activity',
@@ -14,14 +14,14 @@ const ACTIVITY_HISTORY_GROUP_COMPLETED: ActivityHistoryGroup = {
1414
events: [],
1515
};
1616

17-
describe(filterEventsByEventStatus.name, () => {
17+
describe(filterGroupsByGroupStatus.name, () => {
1818
it('should return true if historyEventStatuses is undefined', () => {
1919
const value: WorkflowHistoryFiltersStatusValue = {
2020
historyEventStatuses: undefined,
2121
};
2222

2323
expect(
24-
filterEventsByEventStatus(ACTIVITY_HISTORY_GROUP_COMPLETED, value)
24+
filterGroupsByGroupStatus(ACTIVITY_HISTORY_GROUP_COMPLETED, value)
2525
).toBe(true);
2626
});
2727

@@ -31,7 +31,7 @@ describe(filterEventsByEventStatus.name, () => {
3131
};
3232

3333
expect(
34-
filterEventsByEventStatus(ACTIVITY_HISTORY_GROUP_COMPLETED, value)
34+
filterGroupsByGroupStatus(ACTIVITY_HISTORY_GROUP_COMPLETED, value)
3535
).toBe(true);
3636
});
3737

@@ -41,7 +41,7 @@ describe(filterEventsByEventStatus.name, () => {
4141
};
4242

4343
expect(
44-
filterEventsByEventStatus(
44+
filterGroupsByGroupStatus(
4545
{
4646
...ACTIVITY_HISTORY_GROUP_COMPLETED,
4747
status: 'FAILED',
@@ -57,7 +57,7 @@ describe(filterEventsByEventStatus.name, () => {
5757
};
5858

5959
expect(
60-
filterEventsByEventStatus(
60+
filterGroupsByGroupStatus(
6161
{
6262
...ACTIVITY_HISTORY_GROUP_COMPLETED,
6363
status: 'ONGOING',
@@ -66,7 +66,7 @@ describe(filterEventsByEventStatus.name, () => {
6666
)
6767
).toBe(true);
6868
expect(
69-
filterEventsByEventStatus(
69+
filterGroupsByGroupStatus(
7070
{
7171
...ACTIVITY_HISTORY_GROUP_COMPLETED,
7272
status: 'WAITING',
@@ -82,7 +82,7 @@ describe(filterEventsByEventStatus.name, () => {
8282
};
8383

8484
expect(
85-
filterEventsByEventStatus(ACTIVITY_HISTORY_GROUP_COMPLETED, value)
85+
filterGroupsByGroupStatus(ACTIVITY_HISTORY_GROUP_COMPLETED, value)
8686
).toBe(false);
8787
});
8888
});

src/views/workflow-history/workflow-history-filters-status/helpers/filter-events-by-event-status.ts renamed to src/views/workflow-history/workflow-history-filters-status/helpers/filter-groups-by-group-status.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
55
type WorkflowHistoryFiltersStatusValue,
66
} from '../workflow-history-filters-status.types';
77

8-
export default function filterEventsByEventStatus(
8+
export default function filterGroupsByGroupStatus(
99
group: HistoryEventsGroup,
1010
value: WorkflowHistoryFiltersStatusValue
1111
) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
import {
2+
mockActivityEventGroup,
3+
mockDecisionEventGroup,
4+
mockTimerEventGroup,
5+
mockChildWorkflowEventGroup,
6+
mockSignalExternalWorkflowEventGroup,
7+
mockRequestCancelExternalWorkflowEventGroup,
8+
mockSingleEventGroup,
9+
} from '../../../__fixtures__/workflow-history-event-groups';
10+
import { type WorkflowHistoryFiltersTypeValue } from '../../workflow-history-filters-type.types';
11+
import filterGroupsByGroupType from '../filter-groups-by-group-type';
12+
13+
describe(filterGroupsByGroupType.name, () => {
14+
it('should return true if historyEventTypes is undefined', () => {
15+
const value: WorkflowHistoryFiltersTypeValue = {
16+
historyEventTypes: undefined,
17+
};
18+
19+
expect(filterGroupsByGroupType(mockActivityEventGroup, value)).toBe(true);
20+
expect(filterGroupsByGroupType(mockDecisionEventGroup, value)).toBe(true);
21+
expect(filterGroupsByGroupType(mockTimerEventGroup, value)).toBe(true);
22+
});
23+
24+
it('should return true if Activity group type is included in historyEventTypes', () => {
25+
const value: WorkflowHistoryFiltersTypeValue = {
26+
historyEventTypes: ['ACTIVITY'],
27+
};
28+
29+
expect(filterGroupsByGroupType(mockActivityEventGroup, value)).toBe(true);
30+
});
31+
32+
it('should return false if Activity group type is not included in historyEventTypes', () => {
33+
const value: WorkflowHistoryFiltersTypeValue = {
34+
historyEventTypes: ['DECISION', 'TIMER'],
35+
};
36+
37+
expect(filterGroupsByGroupType(mockActivityEventGroup, value)).toBe(false);
38+
});
39+
40+
it('should return true if Decision group type is included in historyEventTypes', () => {
41+
const value: WorkflowHistoryFiltersTypeValue = {
42+
historyEventTypes: ['DECISION'],
43+
};
44+
45+
expect(filterGroupsByGroupType(mockDecisionEventGroup, value)).toBe(true);
46+
});
47+
48+
it('should return false if Decision group type is not included in historyEventTypes', () => {
49+
const value: WorkflowHistoryFiltersTypeValue = {
50+
historyEventTypes: ['ACTIVITY', 'TIMER'],
51+
};
52+
53+
expect(filterGroupsByGroupType(mockDecisionEventGroup, value)).toBe(false);
54+
});
55+
56+
it('should return true if Timer group type is included in historyEventTypes', () => {
57+
const value: WorkflowHistoryFiltersTypeValue = {
58+
historyEventTypes: ['TIMER'],
59+
};
60+
61+
expect(filterGroupsByGroupType(mockTimerEventGroup, value)).toBe(true);
62+
});
63+
64+
it('should return true if ChildWorkflowExecution group type is included in historyEventTypes', () => {
65+
const value: WorkflowHistoryFiltersTypeValue = {
66+
historyEventTypes: ['CHILDWORKFLOW'],
67+
};
68+
69+
expect(filterGroupsByGroupType(mockChildWorkflowEventGroup, value)).toBe(
70+
true
71+
);
72+
});
73+
74+
it('should return true if SignalExternalWorkflowExecution group type is included in historyEventTypes', () => {
75+
const value: WorkflowHistoryFiltersTypeValue = {
76+
historyEventTypes: ['SIGNAL'],
77+
};
78+
79+
expect(
80+
filterGroupsByGroupType(mockSignalExternalWorkflowEventGroup, value)
81+
).toBe(true);
82+
});
83+
84+
it('should return true if RequestCancelExternalWorkflowExecution group type maps to WORKFLOW and is included in historyEventTypes', () => {
85+
const value: WorkflowHistoryFiltersTypeValue = {
86+
historyEventTypes: ['WORKFLOW'],
87+
};
88+
89+
expect(
90+
filterGroupsByGroupType(
91+
mockRequestCancelExternalWorkflowEventGroup,
92+
value
93+
)
94+
).toBe(true);
95+
});
96+
97+
it('should return true if Event group type maps to WORKFLOW and is included in historyEventTypes', () => {
98+
const value: WorkflowHistoryFiltersTypeValue = {
99+
historyEventTypes: ['WORKFLOW'],
100+
};
101+
102+
expect(filterGroupsByGroupType(mockSingleEventGroup, value)).toBe(true);
103+
});
104+
105+
it('should return true when multiple types are included and group type matches one of them', () => {
106+
const value: WorkflowHistoryFiltersTypeValue = {
107+
historyEventTypes: ['ACTIVITY', 'DECISION', 'TIMER'],
108+
};
109+
110+
expect(filterGroupsByGroupType(mockActivityEventGroup, value)).toBe(true);
111+
expect(filterGroupsByGroupType(mockDecisionEventGroup, value)).toBe(true);
112+
expect(filterGroupsByGroupType(mockTimerEventGroup, value)).toBe(true);
113+
});
114+
115+
it('should return false when multiple types are included but group type does not match any of them', () => {
116+
const value: WorkflowHistoryFiltersTypeValue = {
117+
historyEventTypes: ['ACTIVITY', 'DECISION'],
118+
};
119+
120+
expect(filterGroupsByGroupType(mockTimerEventGroup, value)).toBe(false);
121+
expect(filterGroupsByGroupType(mockChildWorkflowEventGroup, value)).toBe(
122+
false
123+
);
124+
expect(
125+
filterGroupsByGroupType(mockSignalExternalWorkflowEventGroup, value)
126+
).toBe(false);
127+
});
128+
129+
it('should handle empty historyEventTypes array', () => {
130+
const value: WorkflowHistoryFiltersTypeValue = {
131+
historyEventTypes: [],
132+
};
133+
134+
expect(filterGroupsByGroupType(mockActivityEventGroup, value)).toBe(false);
135+
expect(filterGroupsByGroupType(mockDecisionEventGroup, value)).toBe(false);
136+
expect(filterGroupsByGroupType(mockTimerEventGroup, value)).toBe(false);
137+
});
138+
139+
it('should correctly map both RequestCancelExternalWorkflowExecution and Event to WORKFLOW', () => {
140+
const value: WorkflowHistoryFiltersTypeValue = {
141+
historyEventTypes: ['WORKFLOW'],
142+
};
143+
144+
expect(
145+
filterGroupsByGroupType(
146+
mockRequestCancelExternalWorkflowEventGroup,
147+
value
148+
)
149+
).toBe(true);
150+
expect(filterGroupsByGroupType(mockSingleEventGroup, value)).toBe(true);
151+
152+
// Should return false for other types when only WORKFLOW is included
153+
expect(filterGroupsByGroupType(mockActivityEventGroup, value)).toBe(false);
154+
expect(filterGroupsByGroupType(mockDecisionEventGroup, value)).toBe(false);
155+
});
156+
});

src/views/workflow-history/workflow-history-filters-type/helpers/filter-events-by-event-type.ts

Lines changed: 0 additions & 19 deletions
This file was deleted.

0 commit comments

Comments
 (0)