Skip to content

Commit e781668

Browse files
authored
Add reset button to history timeline (#882)
* Timeline reset workflow button * add reset to history timeline * fix mobile styles
1 parent eb183f4 commit e781668

File tree

13 files changed

+184
-34
lines changed

13 files changed

+184
-34
lines changed

src/views/workflow-actions/workflow-actions-modal-content/workflow-actions-modal-content.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,12 @@ export default function WorkflowActionsModalContent<
2424
FormData extends FieldValues,
2525
SubmissionData,
2626
Result,
27-
>({ action, params, onCloseModal }: Props<FormData, SubmissionData, Result>) {
27+
>({
28+
action,
29+
params,
30+
onCloseModal,
31+
initialFormValues,
32+
}: Props<FormData, SubmissionData, Result>) {
2833
const queryClient = useQueryClient();
2934
const { enqueue, dequeue } = useSnackbar();
3035

@@ -37,7 +42,7 @@ export default function WorkflowActionsModalContent<
3742
resolver: action.modal.formSchema
3843
? zodResolver(action.modal.formSchema)
3944
: undefined,
40-
defaultValues: {} as DefaultValues<FormData>,
45+
defaultValues: initialFormValues,
4146
});
4247

4348
const { mutate, isPending, error } = useMutation<

src/views/workflow-actions/workflow-actions-modal-content/workflow-actions-modal-content.types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { type DefaultValues } from 'react-hook-form';
2+
13
import {
24
type WorkflowAction,
35
type WorkflowActionInputParams,
@@ -7,4 +9,5 @@ export type Props<FormData, SubmissionData, Result> = {
79
action: WorkflowAction<FormData, SubmissionData, Result>;
810
params: WorkflowActionInputParams;
911
onCloseModal: () => void;
12+
initialFormValues?: DefaultValues<FormData>;
1013
};

src/views/workflow-actions/workflow-actions-modal/workflow-actions-modal.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export default function WorkflowActionsModal<
1313
>({
1414
action,
1515
onClose,
16+
initialFormValues,
1617
...workflowDetailsParams
1718
}: Props<FormData, SubmissionData, Result>) {
1819
return (
@@ -28,6 +29,7 @@ export default function WorkflowActionsModal<
2829
params={{
2930
...workflowDetailsParams,
3031
}}
32+
initialFormValues={initialFormValues}
3133
onCloseModal={onClose}
3234
/>
3335
)}

src/views/workflow-actions/workflow-actions-modal/workflow-actions-modal.types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { type DefaultValues } from 'react-hook-form';
2+
13
import { type WorkflowAction } from '../workflow-actions.types';
24

35
export type Props<FormData, SubmissionData, Result> = {
@@ -7,4 +9,5 @@ export type Props<FormData, SubmissionData, Result> = {
79
runId: string;
810
action: WorkflowAction<FormData, SubmissionData, Result> | undefined;
911
onClose: () => void;
12+
initialFormValues?: DefaultValues<FormData>;
1013
};

src/views/workflow-history/helpers/get-history-group-from-events/__tests__/get-decision-group-from-events.test.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,4 +259,34 @@ describe('getDecisionGroupFromEvents', () => {
259259
const group = getDecisionGroupFromEvents(events);
260260
expect(group.badges).toEqual([{ content: '1 Retry' }]);
261261
});
262+
263+
it('should set resetToDecisionEventId to null when no close or timeout events are present', () => {
264+
const events: ExtendedDecisionHistoryEvent[] = [
265+
scheduleDecisionTaskEvent,
266+
startDecisionTaskEvent,
267+
];
268+
const group = getDecisionGroupFromEvents(events);
269+
expect(group.resetToDecisionEventId).toBeNull();
270+
});
271+
272+
it('should set resetToDecisionEventId to the close event ID when a close event is present', () => {
273+
const events: ExtendedDecisionHistoryEvent[] = [
274+
scheduleDecisionTaskEvent,
275+
startDecisionTaskEvent,
276+
completeDecisionTaskEvent,
277+
];
278+
const group = getDecisionGroupFromEvents(events);
279+
expect(group.resetToDecisionEventId).toBe(
280+
completeDecisionTaskEvent.eventId
281+
);
282+
});
283+
284+
it('should be set to the timeout event ID when only a timeout event is present', () => {
285+
const events: ExtendedDecisionHistoryEvent[] = [
286+
scheduleDecisionTaskEvent,
287+
timeoutDecisionTaskEvent,
288+
];
289+
const group = getDecisionGroupFromEvents(events);
290+
expect(group.resetToDecisionEventId).toBe(timeoutDecisionTaskEvent.eventId);
291+
});
262292
});

src/views/workflow-history/helpers/get-history-group-from-events/get-decision-group-from-events.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,9 @@ export default function getDecisionGroupFromEvents(
4141
const hasAllCloseEvents = scheduleEvent && startEvent && closeEvent;
4242
const hasMissingEvents = !hasAllTimeoutEvents && !hasAllCloseEvents;
4343

44+
// Retry attempts calculation
4445
let retryAttemptNumber = 0;
46+
4547
if (
4648
pendingStartEvent &&
4749
pendingStartAttr in pendingStartEvent &&
@@ -64,6 +66,17 @@ export default function getDecisionGroupFromEvents(
6466
retryAttemptNumber === 1 ? '1 Retry' : `${retryAttemptNumber} Retries`,
6567
});
6668
}
69+
70+
// set resetToDecisionEventId to close/timeout decision event id
71+
let resetToDecisionEventId: string | null = null;
72+
73+
if (closeEvent && closeEvent.eventId) {
74+
resetToDecisionEventId = closeEvent.eventId;
75+
} else if (timeoutEvent && timeoutEvent.eventId) {
76+
resetToDecisionEventId = timeoutEvent.eventId;
77+
}
78+
79+
// populate event to label and status maps
6780
const eventToLabel: HistoryGroupEventToStringMap<DecisionHistoryGroup> = {
6881
decisionTaskScheduledEventAttributes: 'Scheduled',
6982
pendingDecisionTaskStartEventAttributes: 'Starting',
@@ -93,6 +106,7 @@ export default function getDecisionGroupFromEvents(
93106
hasMissingEvents,
94107
groupType,
95108
badges,
109+
resetToDecisionEventId,
96110
...getCommonHistoryGroupFields<DecisionHistoryGroup>(
97111
events,
98112
eventToStatus,

src/views/workflow-history/workflow-history-timeline-group/__tests__/workflow-history-timeline-group.test.tsx

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
import { render, screen } from '@/test-utils/rtl';
1+
import { render, screen, userEvent } from '@/test-utils/rtl';
22

33
import { startWorkflowExecutionEvent } from '../../__fixtures__/workflow-history-single-events';
44
import type WorkflowHistoryEventStatusBadge from '../../workflow-history-event-status-badge/workflow-history-event-status-badge';
55
import type WorkflowHistoryEventsCard from '../../workflow-history-events-card/workflow-history-events-card';
6+
import type WorkflowHistoryTimelineResetButton from '../../workflow-history-timeline-reset-button/workflow-history-timeline-reset-button';
67
import WorkflowHistoryTimelineGroup from '../workflow-history-timeline-group';
78
import { type styled } from '../workflow-history-timeline-group.styles';
89
import type { Props } from '../workflow-history-timeline-group.types';
@@ -17,6 +18,16 @@ jest.mock<typeof WorkflowHistoryEventsCard>(
1718
() => jest.fn(() => <div>Events Card</div>)
1819
);
1920

21+
jest.mock<typeof WorkflowHistoryTimelineResetButton>(
22+
'../../workflow-history-timeline-reset-button/workflow-history-timeline-reset-button',
23+
() =>
24+
jest.fn((props) => (
25+
<button onClick={props.onReset} data-testid="reset-button">
26+
Reset Button
27+
</button>
28+
))
29+
);
30+
2031
jest.mock('../workflow-history-timeline-group.styles', () => {
2132
const actual = jest.requireActual(
2233
'../workflow-history-timeline-group.styles'
@@ -76,6 +87,27 @@ describe('WorkflowHistoryTimelineGroup', () => {
7687
setup({ isLastEvent: true });
7788
expect(screen.getByText('Divider hidden')).toBeInTheDocument();
7889
});
90+
91+
it('renders reset button when resetToDecisionEventId is provided', () => {
92+
setup({ resetToDecisionEventId: 'decision-event-id' });
93+
expect(screen.getByTestId('reset-button')).toBeInTheDocument();
94+
});
95+
96+
it('does not render reset button when resetToDecisionEventId is not provided', () => {
97+
setup({ resetToDecisionEventId: undefined });
98+
expect(screen.queryByTestId('reset-button')).not.toBeInTheDocument();
99+
});
100+
101+
it('calls onReset when reset button is clicked', async () => {
102+
const { mockOnReset, user } = setup({
103+
resetToDecisionEventId: 'decision-event-id',
104+
});
105+
106+
const resetButton = screen.getByTestId('reset-button');
107+
await user.click(resetButton);
108+
109+
expect(mockOnReset).toHaveBeenCalledTimes(1);
110+
});
79111
});
80112

81113
function setup({
@@ -101,8 +133,11 @@ function setup({
101133
workflowTab: 'history',
102134
},
103135
badges,
136+
resetToDecisionEventId,
104137
}: Partial<Props>) {
105-
return render(
138+
const mockOnReset = jest.fn();
139+
const user = userEvent.setup();
140+
render(
106141
<WorkflowHistoryTimelineGroup
107142
events={events}
108143
eventsMetadata={eventsMetadata}
@@ -115,6 +150,9 @@ function setup({
115150
getIsEventExpanded={jest.fn()}
116151
onEventToggle={jest.fn()}
117152
badges={badges}
153+
resetToDecisionEventId={resetToDecisionEventId}
154+
onReset={mockOnReset}
118155
/>
119156
);
157+
return { mockOnReset, user };
120158
}

src/views/workflow-history/workflow-history-timeline-group/workflow-history-timeline-group.styles.ts

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,7 @@ export const overrides = {
1515
color: $theme.colors.contentSecondary,
1616
...$theme.typography.LabelXSmall,
1717
whiteSpace: 'nowrap',
18-
marginTop: $theme.sizing.scale100,
19-
marginBottom: $theme.sizing.scale100,
18+
2019
[$theme.mediaQuery.medium]: {
2120
marginTop: 0,
2221
marginBottom: 0,
@@ -45,13 +44,13 @@ const cssStylesObj = {
4544
display: 'flex',
4645
flexDirection: 'column',
4746
},
48-
timelineEventHeader: (theme) => ({
47+
eventHeader: (theme) => ({
4948
display: 'flex',
5049
alignItems: 'center',
5150
gap: theme.sizing.scale600,
5251
padding: `${theme.sizing.scale500} 0`,
5352
}),
54-
timelineEventLabelAndTime: (theme) => ({
53+
eventLabelAndSecondaryDetails: (theme) => ({
5554
display: 'flex',
5655
flexDirection: 'column',
5756
gap: 0,
@@ -62,19 +61,27 @@ const cssStylesObj = {
6261
alignItems: 'center',
6362
gap: theme.sizing.scale200,
6463
},
64+
flex: 1,
65+
}),
66+
eventSecondaryDetails: (theme) => ({
67+
display: 'flex',
68+
gap: theme.sizing.scale200,
69+
alignItems: 'center',
70+
flexWrap: 'wrap',
6571
}),
66-
timelineEventsLabel: (theme) => ({
67-
...theme.typography.LabelLarge,
72+
eventsLabel: (theme) => ({
73+
...theme.typography.LabelMedium,
74+
flex: 1,
6875
whiteSpace: 'nowrap',
6976
textOverflow: 'ellipsis',
7077
overflow: 'hidden',
7178
}),
72-
timelineEventsTime: (theme) => ({
79+
eventsTime: (theme) => ({
7380
...theme.typography.LabelXSmall,
7481
color: theme.colors.contentTertiary,
7582
wordBreak: 'break-word',
7683
}),
77-
timelineEventCardContainer: {
84+
eventCardContainer: {
7885
display: 'flex',
7986
gap: '28px',
8087
},

src/views/workflow-history/workflow-history-timeline-group/workflow-history-timeline-group.tsx

Lines changed: 39 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import useStyletronClasses from '@/hooks/use-styletron-classes';
77

88
import WorkflowHistoryEventStatusBadge from '../workflow-history-event-status-badge/workflow-history-event-status-badge';
99
import WorkflowHistoryEventsCard from '../workflow-history-events-card/workflow-history-events-card';
10+
import WorkflowHistoryTimelineResetButton from '../workflow-history-timeline-reset-button/workflow-history-timeline-reset-button';
1011

1112
import {
1213
cssStyles,
@@ -25,40 +26,60 @@ export default function WorkflowHistoryTimelineGroup({
2526
hasMissingEvents,
2627
decodedPageUrlParams,
2728
badges,
29+
resetToDecisionEventId,
2830
getIsEventExpanded,
2931
onEventToggle,
32+
onReset,
3033
}: Props) {
3134
const { cls } = useStyletronClasses(cssStyles);
3235
const hasBadges = badges !== undefined && badges.length > 0;
36+
37+
const handleReset = () => {
38+
if (onReset) {
39+
onReset();
40+
}
41+
};
42+
3343
return (
3444
<div className={cls.groupContainer}>
35-
<div className={cls.timelineEventHeader}>
45+
<div className={cls.eventHeader}>
3646
<WorkflowHistoryEventStatusBadge
3747
status={status}
3848
statusReady={!hasMissingEvents}
3949
size="medium"
4050
/>
41-
<div className={cls.timelineEventLabelAndTime}>
42-
<div className={cls.timelineEventsLabel}>{label}</div>
43-
{hasBadges && (
44-
<div>
45-
{badges.map((badge) => (
46-
<Badge
47-
key={badge.content}
48-
overrides={overrides.headerBadge}
49-
content={badge.content}
50-
shape="rectangle"
51-
color="primary"
52-
/>
53-
))}
51+
<div className={cls.eventLabelAndSecondaryDetails}>
52+
<div className={cls.eventsLabel}>{label}</div>
53+
<div className={cls.eventSecondaryDetails}>
54+
{hasBadges && (
55+
<>
56+
{badges.map((badge) => (
57+
<Badge
58+
key={badge.content}
59+
overrides={overrides.headerBadge}
60+
content={badge.content}
61+
shape="rectangle"
62+
color="primary"
63+
/>
64+
))}
65+
</>
66+
)}
67+
<div suppressHydrationWarning className={cls.eventsTime}>
68+
{timeLabel}
5469
</div>
55-
)}
56-
<div suppressHydrationWarning className={cls.timelineEventsTime}>
57-
{timeLabel}
70+
{resetToDecisionEventId && (
71+
<WorkflowHistoryTimelineResetButton
72+
workflowId={decodedPageUrlParams.workflowId}
73+
runId={decodedPageUrlParams.runId}
74+
domain={decodedPageUrlParams.domain}
75+
cluster={decodedPageUrlParams.cluster}
76+
onReset={handleReset}
77+
/>
78+
)}
5879
</div>
5980
</div>
6081
</div>
61-
<div className={cls.timelineEventCardContainer}>
82+
<div className={cls.eventCardContainer}>
6283
<styled.VerticalDivider $hidden={isLastEvent} />
6384
<WorkflowHistoryEventsCard
6485
events={events}

src/views/workflow-history/workflow-history-timeline-group/workflow-history-timeline-group.types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,11 @@ export type Props = Pick<
1616
| 'hasMissingEvents'
1717
| 'status'
1818
| 'badges'
19+
| 'resetToDecisionEventId'
1920
> & {
2021
isLastEvent: boolean;
2122
decodedPageUrlParams: WorkflowHistoryProps['params'];
2223
getIsEventExpanded: GetIsEventExpanded;
2324
onEventToggle: ToggleIsEventExpanded;
25+
onReset?: () => void;
2426
};

0 commit comments

Comments
 (0)