Skip to content

Commit cbbbcd5

Browse files
authored
Support resetting to binary checksum (#887)
Allow resetting to binary checksum
1 parent ce563b8 commit cbbbcd5

File tree

12 files changed

+312
-30
lines changed

12 files changed

+312
-30
lines changed

src/views/workflow-actions/__tests__/workflow-actions.test.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ import React from 'react';
22

33
import { HttpResponse } from 'msw';
44

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

7-
import { describeWorkflowResponse } from '@/views/workflow-page/__fixtures__/describe-workflow-response';
7+
import { mockDescribeWorkflowResponse } from '@/views/workflow-page/__fixtures__/describe-workflow-response';
88

99
import { mockWorkflowDetailsParams } from '../../workflow-page/__fixtures__/workflow-details-params';
1010
import { mockWorkflowActionsConfig } from '../__fixtures__/workflow-actions-config';
@@ -126,7 +126,7 @@ function setup({
126126
{ status: 500 }
127127
);
128128
} else {
129-
return HttpResponse.json(describeWorkflowResponse, {
129+
return HttpResponse.json(mockDescribeWorkflowResponse, {
130130
status: 200,
131131
});
132132
}

src/views/workflow-actions/config/workflow-actions.config.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,19 @@ export const resetWorkflowActionConfig: WorkflowAction<
109109
withForm: true,
110110
form: WorkflowActionResetForm,
111111
formSchema: resetWorkflowFormSchema,
112-
transformFormDataToSubmission: (v) => v,
112+
transformFormDataToSubmission: (
113+
formData: ResetWorkflowFormData
114+
): ResetWorkflowSubmissionData => {
115+
const decisionFinishEventId =
116+
formData.resetType === 'BinaryChecksum'
117+
? formData.binaryChecksumFirstDecisionCompletedId
118+
: formData.decisionFinishEventId;
119+
return {
120+
reason: formData.reason,
121+
decisionFinishEventId,
122+
skipSignalReapply: formData.skipSignalReapply,
123+
};
124+
},
113125
},
114126
icon: MdRefresh,
115127
getRunnableStatus: () => 'RUNNABLE',

src/views/workflow-actions/workflow-action-reset-form/__tests__/workflow-action-reset-form.test.tsx

Lines changed: 143 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,44 @@
11
import React from 'react';
22

3+
import { HttpResponse } from 'msw';
34
import { type FieldErrors, useForm } from 'react-hook-form';
45

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

8+
import { type ResetPoints } from '@/__generated__/proto-ts/uber/cadence/api/v1/ResetPoints';
9+
import { mockDescribeWorkflowResponse } from '@/views/workflow-page/__fixtures__/describe-workflow-response';
10+
711
import WorkflowActionResetForm from '../workflow-action-reset-form';
812
import { type ResetWorkflowFormData } from '../workflow-action-reset-form.types';
913

14+
const resetPoints = {
15+
points: [
16+
{
17+
binaryChecksum: 'test-binary-checksum',
18+
runId: 'test-run-id',
19+
firstDecisionCompletedId: '4',
20+
resettable: false,
21+
createdTime: null,
22+
expiringTime: null,
23+
},
24+
{
25+
binaryChecksum: 'test-binary-checksum-2',
26+
runId: 'test-run-id',
27+
firstDecisionCompletedId: '7',
28+
resettable: true,
29+
createdTime: null,
30+
expiringTime: null,
31+
},
32+
{
33+
binaryChecksum: 'test-binary-checksum-3',
34+
runId: 'test-run-id',
35+
firstDecisionCompletedId: '10',
36+
resettable: true,
37+
createdTime: null,
38+
expiringTime: null,
39+
},
40+
],
41+
};
1042
describe('WorkflowActionResetForm', () => {
1143
it('renders all form fields correctly', async () => {
1244
await setup({});
@@ -68,6 +100,84 @@ describe('WorkflowActionResetForm', () => {
68100
});
69101
expect(skipSignalCheckbox).not.toBeChecked();
70102
});
103+
104+
it('switches between Event ID and Binary Checksum reset types', async () => {
105+
const { user } = await setup({});
106+
107+
const binaryChecksumRadio = screen.getByRole('radio', {
108+
name: 'Binary Checksum',
109+
});
110+
await user.click(binaryChecksumRadio);
111+
112+
expect(
113+
screen.queryByPlaceholderText('Find Event ID')
114+
).not.toBeInTheDocument();
115+
expect(screen.getByText('Select binary checksum')).toBeInTheDocument();
116+
117+
const eventIdRadio = screen.getByRole('radio', {
118+
name: 'Event ID',
119+
});
120+
await user.click(eventIdRadio);
121+
122+
expect(screen.getByPlaceholderText('Find Event ID')).toBeInTheDocument();
123+
expect(
124+
screen.queryByText('Select binary checksum')
125+
).not.toBeInTheDocument();
126+
});
127+
128+
it('lists only resettable points', async () => {
129+
const { user } = await setup({ resetPoints });
130+
131+
const binaryChecksumRadio = screen.getByRole('radio', {
132+
name: 'Binary Checksum',
133+
});
134+
await user.click(binaryChecksumRadio);
135+
136+
const binaryChecksumSelect = screen.getByRole('combobox', {
137+
name: 'Select binary checksum',
138+
});
139+
await user.click(binaryChecksumSelect);
140+
141+
expect((await screen.findAllByRole('option')).length).toBe(2);
142+
expect(screen.getByText('test-binary-checksum-2')).toBeInTheDocument();
143+
expect(screen.getByText('test-binary-checksum-3')).toBeInTheDocument();
144+
});
145+
146+
it('handles error state for bad binary fetch', async () => {
147+
const { user } = await setup({ isError: true });
148+
149+
const binaryChecksumRadio = screen.getByRole('radio', {
150+
name: 'Binary Checksum',
151+
});
152+
await user.click(binaryChecksumRadio);
153+
154+
const binaryChecksumSelect = screen.getByRole('combobox', {
155+
name: 'Select binary checksum',
156+
});
157+
158+
await user.click(binaryChecksumSelect);
159+
160+
expect(
161+
await screen.findByText('Failed to load binary checksums')
162+
).toBeInTheDocument();
163+
});
164+
165+
it('handles empty state', async () => {
166+
const { user } = await setup({ resetPoints: { points: [] } });
167+
168+
const binaryChecksumRadio = screen.getByRole('radio', {
169+
name: 'Binary Checksum',
170+
});
171+
await user.click(binaryChecksumRadio);
172+
173+
const binaryChecksumSelect = screen.getByRole('combobox', {
174+
name: 'Select binary checksum',
175+
});
176+
177+
await user.click(binaryChecksumSelect);
178+
179+
expect(await screen.findByText('No results found')).toBeInTheDocument();
180+
});
71181
});
72182

73183
type TestProps = {
@@ -83,6 +193,7 @@ function TestWrapper({ formErrors, formData }: TestProps) {
83193
return (
84194
<WorkflowActionResetForm
85195
control={methods.control}
196+
clearErrors={methods.clearErrors}
86197
fieldErrors={formErrors}
87198
formData={formData}
88199
cluster="test-cluster"
@@ -99,11 +210,41 @@ async function setup({
99210
decisionFinishEventId: '',
100211
reason: '',
101212
skipSignalReapply: false,
213+
resetType: 'EventId',
102214
},
103-
}: Partial<TestProps>) {
215+
resetPoints = mockDescribeWorkflowResponse.workflowExecutionInfo
216+
.autoResetPoints,
217+
isError = false,
218+
}: Partial<TestProps> & { resetPoints?: ResetPoints; isError?: boolean }) {
104219
const user = userEvent.setup();
105220

106-
render(<TestWrapper formErrors={formErrors} formData={formData} />);
221+
const describeWorkflowResponse = {
222+
...mockDescribeWorkflowResponse,
223+
workflowExecutionInfo: {
224+
...mockDescribeWorkflowResponse.workflowExecutionInfo,
225+
autoResetPoints: resetPoints,
226+
},
227+
};
228+
render(<TestWrapper formErrors={formErrors} formData={formData} />, {
229+
endpointsMocks: [
230+
{
231+
path: '/api/domains/:domain/:cluster/workflows/:workflowId/:runId',
232+
httpMethod: 'GET',
233+
httpResolver: () => {
234+
if (isError) {
235+
return HttpResponse.json(
236+
{ message: 'Failed to fetch workflow summary' },
237+
{ status: 500 }
238+
);
239+
} else {
240+
return HttpResponse.json(describeWorkflowResponse, {
241+
status: 200,
242+
});
243+
}
244+
},
245+
},
246+
],
247+
});
107248

108249
return { user };
109250
}
Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,21 @@
11
import { z } from 'zod';
22

3-
export const resetWorkflowFormSchema = z.object({
4-
decisionFinishEventId: z.string().min(1),
3+
const baseSchema = z.object({
54
reason: z.string().min(1),
65
skipSignalReapply: z.boolean().optional(),
76
});
7+
8+
const eventIdSchema = baseSchema.extend({
9+
resetType: z.literal('EventId'),
10+
decisionFinishEventId: z.string().min(1),
11+
});
12+
13+
const binaryChecksumSchema = baseSchema.extend({
14+
resetType: z.literal('BinaryChecksum'),
15+
binaryChecksumFirstDecisionCompletedId: z.string().min(1),
16+
});
17+
18+
export const resetWorkflowFormSchema = z.discriminatedUnion('resetType', [
19+
eventIdSchema,
20+
binaryChecksumSchema,
21+
]);

0 commit comments

Comments
 (0)