feat: add schema-backed DAG param forms#2039
Conversation
|
Important Review skippedAuto incremental reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughWalkthroughThis pull request introduces support for JSON Schema-driven parameter rendering in DAG execution. It adds a new optional Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (9)
internal/service/frontend/api/v1/transformer_test.go (1)
119-134: LGTM — test covers happy-path propagation cleanly.Optional follow-up: consider also asserting the negative path (e.g.,
core.DAG{}with noParamSchema, or with malformed JSON) yieldsdetails.ParamSchema == nil, to lock intoJSONObject's nil-return contract against future regressions.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/service/frontend/api/v1/transformer_test.go` around lines 119 - 134, Add a negative-path unit test alongside TestToDAGDetailsIncludesParamSchema that calls toDAGDetails with a core.DAG that has no ParamSchema (e.g., core.DAG{}) and another case with an invalid ParamSchema (malformed JSON) and assert that details.ParamSchema == nil for both; locate and update the test in transformer_test.go and reference the existing toDAGDetails/toJSONObject behavior so the new test locks in the nil-return contract against regressions.ui/src/features/dags/components/dag-execution/__tests__/StartDAGModal.test.tsx (1)
50-52: Minor: preferbeforeEachreset orvi.clearAllMocks().
afterEachonly clearsrenderedFormProps. If morevi.fn()instances accumulate state across tests later, they'd be missed. ConsiderbeforeEach(() => vi.clearAllMocks())for symmetry.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@ui/src/features/dags/components/dag-execution/__tests__/StartDAGModal.test.tsx` around lines 50 - 52, The test teardown currently only calls renderedFormProps.mockClear() in afterEach, which can miss other vi.fn() state; replace or complement this with a beforeEach(() => vi.clearAllMocks()) to ensure all mocks (including renderedFormProps and any future vi.fn() instances) are reset before each test, or alternatively call vi.clearAllMocks() in afterEach for symmetry; update the test file to add the beforeEach/afterEach call referencing renderedFormProps and vi.clearAllMocks accordingly.internal/core/spec/dag_param_schema.go (2)
45-47: Improve readability of the unsupported-construct rejection.Long boolean conjunctions like this (and the analogous one at lines 75-77) are hard to scan and easy to miss when adding new JSON Schema keywords. Consider extracting a small helper.
♻️ Suggested refactor
func hasUnsupportedRootConstruct(s *jsonschema.Schema) bool { return len(s.PatternProperties) > 0 || len(s.AllOf) > 0 || len(s.AnyOf) > 0 || len(s.OneOf) > 0 || s.Not != nil || s.If != nil || s.Then != nil || s.Else != nil || s.Items != nil || len(s.PrefixItems) > 0 || s.Contains != nil }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/core/spec/dag_param_schema.go` around lines 45 - 47, Extract the long conjunctive check into a small helper like hasUnsupportedRootConstruct that accepts *jsonschema.Schema (referencing the variable root used in the current checks) and returns a bool; replace the current long boolean expression in the early-return (the block using root.PatternProperties, root.AllOf, root.AnyOf, root.OneOf, root.Not, root.If, root.Then, root.Else, root.Items, root.PrefixItems, root.Contains) with a call to that helper, and do the same for the analogous check later (the similar expression at the other return site) so both places use the new function.
99-99: Type assertion is safe, but prefer explicit element cloning for clarity.
cloneAny(schema.Enum)safely returns[]anysinceEnumis a slice andcloneAnyexplicitly handles the[]anycase. However, the type assertion can be avoided by directly cloning elements, which is clearer and follows the same defensive approach used elsewhere:Suggested change
if len(schema.Enum) > 0 { - result.Enum = cloneAny(schema.Enum).([]any) + enum := make([]any, len(schema.Enum)) + for i, v := range schema.Enum { + enum[i] = cloneAny(v) + } + result.Enum = enum }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/core/spec/dag_param_schema.go` at line 99, The assignment result.Enum = cloneAny(schema.Enum).([]any) relies on a type assertion; instead, iterate over schema.Enum, clone each element via cloneAny and append to a new []any slice, then assign that slice to result.Enum so element-level cloning is explicit and matches the defensive pattern used elsewhere (update code handling of schema.Enum in the logic around cloneAny, result.Enum, and schema.Enum).ui/src/features/dags/components/dag-execution/paramSchemaForm.ts (1)
60-82: Edge case: empty string coerces to0for integer/number schemas.
Number('')is0andNumber.isInteger(0)istrue, socoerceParamSchemaValue('', integerSchema)returns0rather than the original empty string. Whether this is a problem depends on whetherparseParamscan produce empty-string values; if so, the user could see unexpected zeros pre-filled. Consider returningvaluewhenvalue.trim() === ''.♻️ Suggested change
function coerceParamSchemaValue(value: string, schema: JSONSchema): unknown { + if (value === '') { + return value; + } switch (inferScalarType(schema)) {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@ui/src/features/dags/components/dag-execution/paramSchemaForm.ts` around lines 60 - 82, The function coerceParamSchemaValue incorrectly coerces an empty string to 0 for 'integer'/'number' schemas; update coerceParamSchemaValue to first check if the incoming value is an empty string (e.g., value.trim() === '') and return the original value unchanged before calling inferScalarType / attempting Number(value) conversion; keep this check at the top of coerceParamSchemaValue so integer/number/boolean branches don't convert blank input and retain existing behavior for non-empty strings.ui/src/features/dags/components/dag-execution/StartDAGModal.tsx (1)
256-262: Defensive guard reads well; minor: also reject emptyproperties.
paramSchemawill be set wheneverdagDetails.paramSchemais a plain object, including{}or one withoutproperties. The backend sanitizer rejects schemas without properties so this is unlikely in practice, but a UI-side guard would prevent rendering a useless empty form if a malformed schema ever leaks through.♻️ Suggested change
- const schema = dagDetails?.paramSchema; - if (!schema || Array.isArray(schema) || typeof schema !== 'object') { - return undefined; - } - return schema as JSONSchema; + const schema = dagDetails?.paramSchema as JSONSchema | undefined; + if (!schema || Array.isArray(schema) || typeof schema !== 'object') { + return undefined; + } + if (!schema.properties || Object.keys(schema.properties).length === 0) { + return undefined; + } + return schema;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@ui/src/features/dags/components/dag-execution/StartDAGModal.tsx` around lines 256 - 262, The memoized paramSchema currently accepts any plain object from dagDetails.paramSchema; update the guard in the React.useMemo so it also rejects schemas that lack a non-empty properties object (e.g., ensure schema.properties is an object and Object.keys(schema.properties).length > 0) before returning schema as JSONSchema; this prevents returning {} or schemas without properties and avoids rendering an empty form.ui/src/features/dags/components/dag-execution/schemaFormWidgets.tsx (1)
142-163: Consider whethercurrentValueshould fall back to the encoded rawvalue.When
valuedoesn't match anyenumOptionsentry (e.g., a stale or out-of-range default),enumOptionsIndexForValuereturns nothing andcurrentValuebecomesundefined, leaving the radio group visually unselected even thoughformDataholds a value. RJSF's stock RadioWidget handles this similarly, so this is consistent — but worth a brief inline comment to flag the intent so future readers don't "fix" it.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@ui/src/features/dags/components/dag-execution/schemaFormWidgets.tsx` around lines 142 - 163, The currentValue computation returns undefined when value doesn't match any enumOptions (via enumOptionsIndexForValue) which leaves the radio group unselected; add a brief inline comment inside the React.useMemo (near currentValue, enumOptionsIndexForValue, enumOptionValueEncoder, enumOptions, and value references) stating this is intentional and that we intentionally do not fall back to encoding the raw value so stale/out-of-range defaults remain visually unselected, to prevent future maintainers from "fixing" this behavior.internal/service/frontend/api/v1/transformer.go (1)
443-453: Consider logging on unmarshal failure.
toJSONObjectcollapses both "empty" and "malformed JSON" into the samenilresult. Sincedag.ParamSchemais produced internally bybuildRenderableParamSchema(which alreadyjson.Marshals a sanitized schema), an unmarshal error here indicates an internal inconsistency that would otherwise be invisible to operators. Consider at least logging the error.♻️ Suggested change
func toJSONObject(raw json.RawMessage) *map[string]any { if len(raw) == 0 { return nil } var value map[string]any if err := json.Unmarshal(raw, &value); err != nil { + // Should not happen: paramSchema is produced via json.Marshal upstream. + logger.Warn("toJSONObject: failed to unmarshal param schema", "error", err) return nil } return &value }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/service/frontend/api/v1/transformer.go` around lines 443 - 453, toJSONObject currently hides JSON unmarshal failures by returning nil for both empty and malformed input; update to log the unmarshal error (including the error value and a brief context such as "failed to unmarshal dag.ParamSchema produced by buildRenderableParamSchema") before returning nil. Locate the toJSONObject function and add a logging statement that includes the raw input length or a snippet and the error (so operators can see internal inconsistencies between buildRenderableParamSchema and dag.ParamSchema) while preserving the existing nil return behavior for len(raw)==0 and on error.ui/src/features/dags/components/dag-execution/schemaFormTemplates.tsx (1)
1-1: Verify use of@rjsf/shadcn/lib/...deep import.Importing from
@rjsf/shadcn/lib/BaseInputTemplate/BaseInputTemplate.jsreaches into the package's internal layout. If the package usesexportsfield gating in its package.json, this can break on a minor upgrade. Prefer a public re-export (often available via@rjsf/shadcnor itsTemplates/getDefaultRegistry) if exposed.#!/bin/bash # Inspect `@rjsf/shadcn`'s package.json to see whether deep '/lib/...js' subpaths are part of its public 'exports' map. fd -i 'package.json' -t f node_modules/@rjsf/shadcn -d 2 2>/dev/null | head -5 # Fall back to repo-local lockfile to identify version & published exports surface rg -nP '"@rjsf/shadcn"' -g 'package.json' -g 'pnpm-lock.yaml' | head -20`@rjsf/shadcn` 6.5.1 BaseInputTemplate public export path🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@ui/src/features/dags/components/dag-execution/schemaFormTemplates.tsx` at line 1, The import is using a deep path into `@rjsf/shadcn` ("@rjsf/shadcn/lib/BaseInputTemplate/BaseInputTemplate.js") which can break on upgrades; replace it with the package's public export instead (e.g., import BaseInputTemplate from the top-level "@rjsf/shadcn" or pull it from the package's exported Templates/getDefaultRegistry API) so that the code uses an official public symbol (referencing BaseInputTemplate and any Templates/getDefaultRegistry helpers) rather than a private lib subpath.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@ui/src/features/dags/components/dag-execution/__tests__/StartDAGModal.test.tsx`:
- Around line 106-113: Add an end-to-end test in StartDAGModal.test.tsx that
exercises the typed-fields mode submission flow: render StartDAGModal, switch to
typed-fields mode, fill the typed fields to produce the JSON array parameter
serialization, trigger submit, and assert onSubmit was invoked with the JSON
array payload (instead of the JSON object string). Also add a server-side
integration test for parseStringParams that sends the JSON array representation
and asserts it is parsed into the expected parameters; reference the
StartDAGModal test helper that sets onSubmit and the parseStringParams function
on the backend to locate the relevant code paths.
In `@ui/src/features/dags/components/dag-execution/StartDAGModal.tsx`:
- Line 319: Replace the untyped ref declaration `const schemaFormRef =
React.useRef<any>(null);` with a properly typed ref so `validateForm()` is
type-safe: import or reference the `Form` component and declare `const
schemaFormRef = React.useRef<React.ComponentRef<typeof Form> | null>(null);` (or
use `React.ComponentRef<typeof Form>` directly where appropriate), then use
`schemaFormRef.current?.validateForm()` to call validation—this removes `any`
and ensures `validateForm` is recognized by TypeScript.
---
Nitpick comments:
In `@internal/core/spec/dag_param_schema.go`:
- Around line 45-47: Extract the long conjunctive check into a small helper like
hasUnsupportedRootConstruct that accepts *jsonschema.Schema (referencing the
variable root used in the current checks) and returns a bool; replace the
current long boolean expression in the early-return (the block using
root.PatternProperties, root.AllOf, root.AnyOf, root.OneOf, root.Not, root.If,
root.Then, root.Else, root.Items, root.PrefixItems, root.Contains) with a call
to that helper, and do the same for the analogous check later (the similar
expression at the other return site) so both places use the new function.
- Line 99: The assignment result.Enum = cloneAny(schema.Enum).([]any) relies on
a type assertion; instead, iterate over schema.Enum, clone each element via
cloneAny and append to a new []any slice, then assign that slice to result.Enum
so element-level cloning is explicit and matches the defensive pattern used
elsewhere (update code handling of schema.Enum in the logic around cloneAny,
result.Enum, and schema.Enum).
In `@internal/service/frontend/api/v1/transformer_test.go`:
- Around line 119-134: Add a negative-path unit test alongside
TestToDAGDetailsIncludesParamSchema that calls toDAGDetails with a core.DAG that
has no ParamSchema (e.g., core.DAG{}) and another case with an invalid
ParamSchema (malformed JSON) and assert that details.ParamSchema == nil for
both; locate and update the test in transformer_test.go and reference the
existing toDAGDetails/toJSONObject behavior so the new test locks in the
nil-return contract against regressions.
In `@internal/service/frontend/api/v1/transformer.go`:
- Around line 443-453: toJSONObject currently hides JSON unmarshal failures by
returning nil for both empty and malformed input; update to log the unmarshal
error (including the error value and a brief context such as "failed to
unmarshal dag.ParamSchema produced by buildRenderableParamSchema") before
returning nil. Locate the toJSONObject function and add a logging statement that
includes the raw input length or a snippet and the error (so operators can see
internal inconsistencies between buildRenderableParamSchema and dag.ParamSchema)
while preserving the existing nil return behavior for len(raw)==0 and on error.
In
`@ui/src/features/dags/components/dag-execution/__tests__/StartDAGModal.test.tsx`:
- Around line 50-52: The test teardown currently only calls
renderedFormProps.mockClear() in afterEach, which can miss other vi.fn() state;
replace or complement this with a beforeEach(() => vi.clearAllMocks()) to ensure
all mocks (including renderedFormProps and any future vi.fn() instances) are
reset before each test, or alternatively call vi.clearAllMocks() in afterEach
for symmetry; update the test file to add the beforeEach/afterEach call
referencing renderedFormProps and vi.clearAllMocks accordingly.
In `@ui/src/features/dags/components/dag-execution/paramSchemaForm.ts`:
- Around line 60-82: The function coerceParamSchemaValue incorrectly coerces an
empty string to 0 for 'integer'/'number' schemas; update coerceParamSchemaValue
to first check if the incoming value is an empty string (e.g., value.trim() ===
'') and return the original value unchanged before calling inferScalarType /
attempting Number(value) conversion; keep this check at the top of
coerceParamSchemaValue so integer/number/boolean branches don't convert blank
input and retain existing behavior for non-empty strings.
In `@ui/src/features/dags/components/dag-execution/schemaFormTemplates.tsx`:
- Line 1: The import is using a deep path into `@rjsf/shadcn`
("@rjsf/shadcn/lib/BaseInputTemplate/BaseInputTemplate.js") which can break on
upgrades; replace it with the package's public export instead (e.g., import
BaseInputTemplate from the top-level "@rjsf/shadcn" or pull it from the
package's exported Templates/getDefaultRegistry API) so that the code uses an
official public symbol (referencing BaseInputTemplate and any
Templates/getDefaultRegistry helpers) rather than a private lib subpath.
In `@ui/src/features/dags/components/dag-execution/schemaFormWidgets.tsx`:
- Around line 142-163: The currentValue computation returns undefined when value
doesn't match any enumOptions (via enumOptionsIndexForValue) which leaves the
radio group unselected; add a brief inline comment inside the React.useMemo
(near currentValue, enumOptionsIndexForValue, enumOptionValueEncoder,
enumOptions, and value references) stating this is intentional and that we
intentionally do not fall back to encoding the raw value so stale/out-of-range
defaults remain visually unselected, to prevent future maintainers from "fixing"
this behavior.
In `@ui/src/features/dags/components/dag-execution/StartDAGModal.tsx`:
- Around line 256-262: The memoized paramSchema currently accepts any plain
object from dagDetails.paramSchema; update the guard in the React.useMemo so it
also rejects schemas that lack a non-empty properties object (e.g., ensure
schema.properties is an object and Object.keys(schema.properties).length > 0)
before returning schema as JSONSchema; this prevents returning {} or schemas
without properties and avoids rendering an empty form.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: e39c5913-127d-4045-827a-b0ce6155f24b
⛔ Files ignored due to path filters (1)
ui/pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (17)
api/v1/api.gen.goapi/v1/api.yamlinternal/core/dag.gointernal/core/spec/dag.gointernal/core/spec/dag_param_schema.gointernal/core/spec/dag_params.gointernal/core/spec/dag_params_inline_schema_test.gointernal/service/frontend/api/v1/transformer.gointernal/service/frontend/api/v1/transformer_test.goui/package.jsonui/src/api/v1/schema.tsui/src/features/dags/components/dag-execution/StartDAGModal.tsxui/src/features/dags/components/dag-execution/__tests__/StartDAGModal.test.tsxui/src/features/dags/components/dag-execution/__tests__/paramSchemaForm.test.tsui/src/features/dags/components/dag-execution/paramSchemaForm.tsui/src/features/dags/components/dag-execution/schemaFormTemplates.tsxui/src/features/dags/components/dag-execution/schemaFormWidgets.tsx
| await waitFor(() => | ||
| expect(onSubmit).toHaveBeenCalledWith( | ||
| '{"region":"us-west-2","count":5}', | ||
| undefined, | ||
| true | ||
| ) | ||
| ); | ||
| }); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Look at how the DAG-run start handler parses params to confirm it accepts: KEY="VAL" strings, JSON arrays, and JSON objects.
rg -nP -C5 '\bParams\s*[:=]|defaultParams|parseParams' --type=go -g 'internal/**' -g '!**/*_test.go' | head -200
ast-grep --pattern 'func ($_ $_) Start($$$) ($$$)'Repository: dagucloud/dagu
Length of output: 28659
🏁 Script executed:
# Find the HTTP handler for POST /dags/.../start endpoint
rg -n 'start|Start' --type=go internal/service/handler -A5 -B2 | grep -E '(func|Start|POST|Dags)' | head -50Repository: dagucloud/dagu
Length of output: 123
🏁 Script executed:
# Search for the API endpoint handler or router that maps to /dags/{id}/start
rg -n '/dags.*start|StartDAG|StartRun' --type=go --iglob '!**/*_test.go' | head -40Repository: dagucloud/dagu
Length of output: 1024
🏁 Script executed:
# Look at the test file to see what formats are being tested
cat -n ui/src/features/dags/components/dag-execution/__tests__/StartDAGModal.test.tsx | head -150Repository: dagucloud/dagu
Length of output: 4731
🏁 Script executed:
# Find the ExecuteDAG handler implementation
rg -n 'ExecuteDAG|func.*ExecuteDAG' --type=go --iglob '!**/*_test.go' -A10 | head -80Repository: dagucloud/dagu
Length of output: 5776
🏁 Script executed:
# Find the StartDAGModal component implementation
cat -n ui/src/features/dags/components/dag-execution/StartDAGModal.tsx | head -200Repository: dagucloud/dagu
Length of output: 7175
🏁 Script executed:
# Search for other test files or tests that test different parameter modes
fd -e test.tsx -e test.ts ui/src/features/dags/ | xargs rg -l 'onSubmit|params' | head -5Repository: dagucloud/dagu
Length of output: 618
🏁 Script executed:
# Find paramSchemaForm.tsx to see how schema-mode params are serialized
fd . ui/src/features/dags -name 'paramSchemaForm*' -type fRepository: dagucloud/dagu
Length of output: 289
🏁 Script executed:
# Find the stringifyParamSchemaFormData function
rg -n 'stringifyParamSchemaFormData' ui/src -A15 | head -40Repository: dagucloud/dagu
Length of output: 3862
🏁 Script executed:
# Find the actual ExecuteDAG handler implementation in the API service
rg -n 'func.*ExecuteDAG.*\(.*w.*http|func.*\(.*API.*\).*ExecuteDAG' --type=go -A20 | head -100Repository: dagucloud/dagu
Length of output: 7198
🏁 Script executed:
# Read the full ExecuteDAG handler implementation
sed -n '893,950p' internal/service/frontend/api/v1/dags.goRepository: dagucloud/dagu
Length of output: 1685
🏁 Script executed:
# See how params are passed through the system
rg -n 'request.Body.Params' internal/service/frontend/api/v1/dags.go -A5 -B5 | head -80Repository: dagucloud/dagu
Length of output: 1632
🏁 Script executed:
# Look at the complete flow - check what ExecuteDAGJSONBody expects
rg -n 'type ExecuteDAGJSONBody' api/v1/api.gen.go -A10Repository: dagucloud/dagu
Length of output: 672
🏁 Script executed:
# Find executeDAGRun implementation
rg -n 'func.*executeDAGRun|func.*enqueueDAGRun' internal/service/frontend/api/v1/dags.go -A30 | head -100Repository: dagucloud/dagu
Length of output: 1189
🏁 Script executed:
# Look for where params are validated or processed
rg -n 'ValidateStartParams|startparams|parseParams' internal/service/frontend/api/v1/dags.go -B3 -A3Repository: dagucloud/dagu
Length of output: 436
🏁 Script executed:
# Find ResolveRuntimeParams implementation
rg -n 'func.*ResolveRuntimeParams' --type=go -A30 | head -100Repository: dagucloud/dagu
Length of output: 7091
🏁 Script executed:
# Also search for the parseParamValue function which handles parsing different formats
rg -n 'func.*parseParamValue' --type=go -A20 | head -80Repository: dagucloud/dagu
Length of output: 1390
🏁 Script executed:
# Find actual ResolveRuntimeParams implementation (not test)
rg -n 'func ResolveRuntimeParams' --type=go --iglob '!**/*_test.go' -A30Repository: dagucloud/dagu
Length of output: 2325
🏁 Script executed:
# Look at dag_params_runtime.go to see the full implementation
cat -n internal/core/spec/dag_params_runtime.go | head -200Repository: dagucloud/dagu
Length of output: 7169
🏁 Script executed:
# Find parseStringParams function to see how it handles string input
rg -n 'func parseStringParams' --type=go -A20 | head -50Repository: dagucloud/dagu
Length of output: 1367
🏁 Script executed:
# Search for JSON unmarshaling of params
rg -n 'json.Unmarshal.*params|json.Decode.*params' --type=go -B3 -A3Repository: dagucloud/dagu
Length of output: 1946
🏁 Script executed:
# Find tryParseJSONParams to verify it handles both objects and arrays
rg -n 'func tryParseJSONParams' --type=go -A30Repository: dagucloud/dagu
Length of output: 2005
🏁 Script executed:
# Search for server-side tests that cover all three parameter serialization modes
fd . internal -name '*test.go' -type f | head -20 | xargs rg -l 'ExecuteDAG|startDAG' | head -5Repository: dagucloud/dagu
Length of output: 467
🏁 Script executed:
# Search for integration tests that test ExecuteDAG with different param formats
rg -n 'ExecuteDAG|startDAG' internal/intg --type=go -B5 -A10 | head -150Repository: dagucloud/dagu
Length of output: 4333
🏁 Script executed:
# Check the E2E tests for parameter handling
rg -n 'params|Params' ui/e2e/dag-run-actions.spec.ts -B2 -A2 | head -80Repository: dagucloud/dagu
Length of output: 40
🏁 Script executed:
# Search for tests specifically for typed-fields mode (JSON array)
rg -n 'serializeParamFields|JSON.stringify.*items' ui/src --type=ts -B5 -A5Repository: dagucloud/dagu
Length of output: 3367
🏁 Script executed:
# Look for all tests in StartDAGModal test file to see if there are other test cases
wc -l ui/src/features/dags/components/dag-execution/__tests__/StartDAGModal.test.tsxRepository: dagucloud/dagu
Length of output: 141
🏁 Script executed:
# Check if there are other test files for StartDAGModal or parameter handling
fd . ui/src/features/dags -name '*test*' -o -name '*spec*' | grep -i 'param\|start'Repository: dagucloud/dagu
Length of output: 289
🏁 Script executed:
# Check the full StartDAGModal test file to see all tests
cat ui/src/features/dags/components/dag-execution/__tests__/StartDAGModal.test.tsxRepository: dagucloud/dagu
Length of output: 3758
Add end-to-end test for JSON array parameter format sent by typed-fields mode.
The test asserts onSubmit is invoked with the JSON object '{"region":"us-west-2","count":5}' (schema mode). The backend's parseStringParams correctly handles all three serialization formats: JSON objects, JSON arrays (from typed-fields mode), and KEY="VALUE" strings (from raw mode). However, the JSON array format lacks end-to-end coverage—the typed-fields rendering test doesn't verify submission, and no backend integration test confirms JSON array parameters work correctly. Consider adding a test case where typed-fields submits a JSON array payload and a corresponding server-side integration test.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@ui/src/features/dags/components/dag-execution/__tests__/StartDAGModal.test.tsx`
around lines 106 - 113, Add an end-to-end test in StartDAGModal.test.tsx that
exercises the typed-fields mode submission flow: render StartDAGModal, switch to
typed-fields mode, fill the typed fields to produce the JSON array parameter
serialization, trigger submit, and assert onSubmit was invoked with the JSON
array payload (instead of the JSON object string). Also add a server-side
integration test for parseStringParams that sends the JSON array representation
and asserts it is parsed into the expected parameters; reference the
StartDAGModal test helper that sets onSubmit and the parseStringParams function
on the backend to locate the relevant code paths.
| : undefined, | ||
| [paramSchema] | ||
| ); | ||
| const schemaFormRef = React.useRef<any>(null); |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
@rjsf/core 6.5.1 Form ref imperative handle TypeScript type
💡 Result:
In @rjsf/core version 6.5.1, the Form component is a class component that supports ref forwarding for imperative handle access. The ref type is RefObject<Form<T, S, F>> where Form is the class itself, generic over T (formData type, default any), S (schema type extending StrictRJSFSchema, default RJSFSchema), F (formContext type extending FormContextType, default any). The Form instance exposes these imperative methods: - submit: void - Programmatically submits the form - validateForm: boolean - Validates current form data and returns validity - reset: void - Resets form to initial state - focusOnError(error: RJSFValidationError): void - Focuses on field with given error Usage example: import React, { useRef } from 'react'; import Form from '@rjsf/core'; import { RJSFSchema } from '@rjsf/utils'; import validator from '@rjsf/validator-ajv8'; const schema: RJSFSchema = { /* your schema */ }; const MyForm = => { const formRef = useRef
Citations:
- 1: https://github.com/rjsf-team/react-jsonschema-form/blob/main/packages/core/src/components/Form.tsx
- 2: https://rjsf-team.github.io/react-jsonschema-form/docs/advanced-customization/typescript/
- 3: https://www.npmjs.com/package/@rjsf/core
- 4: https://rjsf-team.github.io/react-jsonschema-form/docs/advanced-customization/typescript
- 5: https://tessl.io/registry/tessl/npm-rjsf--core/files/docs/form-component.md
- 6: The FormProps interface should include the ref prop and the Form class should include the state generic. rjsf-team/react-jsonschema-form#2186
- 7: https://github.com/rjsf-team/react-jsonschema-form/releases/tag/v6.0.2
🏁 Script executed:
# Check the Form import and typing in StartDAGModal.tsx
head -50 ui/src/features/dags/components/dag-execution/StartDAGModal.tsx | grep -E "import|Form"Repository: dagucloud/dagu
Length of output: 1120
🏁 Script executed:
# Also check around line 319 for context
sed -n '310,330p' ui/src/features/dags/components/dag-execution/StartDAGModal.tsxRepository: dagucloud/dagu
Length of output: 610
🏁 Script executed:
# Check package.json for `@rjsf/core` version
cat ui/package.json | grep -A 2 -B 2 "@rjsf"Repository: dagucloud/dagu
Length of output: 341
🏁 Script executed:
# Find all usages of schemaFormRef in the file
grep -n "schemaFormRef" ui/src/features/dags/components/dag-execution/StartDAGModal.tsxRepository: dagucloud/dagu
Length of output: 222
🏁 Script executed:
# Search for the validateForm call and surrounding context
grep -n "validateForm" ui/src/features/dags/components/dag-execution/StartDAGModal.tsx -A 3 -B 3Repository: dagucloud/dagu
Length of output: 325
🏁 Script executed:
# Check what `@rjsf/shadcn` exports as Form
node << 'EOF'
const fs = require('fs');
// Check if we can inspect the package structure
const packagePath = 'ui/node_modules/@rjsf/shadcn/package.json';
try {
if (fs.existsSync(packagePath)) {
const pkg = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
console.log('Main export:', pkg.main);
console.log('Module export:', pkg.module);
console.log('Exports field:', JSON.stringify(pkg.exports || {}, null, 2));
} else {
console.log('Package not found in node_modules');
}
} catch (e) {
console.log('Error:', e.message);
}
EOFRepository: dagucloud/dagu
Length of output: 92
🏁 Script executed:
# Check the Form usage at line 536 and surrounding context
sed -n '530,545p' ui/src/features/dags/components/dag-execution/StartDAGModal.tsxRepository: dagucloud/dagu
Length of output: 606
🏁 Script executed:
# Look for any other type hints or patterns in similar files
find ui/src -name "*.tsx" -type f -exec grep -l "useRef.*Form" {} \; | head -5Repository: dagucloud/dagu
Length of output: 40
🏁 Script executed:
# Check if there are any TypeScript definitions or type declarations
find ui -name "*.d.ts" -o -name "tsconfig.json" | head -5Repository: dagucloud/dagu
Length of output: 145
🏁 Script executed:
# Check tsconfig.json for TypeScript configuration
cat ui/tsconfig.json | head -40Repository: dagucloud/dagu
Length of output: 774
🏁 Script executed:
# Search for other ref patterns with Form in the codebase
rg "useRef.*Form|ref.*Form" ui/src --type tsx -A 2 -B 2 | head -40Repository: dagucloud/dagu
Length of output: 86
🏁 Script executed:
# Try searching for useRef patterns with proper regex
rg "useRef.*Form" ui/src --max-count 20 -A 1 -B 1Repository: dagucloud/dagu
Length of output: 40
🏁 Script executed:
# Check if Form is explicitly typed anywhere else
rg "Form\>" ui/src/features/dags/components/dag-execution/StartDAGModal.tsx -B 2 -A 2 | head -50Repository: dagucloud/dagu
Length of output: 135
🏁 Script executed:
# Search for Form usage in the file more carefully
grep "Form" ui/src/features/dags/components/dag-execution/StartDAGModal.tsx | head -20Repository: dagucloud/dagu
Length of output: 960
🏁 Script executed:
# Check the documentation or type hints for `@rjsf/shadcn`
rg "export.*Form" ui --max-count 10Repository: dagucloud/dagu
Length of output: 1084
🏁 Script executed:
# Check the actual type definition or documentation for `@rjsf/shadcn` Form
# Try to find any documentation in the project or package
find ui -name "*.md" -type f -exec grep -l "Form.*ref\|useRef" {} \;Repository: dagucloud/dagu
Length of output: 40
🏁 Script executed:
# Let's check if there are any compilation errors currently or type checking
cd ui && npx tsc --noEmit 2>&1 | grep -i "schemaFormRef\|StartDAGModal" | head -20Repository: dagucloud/dagu
Length of output: 40
Replace useRef<any> with a properly typed ref.
The ref should be typed against the Form component to ensure type safety for the validateForm() call and comply with TypeScript strict mode. Since Form is a class component from @rjsf/shadcn, use React.ComponentRef<typeof Form> to extract its instance type.
♻️ Suggested change
- const schemaFormRef = React.useRef<any>(null);
+ const schemaFormRef = React.useRef<React.ComponentRef<typeof Form>>(null);This eliminates the any type and makes the validateForm() call at line 375 self-documenting, as required by the coding guideline: "Use type annotations in TypeScript files".
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const schemaFormRef = React.useRef<any>(null); | |
| const schemaFormRef = React.useRef<React.ComponentRef<typeof Form>>(null); |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@ui/src/features/dags/components/dag-execution/StartDAGModal.tsx` at line 319,
Replace the untyped ref declaration `const schemaFormRef =
React.useRef<any>(null);` with a properly typed ref so `validateForm()` is
type-safe: import or reference the `Form` component and declare `const
schemaFormRef = React.useRef<React.ComponentRef<typeof Form> | null>(null);` (or
use `React.ComponentRef<typeof Form>` directly where appropriate), then use
`schemaFormRef.current?.validateForm()` to call validation—this removes `any`
and ensures `validateForm` is recognized by TypeScript.
Summary
Testing
Closes #2033
Summary by CodeRabbit