Skip to content

Commit 0e22cbc

Browse files
authored
Signal workflow form (#903)
1 parent 6460fd4 commit 0e22cbc

File tree

6 files changed

+210
-1
lines changed

6 files changed

+210
-1
lines changed

src/views/workflow-actions/workflow-action-new-run-success-msg/workflow-action-new-run-success-msg.types.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,4 @@ export type Props = WorkflowActionSuccessMessageProps<
55
{ runId: string }
66
> & {
77
successMessage: string;
8-
onDismissMessage: () => void;
98
};
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import React from 'react';
2+
3+
import { type FieldErrors, useForm } from 'react-hook-form';
4+
5+
import { render, screen, fireEvent } from '@/test-utils/rtl';
6+
7+
import WorkflowActionSignalForm from '../workflow-action-signal-form';
8+
import { type SignalWorkflowFormData } from '../workflow-action-signal-form.types';
9+
10+
describe('WorkflowActionSignalForm', () => {
11+
it('renders all form fields correctly', async () => {
12+
await setup({});
13+
14+
expect(
15+
screen.getByPlaceholderText('Enter signal name')
16+
).toBeInTheDocument();
17+
expect(screen.getByPlaceholderText('Enter JSON input')).toBeInTheDocument();
18+
});
19+
20+
it('displays error when form has errors', async () => {
21+
const formErrors = {
22+
signalName: {
23+
message: 'Signal name is required',
24+
type: 'required',
25+
},
26+
signalInput: {
27+
message: 'Invalid JSON format',
28+
type: 'invalid',
29+
},
30+
};
31+
32+
await setup({ formErrors });
33+
34+
const signalNameInput = screen.getByPlaceholderText('Enter signal name');
35+
expect(signalNameInput).toHaveAttribute('aria-invalid', 'true');
36+
37+
const signalInputTextarea = screen.getByPlaceholderText('Enter JSON input');
38+
expect(signalInputTextarea).toHaveAttribute('aria-invalid', 'true');
39+
});
40+
41+
it('handles input changes correctly', async () => {
42+
await setup({});
43+
44+
const signalNameInput = screen.getByPlaceholderText('Enter signal name');
45+
fireEvent.change(signalNameInput, { target: { value: 'test-signal' } });
46+
expect(signalNameInput).toHaveValue('test-signal');
47+
48+
const signalInputTextarea = screen.getByPlaceholderText('Enter JSON input');
49+
fireEvent.change(signalInputTextarea, {
50+
target: { value: '{"key": "value"}' },
51+
});
52+
expect(signalInputTextarea).toHaveValue('{"key": "value"}');
53+
});
54+
55+
it('renders with default values', async () => {
56+
await setup({
57+
formData: {
58+
signalName: 'test-signal',
59+
signalInput: '{"key": "value"}',
60+
},
61+
});
62+
63+
const signalNameInput = screen.getByPlaceholderText('Enter signal name');
64+
expect(signalNameInput).toHaveValue('test-signal');
65+
66+
const signalInputTextarea = screen.getByPlaceholderText('Enter JSON input');
67+
expect(signalInputTextarea).toHaveValue('{"key": "value"}');
68+
});
69+
});
70+
71+
type TestProps = {
72+
formErrors: FieldErrors<SignalWorkflowFormData>;
73+
formData: SignalWorkflowFormData;
74+
};
75+
76+
function TestWrapper({ formErrors, formData }: TestProps) {
77+
const methods = useForm<SignalWorkflowFormData>({
78+
defaultValues: formData,
79+
});
80+
81+
return (
82+
<WorkflowActionSignalForm
83+
control={methods.control}
84+
clearErrors={methods.clearErrors}
85+
fieldErrors={formErrors}
86+
formData={formData}
87+
cluster="test-cluster"
88+
domain="test-domain"
89+
workflowId="test-workflow-id"
90+
runId="test-run-id"
91+
/>
92+
);
93+
}
94+
95+
async function setup({
96+
formErrors = {},
97+
formData = {
98+
signalName: '',
99+
signalInput: '',
100+
},
101+
}: Partial<TestProps>) {
102+
render(<TestWrapper formErrors={formErrors} formData={formData} />);
103+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { z } from 'zod';
2+
3+
import signalWorkflowInputSchema from '@/route-handlers/signal-workflow/schemas/signal-workflow-input-schema';
4+
5+
export const signalWorkflowFormSchema = z.object({
6+
signalName: z.string().min(1),
7+
signalInput: signalWorkflowInputSchema.optional(),
8+
});
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { type Theme } from 'baseui';
2+
import { type TextareaOverrides } from 'baseui/textarea';
3+
import { type StyleObject } from 'styletron-react';
4+
5+
export const overrides = {
6+
jsonInput: {
7+
Input: {
8+
style: ({ $theme }: { $theme: Theme }): StyleObject => ({
9+
...$theme.typography.MonoParagraphSmall,
10+
'::placeholder': {
11+
...$theme.typography.ParagraphSmall,
12+
},
13+
}),
14+
},
15+
} satisfies TextareaOverrides,
16+
};
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { FormControl } from 'baseui/form-control';
2+
import { Input } from 'baseui/input';
3+
import { Textarea } from 'baseui/textarea';
4+
import { Controller } from 'react-hook-form';
5+
6+
import { type WorkflowActionFormProps } from '../workflow-actions.types';
7+
8+
import { overrides } from './workflow-action-signal-form.styles';
9+
import { type SignalWorkflowFormData } from './workflow-action-signal-form.types';
10+
11+
export default function WorkflowActionSignalForm({
12+
fieldErrors,
13+
control,
14+
}: WorkflowActionFormProps<SignalWorkflowFormData>) {
15+
const getErrorMessage = (field: string) => {
16+
return field in fieldErrors
17+
? fieldErrors[field as keyof typeof fieldErrors]?.message
18+
: undefined;
19+
};
20+
21+
return (
22+
<div>
23+
<FormControl label="Signal Name">
24+
<Controller
25+
name="signalName"
26+
control={control}
27+
defaultValue=""
28+
render={({ field: { ref, ...field } }) => (
29+
<Input
30+
{...field}
31+
// @ts-expect-error - inputRef expects ref object while ref is a callback. It should support both.
32+
inputRef={ref}
33+
onChange={(e) => {
34+
field.onChange(e.target.value);
35+
}}
36+
size="compact"
37+
onBlur={field.onBlur}
38+
error={Boolean(getErrorMessage('signalName'))}
39+
placeholder="Enter signal name"
40+
/>
41+
)}
42+
/>
43+
</FormControl>
44+
<FormControl label="JSON Input (optional)">
45+
<Controller
46+
name="signalInput"
47+
control={control}
48+
defaultValue=""
49+
render={({ field: { ref, ...field } }) => (
50+
<Textarea
51+
{...field}
52+
overrides={overrides.jsonInput}
53+
// @ts-expect-error - inputRef expects ref object while ref is a callback. It should support both.
54+
inputRef={ref}
55+
onChange={(e) => {
56+
field.onChange(e.target.value);
57+
}}
58+
size="compact"
59+
onBlur={field.onBlur}
60+
error={Boolean(getErrorMessage('signalInput'))}
61+
placeholder="Enter JSON input"
62+
/>
63+
)}
64+
/>
65+
</FormControl>
66+
</div>
67+
);
68+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { type z } from 'zod';
2+
3+
import type signalWorkflowRequestBodySchema from '@/route-handlers/signal-workflow/schemas/signal-workflow-request-body-schema';
4+
5+
import { type WorkflowActionFormProps } from '../workflow-actions.types';
6+
7+
import { type signalWorkflowFormSchema } from './schemas/signal-workflow-form-schema';
8+
9+
export type SignalWorkflowFormData = z.infer<typeof signalWorkflowFormSchema>;
10+
11+
export type Props = WorkflowActionFormProps<SignalWorkflowFormData>;
12+
13+
export type SignalWorkflowSubmissionData = z.infer<
14+
typeof signalWorkflowRequestBodySchema
15+
>;

0 commit comments

Comments
 (0)